Fetch the Flag CTF 2022: Write-up zu Moongoose
Jason Lynch
10. November 2022
0 Min. LesezeitDanke, dass Sie mit uns Fetch gespielt haben! Herzlichen Glückwunsch an die Tausenden von Spielerinnen und Spielern, die bei Fetch the Flag CTF dabei waren. Und ein großes Dankeschön an die Snyker, die die Challenges erstellt, getestet und dokumentiert haben!
Als Snyk-Mitarbeiter hatte ich die Gelegenheit, gemeinsam mit meinen Teamkollegen an einem „Betatest“ des Fetch the Flag-Wettbewerbs 2022 von Snyk teilzunehmen. Wir hatten viel Spaß dabei, diese Challenges gemeinsam zu lösen, und ich persönlich habe viel aus dieser Erfahrung gelernt. In diesem Write-up geht es um eine Challenge, die mir besonders gut gefallen hat: Moongoose.
Challenge
Wenn Sie geglaubt haben, dass sie eine Gans auf den Mond gebracht haben
Diese Challenge beginnt auf einer kryptischen Login-Seite mit Mond-und-Gans-Thema. Um an die Flag zu gelangen, müssen wir mehrere Schwachstellen ausnutzen: Directory Traversal, NoSQL-Injection und ein kniffliges Verhalten von JavaScript. Wie bei jeder anderen CTF-Challenge ist unser erster Schritt, einen Einstiegspunkt zu finden.
Schritt-für-Schritt-Anleitung
Einen Einstiegspunkt finden
Vielleicht ist Ihnen zuerst aufgefallen, dass sich der Name dieser Challenge, „Moongoose“, nur um einen Buchstaben von „Mongoose“ unterscheidet – so heißt ein beliebtes MongoDB-Framework für Node.js. Könnte das ein Hinweis auf die Lösung sein? Finden wir heraus, ob sich dieser Verdacht bestätigt.
Sehen wir uns zunächst die Challenge an und verschaffen wir uns einen ersten Eindruck. Wir sehen Folgendes:
Ein Login-Formular
Ein Registrierungsformular
Zwei verspielte, rotierende Bilder von einem Mond und einer Gans
Ein cooler Sternenhimmel als Hintergrund
Alles, was Benutzereingaben entgegennimmt, ist ein sinnvoller Ausgangspunkt für die Analyse. Deshalb konzentrieren wir uns zuerst auf die Login- und Registrierungsformulare.

Wenn wir in den beiden Formularen verschiedene Kombinationen aus Benutzernamen und Passwörtern ausprobieren, erhalten wir zwei unterschiedliche Fehlermeldungen:
Wenn sowohl Benutzername als auch Passwort ausgefüllt sind, erhalten wir:
{ "☾ 𓅬": "invalid moongoose!" }Wenn eines oder beide Felder leer sind, erhalten wir:
{"☾ 𓅬":"invalid moongoose: geese have credentials"}
Seltsamerweise verhalten sich das Login- und das Registrierungsformular gleich. Öffnen wir die Netzwerkansicht in den Entwickler-Tools unseres Browsers, um besser zu verstehen, was vor sich geht.

Für diese Screenshots verwende ich Firefox, aber dieselben Prinzipien gelten für alle gängigen Browser.
Anhand des Netzwerkverkehrs sehen wir, dass beide Formulare dieselbe POST-Anfrage an den Endpunkt api/auth senden. Es spielt also keine Rolle, welches Formular wir verwenden. Einer der Antwort-Header dieser Anfragen fällt besonders auf:
Falls Ihnen der Name nicht geläufig ist: Express ist ein sehr verbreitetes Node.js-Webserver-Framework. Für uns als Angreifer ist das eine potenziell wertvolle Information. Wir kennen jetzt die Sprache, die Laufzeitumgebung und das Framework dieses Servers – oder vermuten es zumindest sehr stark. Damit ist zwar nicht bestätigt, dass der Server Mongoose und MongoDB verwendet, aber es stützt diese Vermutung.
Wenn wir dieser Hypothese folgen und annehmen, dass MongoDB im Login-Prozess verwendet wird, könnten wir eine MongoDB-Injection über den Endpunkt api/auth ausprobieren. Diese Query-Injection habe ich bei book.hacktricks.xyz gefunden, einer äußerst hilfreichen Quelle:
In der MongoDB-Abfragesyntax ist $ne ein Operator, der passende Ergebnisse liefert, wenn ein Feld nicht dem angegebenen Wert entspricht. In SQL würde das so aussehen:
Wenn wir annehmen, dass dieser Endpunkt MongoDB nach einem Benutzer mit username = X und password = Y abfragt, würde diese Injection bedeuten: „Gib mir den Benutzer, bei dem username nicht null und password nicht null ist.“ Theoretisch würde diese Abfrage auf jeden gültigen Benutzer zutreffen. Öffnen wir das Terminal und probieren es mit cURL aus:
Das scheint nicht so einfach zu sein! Wechseln wir zurück zum Browser und suchen bei weiterhin geöffneter Netzwerkansicht nach anderen API-Endpunkten, die wir ausnutzen können. Für diesen Screenshot habe ich die Seite neu geladen und den Netzwerkverkehr dann nach Anfragen gefiltert, deren Pfad api enthält.

