Während Penetrationstests werden häufig Schwachstellen entdeckt, die sich nicht unmittelbar mit einem einfachen Klick ausnutzen lassen. Oft erfordern diese Schwachstellen spezifische Randbedingungen für eine erfolgreiche Ausnutzung, die von gängigen Tools nicht automatisch unterstützt werden.
Die folgenden Abschnitte beschreiben die Ausnutzung einer SQL-Injection mit der Rahmenbedingung einer Anti-Cross-Site-Request-Forgery (CSRF)-Maßnahme, welche wir in einem Pentest bei einem unserer Kunden angetroffen und umgangen haben. Der Grund, weshalb wir die im Laufe des Artikels beschriebene Methode verwendeten, bezieht sich darauf, dass wir einen Bug in SQLMap identifizierten, welcher uns damals zu einem anderen Vorgehen zwang. Der Bug wurde inzwischen behoben.
Da zu der verwendeten Methode bislang nur wenige Informationen online verfügbar sind und wir die Schwachstelle anschaulich demonstrieren möchten, haben wir uns entschieden, sie in eine kleine CTF-Challenge zu verwandeln, um die Nachvollziehbarkeit zu verbessern.
Hierfür haben wir das folgende Github-Repository auf die Gegebenheiten der Schwachstelle abgeändert, sodass diese den real angetroffenen Umständen gleicht:
Wer sich selbst an der CTF Challenge versuchen möchte, kann diese von unserem Github Repository herunterladen. Der Docker Build-Befehl kann der ReadMe.md Datei entnommen werden:
Zunächst erfolgt eine einführende Begriffserklärung:
SQL-Injection
SQL-Injection (SQLi) Schwachstellen existieren, wenn Nutzereingaben auf unsichere Art und Weise in SQL-Abfragen einbezogen werden. Ein Angreifer, der diese Schwachstelle ausnutzt, kann SQL-Abfragen so manipulieren, dass er beliebige Befehle an die von der Applikation verwendete Datenbank senden kann. Somit können sämtliche Anwendungsdaten, wie auch Nutzer- und Zugangsdaten, ausgelesen werden. Um SQL-Injections auszunutzen, kann beispielsweise das Open-Source Tool „SQLMap“ verwendet werden.
Cross Site Request Forgery
CSRF (Cross-Site Request Forgery) ist eine Sicherheitslücke, bei der ein Angreifer einen Benutzer dazu bringt, ungewollte Aktionen auf einer Webseite auszuführen, bei der er bereits authentifiziert ist. Dies geschieht meist durch manipulierte Links oder unsichtbare Formulare, die im Hintergrund geladen werden. Wenn ein Benutzer beispielsweise bei einem verwundbaren Onlineshop angemeldet ist und parallel eine bösartige Webseite besucht, kann diese unbemerkt eine Anfrage, mit den gültigen Sitzungscookies des Benutzers, an den Onlineshop senden, der eine Bestellung ausführt. Da der Server diese Anfrage durch Angabe des Cookies als authentisch erkennt, wird sie ausgeführt, obwohl der Benutzer sie nie selbst initiiert hat.

Um sich davor zu schützen, setzen viele Webseiten auf CSRF-Tokens. Ein CSRF-Token ist ein zufällig generierter, eindeutiger Wert, der mit jeder Anfrage an den Server gesendet werden muss. Beim Laden einer geschützten Seite oder eines Formulars generiert der Server dieses Token und bindet es entweder direkt in die HTML-Struktur ein oder übergibt es in einem HTTP-Header.
Wenn der Benutzer eine Anfrage sendet, muss das Token mitgeschickt werden. Der Server überprüft dann, ob das übermittelte Token mit dem zuvor gespeicherten übereinstimmt. Falls nicht, wird die Anfrage blockiert. Da ein Angreifer eine bösartige Seite im Vorhinein präparieren muss und das gültige CSRF-Token in der Regel nicht kennt, kann er keine gültige Anfrage im Namen des Benutzers stellen.
Dieses Verfahren stellt sicher, dass nur legitime Anfragen ausgeführt werden und schützt somit vor CSRF-Angriffen.
Das Szenario
Während unseres Tests fanden wir folgende Rahmenbedingungen vor.
- Eine verwundbare Webanwendungsfunktion, um SQL-Abfragen zu testen, allerdings ohne das Ergebnis der Abfrage auszugeben. Ist die Abfrage jedoch syntaktisch fehlerhaft, wurde eine Fehlermeldung ausgegeben.

