Overview
Machine author: TheCyberGeek. IP: 10.10.10.x.
- Recon: only port 80 is open (Werkzeug/Python 3.9.2, a Flask application).
- SQL injection in the login form: authentication bypass plus a dump of the
maindatabase. - The admin’s MD5 hash cracks to
superadministrator. - Subdomain
internal-administration.goodgames.htb: a Flask panel, password reuse. - SSTI in the profile field: RCE, a reverse shell, root inside a Docker container.
- Docker escape via a shared bind-mount plus the SUID bit: root on the host.
Reconnaissance
| |
Port 80: Werkzeug/2.0.2 Python/3.9.2, title GoodGames | Community and Store. The
footer reveals the domain goodgames.htb.
| |
Initial Access
SQL injection
Login form bypass payload:
| |
The response is “Welcome admin”. Save the Burp request to goodgames.req and run sqlmap:
| |
| id | name | password | |
|---|---|---|---|
| 1 | admin | [email protected] | 2b22337f218b2d82dfc3b6f77e7cb8ec |
Cracking the hash
The hash is MD5 (32 hex chars). Crack it, for example on CrackStation:
| |
Logging in as admin, the panel shows a gear icon leading to a new subdomain:
| |
internal-administration.goodgames.htb is a Flask panel (Volt Dashboard). Password reuse:
admin / superadministrator works.
SSTI to RCE
The profile settings allow changing Full Name. SSTI test:
| |
Reverse shell: base64 the payload and start a listener.
| |
SSTI payload in Full Name (${IFS} replaces spaces):
| |
The shell lands as root, but in a container:
| |
user.txt is in the user’s home directory inside the container.
Privilege Escalation
Docker escape
The host home directory is mounted into the container read-write:
| |
The raw UID 1000 instead of a name indicates the directory comes from the host (there is
no user with UID 1000 in the container, so the kernel shows the number). The mount is
read-write and without nosuid.
The host is reachable from the Docker network:
| |
Port scan the host with pure bash (no nmap):
| |
Password reuse on the host SSH, augustus / superadministrator:
| |
The exploit itself, two halves of one key:
| |
Three conditions must hold together:
- A shared bind-mount (rw, no
nosuid):/home/augustusis the same on-disk directory for the host and the container. - No user-namespace remapping (
userns-remap): root in the container has numerically the same UID 0 as root on the host. File ownership is stored as a bare number on the shared filesystem. - SUID semantics plus a shared kernel: the SUID bit tells the kernel to run the file
with the owner’s effective UID, and the mount lacks
nosuid, so the host honours the bit.
Root in the container plants a SUID-root file on the shared disk, and the regular
augustus on the host runs it and gets euid=0. Neither half alone is sufficient.
Detection and Mitigation
- Do not mount host directories into containers read-write unless necessary; consider
nosuid. - Enable user namespace remapping so root in the container is not root on the host.
- Do not reuse passwords across the application, the panel and system accounts.
- Sanitize input (SQLi), use strong hashes (not MD5), and avoid
render_template_stringon user data (SSTI).
Lessons Learned
- A raw UID instead of a username in a container listing means a host mount.
- Bash without
nmap:</dev/tcp/host/portis a working port scanner. - Docker escape here is two halves of one key: root-in-container plants the SUID file, the host user runs it.
Command Reference
| |