Blind XPath-Injection – Vorgehensweise bei unbekannten Datensätzen

Blind XPath-Injection

Vorgehensweise bei unbekannten Datensätzen

Andrea Hauser
von Andrea Hauser
am 02. August 2018
Lesezeit: 12 Minuten

Keypoints

Nutzen Sie die erweiterten Angriffsmöglichkeiten von XPath-Injection

  • XPath steht für XML Path Language
  • XPath wird für die Abfrage von XML-Dokumenten verwendet
  • Eine XPath-Injection kann man sich ähnlich wie eine SQL-Injection vorstellen
  • Mit einer XPath-Injection ist das Auslesen ganzer XML-Dokumente möglich

Bei XML Path Language, kurz XPath, handelt es sich um eine Abfragesprache für XML-Dokumente. Diese Abfragesprache kann genutzt werden, um durch Elemente und Attribute von XML-Dokumenten zu navigieren. Dabei wird ein XML-Dokument als ein Baum aus Knoten dargestellt.

Grundlagen

Um die wichtigsten Begriffe zu klären, ist nachfolgend ein einfaches XML-Dokument dargestellt, welches als Basis für den weiteren Verlauf des Artikels gilt.

<?xml version="1.0" encoding="UTF-8"?>
<accounts> <!-- root node -->
    <user id="1"> <!-- node with attribute -->
        <username> <!-- child of user node -->
            1337h4x0r <!-- node value -->
        </username>
        <firstname>Leet</firstname>
        <lastname>Hacker</lastname>
        <email>h@ck.er</email>
        <accounttype>normal</accounttype>
        <password>123456</password>
    </user>
    <user id="2">
        <username>johnnynormal</username>
        <firstname>John</firstname>
        <lastname>Doe</lastname>
        <email>john@company.com</email>
        <accounttype>administrator</accounttype>
        <password>UiobxmA5UcDVF9m5VAq</password>
    </user>
</accounts>

Das oben dargestellte XML-Dokument entspricht dabei ungefähr dem folgenden Baum:

Darstellung des XML-Dokuments als Baum

Die Knoten eines XML Dokuments können auf verschiedene Arten mit XPath selektiert werden. Die wichtigsten Selektionsmöglichkeiten sind dabei:

XPath Abfrage Resultat der XPath Abfrage
/accounts Es wird der root-Knoten accounts selektiert.
//user Es werden alle Knoten mit dem Namen user selektiert.
/accounts/user Es werden alle user-Knoten selektiert, die child-Knoten des accounts-Knoten sind.
/accounts/user[username=‘1337h4×0r’] Es wird der user Knoten zurückgegeben welcher den username 1337h4×0r beinhaltet. Ein absoluter Pfad beginnt mit /.
//user[email=‘john@company.com’] Es wird der user Knoten zurückgegeben welcher die email john@company.com beinhaltet. Ein relativer Pfad beginnt mit //. Damit werden sämtliche Knoten selektiert, welche die gestellte(n) Bedingung(en) erfüllen, egal wo im Baum sich die Knoten befinden.
/accounts/child::node() Damit werden sämtliche child Knoten des Knoten accounts selektiert.
//user[position()=2] Damit wird der user Knoten an der Position ausgewählt. Achtung da der Index bei 1 beginnt, wird damit der Knoten des Benutzers johnnynormal selektiert.

Blind XPath-Injection

Nachdem die wichtigsten Grundlagen der XML Path Language abgedeckt sind, möchte ich nun Schritt für Schritt darauf eingehen, wie bei einer Blind XPath-Injection vorgegangen werden kann. Als Beispiel orientieren wir uns dabei an einer Login-Maske. Ziel ist es, diese Login-Maske zu umgehen, um schlussendlich die Passwörter aller Benutzer auslesen zu können.

Schwachstelle finden