Wir sehen, dass das Gänsebild über einen Aufruf von /api/image/goose.png geladen wird. Wie der Endpunkt /api/auth enthält auch diese Antwort den Header X-Powered-By: Express. Vermutlich verwendet derselbe Server auch zum Bereitstellen statischer Dateien. Sehen wir, was passiert, wenn wir eine andere Datei anfordern, zum Beispiel package.json:
Diese Antwort ist ein deutliches Warnsignal. Es sieht so aus, als würde:
Der Server versucht, die von uns angegebene Datei von der Festplatte zu öffnen.
der Anwendungscode wahrscheinlich unter
/appliegen
Wir wissen noch nicht, ob dieser Server eine Bibliothek oder eigenen Code zum Bereitstellen statischer Dateien verwendet. Wir können jedoch eine gängige Schwachstelle in Static-File-Servern ausprobieren: Directory Traversal. Dabei verwenden wir ../, um eine Datei aus dem übergeordneten Verzeichnis des Speicherorts der Bilder anzufordern. Beachten Sie, dass wir den Schrägstrich URL-kodieren müssen (%2f), da unsere Anfrage sonst als /api/package.json interpretiert würde:
Damit haben wir unseren ursprünglichen Verdacht endlich bestätigt: Der Server verwendet die MongoDB-Bibliothek Mongoose. Außerdem sehen wir, dass sich der Hauptservercode in server.js befindet. Versuchen wir als Nächstes, diese Datei abzurufen:
Geschafft! Sehen wir uns diesen Server genauer an, um herauszufinden, wie wir der Flag näherkommen.
Analyse des Authentifizierungssystems
Diese Abschnitte aus server.js bilden das Authentifizierungssystem:
Hier gibt es viel zu entdecken. Ich fasse meine wichtigsten Erkenntnisse zusammen:
MongoDB wird im Authentifizierungssystem nicht verwendet.
Dieser Code ignoriert den
usernameund vergleicht nur einen Hash despasswordmit einer fest codierten Variable namensADMIN_HASH.Dieses Passwort wird mit bcrypt gehasht, was einen Brute-Force-Angriff sehr rechenintensiv macht.
Nach erfolgreicher Authentifizierung generiert der Server ein zufälliges Token und fügt es dem globalen Objekt
SESSIONShinzu. Das Token wird dem Client alsAuthorization-Header zurückgegeben. Der Client kann diesen Header dann an Endpunkte senden, die die MiddlewarerequireAuthenticationverwenden.
Analyse des Flag-Endpunkts
Um die Flag abzurufen, müssen wir:
die Authentifizierungsprüfung bestehen
im Request-Body den richtigen Wert für
flagangeben
Wenn wir die Datei models/user.model.js mit unserem Directory-Traversal-Exploit anfordern, sehen wir, dass Flag ein Mongoose-Modell ist und dieser Aufruf Flag.find() MongoDB nach Einträgen mit name = <flag> abfragt.
Der Handler von /api/flags bereinigt oder validiert den Request-Body nicht. Es sieht also so aus, als könnten wir die zuvor ausprobierte MongoDB-Injection anstelle des Flagnamens verwenden. Zuerst müssen wir jedoch die Middleware requireAuthentication() umgehen.
Alles zusammenfügen
Wie bereits erwähnt, ist es unwahrscheinlich, dass wir ADMIN_HASH in vertretbarer Zeit per Brute Force knacken können. Können wir den Server dazu bringen, uns für bereits authentifiziert zu halten?
Sehen wir uns die Funktion an, die das Sitzungstoken validiert:
Die Schwachstelle dieser Prüfung besteht darin, dass sie davon ausgeht, das Objekt SESSIONS enthalte nur Sitzungstoken. Durch die prototypische Vererbung von JavaScript wird das Objekt SESSIONS jedoch mit einigen verborgenen, nicht aufzählbaren Eigenschaften initialisiert. Wir können diese Eigenschaften auflisten, indem wir die folgende Anweisung entweder in einer Node.js-REPL oder im Console-Tab der Entwickler-Tools unseres Browsers ausführen:
Wenn unsere Annahme stimmt, sollten wir eine beliebige dieser Eigenschaften als Sitzungstoken verwenden und so die Funktion checkToken() bestehen können. Zusammen mit unserer MongoDB-Injection sieht die endgültige Anfrage dann so aus:
Die Antwort auf diese Anfrage enthält den gesamten Inhalt der Sammlung flag – einschließlich der tatsächlichen Flag!
Moongoose gefasst!
Diese Challenge hat verschiedene Schwachstellen demonstriert. Wir haben mit Directory Traversal den Servercode ausgelesen, die prototypische Vererbung von JavasScript genutzt, um das Authentifizierungssystem zu umgehen, und schließlich eine MongoDB-Query-Injection eingesetzt, um beim Endpunkt /api/flags nicht raten zu müssen. Diese Anwendung wurde absichtlich verwundbar gemacht, aber solche Schwachstellen kommen auch in der realen Welt vor. Wenn Sie mehr über verschiedene Arten von Schwachstellen, ihre Ausnutzung und den Schutz davor erfahren möchten, ist Snyk Learn eine hervorragende praxisorientierte Ressource.
Möchten Sie erfahren, wie wir alle anderen Flags gefunden haben? Auf unserer Seite Fetch the Flag-Lösungen erfahren Sie, wie wir dabei vorgegangen sind.
Starten Sie mit Capture the Flag
Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.
