root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat soccer.md
// writeups

Soccer

17 Dec 2022 · 23 min read · root access
Soccer - maquina de Hack The Box

Executive summary — Soccer chains factory credentials on Tiny File Manager with an unrestricted file upload to get RCE as www-data, discovers a second vhost from there exposing a Node application over WebSocket, exploits a blind, numeric-context SQL injection carried over that WebSocket (not HTTP, so standard tooling never reaches it) to steal database credentials, and closes the loop with a password reuse into SSH plus a doas rule that allows loading arbitrary Python plugins into dstat as root.

PlatformHack The Box
Operating systemLinux
DifficultyEasy
StatusRetired
Target IP10.129.43.221

Attack map

[80] soccer.htb — static site   [9091] Express unidentified by nmap
   │  directory brute force -> /tiny/ -> Tiny File Manager 2.4.3
   ▼
[tiny] admin:admin@123 (factory credentials) -> full file manager
   │  unrestricted-extension upload to /tiny/uploads/ (served with PHP)
   ▼
[RCE]  www-data  (PHP webshell)
   │  /etc/nginx/sites-enabled reveals hidden vhost soc-player.soccer.htb
   │  -> proxies to Node on :3000, real WebSocket on :9091
   ▼
[9091] WebSocket — "id" parameter interpolated unquoted in a template literal
   │  Blind SQL injection (numeric context) -> dump of accounts
   ▼
[creds] player:PlayerOftheMatch2022 (DB, plaintext) -> reused over SSH
   ▼
[SSH]  player  (user.txt)
   │  doas NOPASSWD /usr/bin/dstat + plugin directory writable by
   │  the player group -> dstat loads and runs Python as root
   ▼
[ROOT]  malicious dstat Python plugin -> SUID bash -> root.txt

1. Reconnaissance

nmap -p- --min-rate 5000 -T4 -Pn -oN nmap-allports.txt 10.129.43.221
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http
9091/tcp  open     xmltec-xmlmail
9287/tcp  filtered cumulus
22177/tcp filtered unknown
...  (9 more "filtered" ports)

The scattered, randomly-numbered "filtered" ports are noise from the aggressive --min-rate (dropped SYNs with no response or RST), not a real firewall — a real firewall filters coherent ranges, not random stray ports. What matters is 9091/tcp open: a non-standard high port is usually a custom application, not a packaged, hardened service.

nmap -sCV -p22,80,9091 -Pn -oN nmap-services.txt 10.129.43.221
22/tcp   open  http            OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http            nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soccer.htb/
9091/tcp open  xmltec-xmlmail?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 404 Not Found
|     Content-Security-Policy: default-src 'none'
|     X-Content-Type-Options: nosniff
|     <pre>Cannot GET /</pre>
1 service unrecognized despite returning data.

nmap can't identify the service on 9091, but the fingerprint gives it away: Cannot GET / is the exact text of Express.js's default 404 handler, and the Content-Security-Policy/X-Content-Type-Options headers are the ones Express adds automatically to its error responses. It's a Node/Express app that serves nothing at / — either it only handles specific routes, or it doesn't speak plain HTTP at all.

echo "10.129.43.221 soccer.htb" | sudo tee -a /etc/hosts

With both surfaces confirmed (a static site on 80, an unidentified Express app on 9091), it's time to enumerate each.

🔒 Clearance required

This content is Root Access only. Everything else on the site — the free tier, the whole public library — stays open.

See Root Access plans

No account? Create one free then upgrade from your console.