// HackTheBox
GoodGames
SQLi Auth-Bypass → SSTI RCE → Bind-Mount-Container-Ausbruch zu Host-Root
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 einUNION SELECTunsere 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.1erreichbar. Der Zielaccount dort istaugustus, dessen SSH-Passwort nichtsuperadministratorist. 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 seineauthorized_keysund 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/bashdes Containers auf den Mount zu kopieren und auf dem Host auszuführen schlägt fehl — der Host ist eine andere Distribution und dem fehltlibtinfo.so.5, das Binary läuft dort also nicht. Die Lösung ist, die eigene bash des Hosts zu verwenden. Kopiere alsaugustusdie/bin/bashdes Hosts in sein Home-Verzeichnis:$ cp /bin/bash /home/augustus/hostbashDann, von Container-Root aus über den Mount, übernimm den Besitz und setze das SUID-Bit (die
4in4777bedeutet “als Besitzer der Datei ausführen”):$ chown root:root /home/augustus/hostbash $ chmod 4777 /home/augustus/hostbashNun ist
hostbasheine root-eigene SUID-Kopie der Shell des Hosts. Zurück alsaugustus, führe sie mit-paus, damit sie ihre erhöhten Privilegien behält, statt sie abzugeben:$ ./hostbash -pDas liefert
euid=0auf dem Host selbst — root, und root.txt.