Um ganz grundsätzlich das Vorkommen einer XPath-Injection zu bestimmen, kann als erstes im Feld des Benutzernamens ein Hochkomma ' oder ein Anführungszeichen " eingegeben werden. Im besten Fall wird bei einem dieser Zeichen eine Fehlermeldung zurückgegeben, welche ungefähr wie folgt aussieht:

Warning: SimpleXMLElement::xpath(): Invalid predicate in /webserver/index.php on line 56

Warning: SimpleXMLElement::xpath(): xmlXPathEval: evaluation failed in /webserver/index.php on line 56

Mit der Anzeige einer solchen oder ähnlichen Fehlermeldung wird eindeutig bestätigt, dass eine XPath-Injection in diesem Bereich der richtige Ansatz ist.

Injection vorbereiten

Grundsätzlich handelt es sich bei einer XPath Injection um ein ähnliches Prinzip wie bei einer SQL-Injection. Es geht darum, eine bestehende XPath Abfrage so zu verändern, dass sie den durch den Angreifer gewünschten Effekt durchführt.

Im Unterschied zu einer SQL-Injection ist es bei der XPath-Injection aus Sicht des Angreifers glücklicherweise so, dass keine Zugriffskontrollen innerhalb des XML-Dokuments umgesetzt werden können. Dementsprechend kann das ganze XML-Dokument ausgelesen werden, sollte eine XPath-Injection vorhanden sein.

Zudem ist XPath eine standardisierte Abfragesprache was bedeutet, dass man sich nicht mit unterschiedlichen XPath Dialekten herumschlagen muss. Das einzige was es zu beachten gibt ist, dass es unterschiedliche XPath Versionen gibt. Momentan ist XPath 3.1 die aktuellste Version. Um herauszufinden welche Version von XPath genutzt wird, kann eine Funktion aus der Version 2.0 oder 3.1 verwendet werden, welche in der vorhergehenden XPath Version noch nicht vorhanden war. Wenn eine Fehlermeldung angezeigt wird, die aussagt, dass es diese Funktion nicht gibt, kann davon ausgegangen werden, dass es sich um eine ältere XPath Version handelt.

In unserem Beispiel verwende ich für die Überprüfung der XPath Version die Funktion lower-case("ABC") welche erst ab der Version 2.0 verfügbar ist. Da in unserem Beispiel die folgende Fehlermeldung ausgegeben wird, kann darauf geschlossen werden, dass XPath 1.0 verwendet wird.

"Warning : SimpleXMLElement::xpath() : xmlXPathCompOpEval : function lower-case not found in /webserver/index.php on line 56

Warning : SimpleXMLElement::xpath() : Unregistered function in /webserver/index.php on line 56

Warning : SimpleXMLElement::xpath() : Stack usage error in /webserver/index.php on line 56

Warning : SimpleXMLElement::xpath() : xmlXPathEval : 1 object left on the stack in /webserver/index.php on line 56"

Ähnlich wie der Ausdruck ' OR '1'='1 bei SQL-Injection, existiert bei der XPath-Injection ' or 1=1 or ''='. Damit kann bei einer Abfrage in der Form 'bool_value_1 and bool_value_2' wie zum Beispiel username='...' and password='...' die Auswertung der Bedingung bool_value_2 bzw. password='...' umgangen werden.

In unserem Beispiel gibt es die Input Felder Benutzername und Passwort. Wir geben den Wert ' or 1=1 or ''=' als Benutzername und den Wert bla als Passwort ein. Wenn die folgende Serverlogik angenommen wird:

simplexml_load_file("useraccounts.xml")->xpath("/accounts/user[username=' " . $_POST["username"] . " ' and password=' " . $_POST["password"] . " ' ]");

Entsteht daraus die folgende XPath Abfrage:

xpath("/accounts/user[username='' or 1=1 or ''='' and password='bla' ]")