- Die verwundbare Anwendungsfunktion befindet sich auf einer Unterseite der Anwendung und ist lediglich mit einem gültigen CRSF-Token ansprechbar. Wird dieser nicht übergeben oder ist dieser nicht gültig, erfolgt eine Weiterleitung auf eine Fehlerseite.

- Die Anwendungsfunktion ist lediglich über POST-Anfragen ansprechbar. Bei GET-Anfragen erfolgt ebenfalls eine Weiterleitung auf eine Fehlerseite.
All dies wurde in der CTF-Challenge berücksichtigt.
Das Problem
Als Angreifer behindern uns CSRF-Token automatisierte Tools zu verwenden, da diese meist nicht oder nur bedingt über die Funktionalität verfügen, CSRF-Token selbst auszulesen und/oder zu verwenden.
HTTP-Anfragen ohne einen gültigen Token werden nicht akzeptiert, jedoch können wir gültige Token selbst auslesen, wenn uns mindestens ein CSRF-Token bekannt ist, den wir der Anwendung übergeben können. Anders als bei Sitzungsbasierten CSRF token, welche nur einmal initial während des Anmeldeprozesses gesetzt werden, wird unser Token für jede POST form dynamisch neu generiert und muss entsprechend für jede Anfrage neu ausgelesen werden.
Da wir keine CSRF-Schwachstelle ausnutzen möchten und somit nicht wie ein regulärer Angreifer darauf angewiesen sind, ein Opfer auf eine fragwürdige Webseite zu locken, sondern direkt mit der Anwendung interagieren können, können wir den initialen CSRF-Token einfach selbst auslesen.

Den ausgelesenen Token können wir anschließend für unseren Angriff verwenden. Da der Token nur einmalig verwendbar ist, muss vor jedem HTTP-Request ein neuer Token ausgelesen und gesetzt werden. Im nachfolgenden Bild wird unser Angriffsprozess vereinfacht dargestellt.

Das Tool SQLMap ist leider nur bedingt in der Lage, CSRF-Token automatisch auszulesen und diese in weiteren Requests zu verwenden. Standardmäßig versendet SQLMap eine Anfrage, um den CSRF-Token aus der Serverantwort auszulesen und diesen Token im nachfolgenden, eigentlichen Angriffs-Request zu verwenden. Jedoch ist diese Funktionalität beschränkt.
So fanden wir folgende Limitierung des Tools vor:
- SQLMap versendet zum Auslesen von gültigen CSRF Token lediglich GET-Anfragen ohne Angabe weiterer Request-Parameter. Die verwundbare Funktion in unserer CTF-Challenge akzeptiert jedoch ausschließlich POST-Anfragen mit einem gültigen CSRF-Token. Somit konnte SQLMap nie einen gültigen Request stellen, um anschließend einen CSRF-Token für nachfolgende Angriffe auszulesen und zu verwenden.

SQLMap bietet zwar verschiedene Parameter, um mit CSRF-Mechanismen umzugehen, jedoch hatte die Angabe der wesentlich benötigten Parameter keinen Effekt. Konkret wurden die Parameter „–csrf-method“ und „–csrf-data“ einfach ignoriert. Hierzu haben wir bereits ein Github issue im SQLMap-Projekt eröffnet.
- Die Verwendung des Parameters „–csrf-method“ mit Wert „POST“ wurde ignoriert und es erfolgte dennoch eine GET-Anfrage zum Auslesen des CSRF-Tokens. Eine POST-Anfrage konnte dennoch mittels des Parameters “–method=POST“ bewerkstelligt werden.
- Daten, die im Parameter „–csrf-data“ übergeben wurden, wurden nicht in die CSRF-Token Anfrage übertragen. Normalerweise würde in diesem Parameter initial ein gültiger CSRF-Token übergeben werden, um anschließend weitere CSRF-Token aus der Serverantwort auslesen zu können.

So wurde effektiv nie ein POST-Body im Request übertragen und eine Weiterleitung auf die Fehlerseite erfolgte. Dies führte dazu, dass SQLMap nie einen CSRF-Token für den nachfolgenden Request auslesen konnte und ein Angriff stets fehlschlug.

