// HackTheBox

GoodGames

SQLi Auth-Bypass → SSTI RCE → Bind-Mount-Container-Ausbruch zu Host-Root

HackTheBoxLeichtLinux 21. Juli 2026 4 Min. Lesezeit

Aufklärung

Nur ein Port ist offen:

Port Dienst
80 HTTP (Werkzeug/Flask) — GoodGames

Das Werkzeug-Server-Banner weist auf eine Python-Flask-Anwendung hin. Eine selbstgebaute Flask-Seite (im Gegensatz zu einem fertigen CMS) bedeutet, dass die interessanten Bugs eher im eigenen Code der App stecken — in ihrem Login-Formular und ihrem Templating — als in einem bekannten CVE. Das Login-Formular ist also das Erste, was man abklopft.

Erstzugang

1. SQL-Injection im Login-Formular

Das /login der Hauptseite nimmt eine E-Mail-Adresse entgegen, und dieses Feld ist injizierbar. Zwei Dinge fallen dabei heraus:

  • Auth-Bypass. Eine klassische Tautologie im E-Mail-Feld macht die WHERE-Klausel immer wahr und loggt uns ohne gültiges Passwort ein:

    ' OR 1=1-- -
    
  • UNION-Dump. Die Abfrage gibt vier Spalten zurück, und die vierte wird als Welcome <x> in die Seite reflektiert, sodass ein UNION SELECT unsere eigenen Daten auf das Ergebnis pfropft und Spalte 4 auf dem Bildschirm ausgibt:

    ' UNION SELECT 1,2,3,4-- -
    

Setzt man echte Spaltenausdrücke an Position 4 ein, kann man die Datenbank auslesen. Das Auslesen der user-Tabelle liefert den Administrator-Account admin@goodgames.htb und seinen Passwort-Hash 2b22337f218b2d82dfc3b6f77e7cb8ec. Das ist ein blanker 32-Hex-Zeichen-MD5 — ungesalzen und schnell — er knackt also fast augenblicklich gegen rockyou.txt:

$ echo 2b22337f218b2d82dfc3b6f77e7cb8ec > hash.txt
$ john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Der Klartext ist superadministrator.

2. Der Admin-vhost und seine Login-Falle

Diese Credentials schalten auf der Hauptseite nichts Neues frei — sie gehören zu einer zweiten App auf dem vhost internal-administration.goodgames.htb (in /etc/hosts eintragen). Es ist eine separate Flask-Anwendung, die das Volt-Admin-Template verwendet.

Ihr Login hat eine Falle. Das Submit-Element ist ein benannter Button:

<button type="submit" name="login">

und die Route führt ihren Authentifizierungs-Zweig nur aus, wenn dieses login-Feld im POST-Body vorhanden ist. Bildet man den Login außerhalb eines Browsers nach und lässt login weg, erfolgt bei der Anfrage immer ein 302 auf /index, ohne jemals die _user_id-Session zu setzen — was genau wie ein defekter oder abgelehnter Login aussieht. Nimmt man login= in den Body auf, zusammen mit admin:superadministrator, feuert der Auth-Zweig und loggt uns ein.

3. Server-Side Template Injection → RCE

Im Admin-Panel nimmt /settings einen name-Wert entgegen und reflektiert ihn in die gerenderte Seite zurück. Da die App diesen Wert direkt in ein Jinja2-Template (Flasks Engine) einsetzt, ist sie anfällig für SSTI — Server-Side Template Injection, bei der Eingabe als Template-Code ausgewertet wird, statt als Text ausgegeben zu werden. Eine schnelle Rechen-Probe bestätigt es: die Eingabe {{1337*1337}} wird als 1787569 gerendert, der Server wertet also unsere geschweiften Klammern aus.

Von der Arithmetik zur Codeausführung: laufe Jinja2s Objektgraph hinaus zu Pythons os-Modul und rufe popen auf:

{{ cycler.__init__.__globals__.os.popen('CMD').read() }}

Ersetzt man CMD, führt es Shell-Befehle aus und gibt ihre Ausgabe zurück. Das bringt uns als Root, aber innerhalb eines Docker-Containers (ein isolierter Host mit der ID 3a453ab...). Der Container hat /home/augustus per Bind-Mount von der /dev/sda1-Platte des echten Hosts eingebunden — ein Verzeichnis, das direkt vom Host durchgereicht wird — sodass die User-Flag direkt von diesem Mount gelesen werden kann.

Rechteausweitung — Bind-Mount-Container-Root → Host-Root

Root innerhalb des Containers zu sein ist nicht Root auf dem Host, aber der Bind-Mount ist die Brücke: Dateien, die unter /home/augustus aus dem Container geschrieben werden, landen auf dem Host, im Besitz von root, weil wir das im Container sind. Das Problem ist, diesen Datei-Schreibzugriff in Codeausführung auf dem Host zu verwandeln.

  • Den Host-Account erreichen. Das SSH des Hosts (Port 22) ist nicht zum VPN exponiert, aber der Host ist vom Container aus über das Docker-Bridge-Gateway 172.19.0.1 erreichbar. Der Zielaccount dort ist augustus, dessen SSH-Passwort nicht superadministrator ist. Da wir sein Home-Verzeichnis über den Mount beschreiben können, platzieren wir einen SSH-Key, statt das Passwort zu raten — legen unseren Public-Key in seine authorized_keys und loggen uns ein:

    $ cat attacker.pub >> /home/augustus/.ssh/authorized_keys
    $ ssh -i attacker augustus@172.19.0.1
    
  • Von augustus zu Host-Root. Der saubere Trick wäre, eine root-eigene SUID-Shell auf dem Mount abzulegen, aber die /bin/bash des Containers auf den Mount zu kopieren und auf dem Host auszuführen schlägt fehl — der Host ist eine andere Distribution und dem fehlt libtinfo.so.5, das Binary läuft dort also nicht. Die Lösung ist, die eigene bash des Hosts zu verwenden. Kopiere als augustus die /bin/bash des Hosts in sein Home-Verzeichnis:

    $ cp /bin/bash /home/augustus/hostbash
    

    Dann, von Container-Root aus über den Mount, übernimm den Besitz und setze das SUID-Bit (die 4 in 4777 bedeutet “als Besitzer der Datei ausführen”):

    $ chown root:root /home/augustus/hostbash
    $ chmod 4777 /home/augustus/hostbash
    

    Nun ist hostbash eine root-eigene SUID-Kopie der Shell des Hosts. Zurück als augustus, führe sie mit -p aus, damit sie ihre erhöhten Privilegien behält, statt sie abzugeben:

    $ ./hostbash -p
    

    Das liefert euid=0 auf dem Host selbst — root, und root.txt.

← Alle Writeups