Aufgrund der zwei nacheinander folgenden or-Statements wird die Überprüfung der and-Aussage umgangen. Das Ergebnis der Eingabe ist, dass wir als erster Benutzer des XML-Dokuments angemeldet werden. Wir werden als erster Benutzer angemeldet, da mit der veränderten XPath-Query sämtliche Benutzer zurückgeliefert werden und aus diesem Resultat dann jeweils der erste Benutzer verwendet wird.

Angriff umsetzen

Nun sind wir bei einem Zustand angelangt, der es uns erlaubt eine Boolean Based Blind-Injection durchzuführen. Wir können die erste or-Aussage verändern und wenn wir nach dieser Veränderung immer noch als erster Benutzer des XML-Dokuments angemeldet werden, ist diese or-Aussage korrekt.

Um dieses Beispiel sinnvoll weiterführen zu können, gehen wir davon aus, dass wir das Passwort des ersten Benutzers des XML-Dokuments in Erfahrung bringen können. Dies kann entweder dadurch geschehen, dass wir das Passwort ändern können, sobald wir angemeldet sind oder weil wir es aus dem angemeldeten Profil im Benutzerinterface auslesen konnten. Für den weiteren Verlauf dieses Beispiels gehen wir also davon aus, dass wir das Passwort als 123456 kennen.

Da wir davon ausgehen, dass das XML-Dokument eine für uns unbekannte Struktur hat, geht es nun zuerst darum herauszufinden, in welchem Knoten das Passwort abgespeichert ist. Das Kennen dieser Position innerhalb eines Benutzer-Elements ist elementar, da wir erst mit diesem Wissen ein unbekanntes Passwort bruteforcen können. Dabei hilft uns, dass wir das Passwort des Benutzers an der ersten Stelle kennen.