Die Lösung: Request Preprocessing
SQLMap erlaubt die Bearbeitung einer jeden HTTP-Anfrage, bevor diese versendet wird. Hierfür stellt SQLMap die Möglichkeit bereit, mit dem Python Request-Objekt zu interagieren. Die Dokumentation des Request-Objekts lässt sich hier einsehen: https://docs.python.org/3/library/urllib.request.html#request-objects
Somit wird ermöglicht, gezielt einzelne Requests zu manipulieren. Konkret funktioniert dies durch Verwendung des Parameters “–preprocess <dateiname>“. Wird dieser verwendet, lädt SQLMap die angegebene Python-Datei und übergibt dieser jeden Request als Objekt vor Versand.
Die SQLMap-Dokumentation des Parameters:
Zum Verwenden des Preprocessings wird zusätzlich eine weitere Datei benötigt. Diese enthält jedoch keine weitere anzupassende Logik.

Unsere Lösung wich nicht viel von dem oben dargestellten Preprocessing-Beispiel der SQLMap-Dokumentation ab. Es wurde lediglich eine weitere Zeile eingefügt, die sicherstellt, dass nur Requests mit der Endung „index.php“ manipuliert werden. Von diesem Endpunkt lesen wir später den neuen CSRF-Token aus.

Mittels des Preprocessings war es somit möglich, der Anfrage zum Auslesen des CSRF-Tokens einen POST-Body samt einem initialen gültigen CSRF-Token hinzuzufügen. Dies hatte gleichzeitig zur Folge, dass der Request automatisch zu einem POST Request umgewandelt wurde, was den Parameter “–method=POST“ ersetzte.

Dies löste unsere Randbedingungen:
- Durch Übergabe eines gültigen CSRF-Tokens gibt die Anwendung nun einen neuen gültigen CSRF-Token zurück, welchen wir für nachfolgende Requests verwenden können. SQLMap liest diesen nun selbständig aus und verwendet ihn weiter.

- SQLMap verwendet nun POST-Anfragen zum Auslesen der CSRF-Token. Somit erfolgt keine automatische Weiterleitung mehr auf eine Fehlerseite.
Durch den folgenden Befehl waren wir in der Lage, uns an die Gegebenheiten der Schwachstelle anzupassen und diese automatisiert auszunutzen:
sqlmap -r req.txt -p action --level=5 --risk=3 --csrf-token=csrf --csrf-url=http://localhost/index.php –batch --banner --dbs --preprocess ./preprocess.py
Schließlich gelang es uns Zugriff auf die Datenbank der Anwendung zu erhalten.

Allgemeine Empfehlungen:
- Prepared Statements und Parameterized Queries nutzen, um SQL-Befehle von Benutzereingaben zu trennen.
- Stored Procedures verwenden, sofern sie korrekt implementiert sind und keine dynamischen SQL-Statements enthalten.
- Benutzereingaben validieren und bereinigen, um sicherzustellen, dass sie nur erwartete Werte enthalten.
- Least Privilege Principle (Prinzip der geringsten Rechte) anwenden, indem die Datenbankbenutzer nur minimale Berechtigungen erhalten.
- Web Application Firewalls (WAFs) einsetzen, um verdächtigen Datenverkehr zu filtern.
- Fehlermeldungen minimieren, um keine sensiblen Informationen über die Datenbankstruktur preiszugeben.
- Regelmäßige Sicherheitsupdates und Patches für Datenbanken und Webanwendungen einspielen.
- Code-Reviews und Penetrationstests durchführen, um potenzielle Schwachstellen frühzeitig zu erkennen.
- Security-Frameworks oder ORM (Object-Relational Mapping) Systeme verwenden, die SQL-Injections erschweren.
- Logging und Monitoring aktivieren, um Angriffsversuche zu erkennen und zu analysieren.
Nachtrag – Github Issue
Das von uns erstellte Github issue wurde bereits durch die SQLMap Entwickler geschlossen. Wie es sich herausstellte, existierte tatsächlich ein kleiner Bug in den CSRF-Parameter Einstellungen, sodass die Werte der Parameter „–csrf-method“ und „–csrf-data“ nicht in die anschließende Anfrage übertragen wurden. Der Bugfix wurde noch nicht in das aktuelle Kali Package integriert, wurde jedoch in der SQLMap Version „1.9.2.7“ auf Github veröffentlicht.

Nun ist eine Ausnutzung ebenfalls mit dem folgenden Befehl möglich:
sqlmap -u http://localhost/inject.php --data="action=1&csrf=23995caa2f118261c9877698d79d3620" -p action --level=5 --risk=3 --csrf-method=POST --csrf-data="init=4a88b0c79048ea94fd29eee84772e0aa" --csrf-token=csrf --csrf-url=http://localhost/index.php –batch --banner --dbs