Den Aufbau der ersten or-Abfrage, welche wir für die blind XPath-Injection benötigen, möchte ich nun Schritt für Schritt angehen:

  1. //user[position()=1]: Damit wird der Benutzer an der ersten Stelle des XML-Dokuments ausgelesen. Hier sei darauf hingewiesen, dass der Knotenname user eine begründete Vermutung darstellt. Wenn damit keine Treffer gemacht werden, sollten weitere ähnliche Begriffe für Benutzer verwendet werden.
  2. (//user[position()=1]/child::node()[position()=1]): Diese Abfrage bezweckt das Auslesen des ersten child-Knoten des ersten Benutzers. Angewendet auf unser Beispiel-XML-Dokument wäre dies der Knoten username.
  3. substring((//user[position()=1]/child::node()[position()=1]),1): Substring wird definiert als string substring(string_to_work_with, start_of_substring_extraction, [optional_length_of_extracted_string]). Angewandt auf unser XML Beispiel bedeutet dies, dass der String aus dem Knoten username ausgelesen wird. Da keine Länge angegeben wird, wird der gesamte String 1337h4x0r ausgelesen. Achtung, bei XPath beginnt der Index bei 1 und nicht ansonsten in der Informatik üblich bei 0.
  4. substring((//user[position()=1]/child::node()[position()=1]),1)="123456": Nachdem nun der effektive Wert des ersten child-Knotens des ersten Benutzers ausgelesen und als 1337h4x0r bestimmt wurde, wird dieser Wert mit 123456 verglichen. In diesem Fall führt der Vergleich zu einem false Wert. Wenn diese Abfrage so als unser erster or-Wert in die Abfrage ' or 1=1 or ''=' eingefügt wird, führt die Auswertung dieser Query zu keinem erfolgreichen Login. Daraus kann geschlossen werden, dass es sich bei der ersten Position des Benutzers nicht um das Passwort handelt. Um in unserem Beispiel XML eine Anmeldung zu erreichen, ist die Query ' or substring((//user[position()=1]/child::node()[position()=6]),1)="123456" or ''=' notwendig. Damit konnte die Position des Passworts im user-Knoten als Position 6 bestimmt werden.

Da wir nun die Position des Passworts kennen, können wir unsere Abfrage leicht anpassen auf: ' or substring((//user[position()=2]/child::node()[position()=6]),1,1)="a" or ''='. Damit fragen wir für den zweiten Benutzer das erste Zeichen des Passworts ab und vergleichen es mit dem Buchstaben 'a'. Dies wird bewerkstelligt, indem bei der substring-Abfrage angegeben wird, wie viele Zeichen ab der Startposition zurückgegeben werden sollen. Da wir mit einem Vergleich zu 'a' kein erfolgreiches Login erhalten, kann darauf geschlossen werden, dass das Passwort des zweiten Benutzers nicht mit a beginnt. Erst bei ' or substring((//user[position()=2]/child::node()[position()=6]),1,1)="U" or ''=' werden wir wieder als erster Benutzer des XML-Dokuments erfolgreich angemeldet. Dementsprechend ist das erste Zeichen des Passworts der Buchstabe 'U'.

Das einzige was nun zu tun bleibt, ist die Position des ausgewählten Substrings zu inkrementieren und das Vergleichen der Zeichen erneut durchzuführen. Mit ' or substring((//user[position()=2]/child::node()[position()=6]),2,1)="i" or ''=' erhalten wir unseren zweiten Treffer.

Das Automatisieren dieser Art der Vergleiche lässt sich relativ einfach umsetzen und wird dem interessierten Leser selbst überlassen.

Schutzmassnahmen

Um eine XPath-Injection zu verhindern, sollten soweit möglich vorkompilierte XPath-Abfragen genutzt werden. Wenn die gewählte Bibliothek dies nicht unterstützt, sollte eine parametrisierte XPath-Schnittstelle verwendet werden. Falls diese beiden Möglichkeiten nicht vorhanden sind und der Input eines Benutzers in eine dynamische XPath-Abfrage eingebunden werden muss, muss der Input des Benutzers escaped werden. Beim Escapen von Werten sollte so weit wie möglich Whitelisting-Ansätze verwendet werden.

Schlusswort

Abschliessend möchte ich darauf hinweisen, dass sich dieser Artikel lediglich auf die Auswertung von XPath 1.0 konzentriert hat. Da XPath 1.0 im Vergleich zu den Versionen 2.0 und 3.1 nur wenige Funktionen zur Verfügung hat, ist das Auslesen von XML-Dokumenten mit vielen Abfragen verbunden. Mit den Erweiterungen von XPath 2.0 und 3.1 sind viele Funktionen hinzugefügt worden, welche das Auslesen von XML-Dokumenten erleichtern und die Tragweite einer XPath-Injection vergrössern. Beispielsweise gibt es seit XPath 2.0 die Funktion doc(path_to_xml_document), welche es erlaubt andere XML-Dokumente zu referenzieren und somit auszulesen. Damit können beispielsweise config-Files mit bekannten Speicherorten ausgelesen werden.

Über die Autorin

Andrea Hauser

Andrea Hauser hat ihren Bachelor of Science FHO in Informatik an der Hochschule für Technik Rapperswil abgeschlossen. Sie setzt sich im offensiven Bereich in erster Linie mit Web Application Security Testing und der Umsetzung von Social Engineering Kampagnen auseinander. Zudem ist sie in der Forschung zum Thema Deepfakes tätig. (ORCID 0000-0002-5161-8658)

Sie wollen Ihre Webapplikaion prüfen?

Unsere Spezialisten kontaktieren Sie gern!

×
GraphQL

GraphQL

Andrea Hauser

Server-Side-Request-Forgery

Server-Side-Request-Forgery

Andrea Hauser

Policy Analyzer

Policy Analyzer

Andrea Hauser

Sie wollen mehr?

Weitere Artikel im Archiv

Sie wollen Ihre Webapplikaion prüfen?

Unsere Spezialisten kontaktieren Sie gern!

Sie wollen mehr?

Weitere Artikel im Archiv