XPath 1.0 has no parameterized queries - user input concatenated into a tree-parse expression carries the same bypass potential as SQL injection, with distinct error signals per library and a boolean differential as the confirming artifact.
XPath injection exploits the same root cause as SQL injection - user input concatenated into a query string - but targets a tree parser rather than a relational database. The result looks different: no error messages citing table names, no information_schema to mine. What stays the same is the oracle: a boolean predicate that flips a conditional from false to true proves the parser evaluated your input, not the application.
XPath 1.0 - still the dominant standard in SOAP services, XML-backed user stores and configuration-driven logic - takes a raw string. The library evaluates whatever expression it receives. When user input is concatenated into that string without escaping, the query changes meaning. There is no equivalent of a prepared statement at the XPath level: the only way to prevent injection is to validate and escape input before it reaches the expression builder, or to restructure the query so user-supplied values appear only inside fixed, quoted literals that cannot change the predicate.
or '1'='1 is always true, so every user node matches - the same logic as SQL auth bypass but evaluated by the XML engine with no SQL keyword in sight.The most common vectors are login forms backed by an XML user store, SOAP services that embed user input in XPath expressions for data retrieval, and application configuration files read with embedded query parameters. Any place a string value flows from user input into an XPath expression without escaping is a candidate surface.
Confirming XPath injection follows the same differential discipline as every other oracle probe: send a payload that changes result cardinality without touching or disclosing any real data, then compare the two responses. For an authentication context the confirming artifact is a 200 where a baseline wrong credential returns 401 - with the injected predicate documented and no real user credential required:
# baseline: legitimate wrong-credential attempt POST /api/login Content-Type: application/x-www-form-urlencoded username=probe&password=probe HTTP/1.1 401 Unauthorized # boolean injection: predicate appended in string-literal context POST /api/login Content-Type: application/x-www-form-urlencoded username=probe%27%5D+or+%271%27%3D%271&password=x # decoded: probe'] or '1'='1 HTTP/1.1 200 OK Set-Cookie: session=... # predicate evaluated - boundary broke
If there is no authentication flow to probe, a blind boolean oracle works on any endpoint that returns a discernibly different response for a true versus false predicate. Injecting a condition that evaluates against a known structural fact - such as whether a node count exceeds a threshold - and comparing response length or status code produces the same differential evidence. Timing is less reliable because XPath evaluation offers no sleep equivalent; behavioral differences on result cardinality are the primary signal.
A malformed XPath expression produces different error output depending on the library processing it. Fingerprinting the library before probing avoids false negatives from payloads calibrated to a different engine. The key observable is the error structure the application surfaces when the expression is syntactically invalid - even a sanitized application will often expose the library class name or message prefix in a 500 response or a SOAP fault envelope.
| Library | Typical error signal | Common host |
|---|---|---|
| libxml2 (PHP / Python lxml) | XPath error: invalid expression - prefix in error body | PHP DOMXPath, SimpleXML; Python lxml etree |
| Java javax.xml.xpath / Saxon | javax.xml.xpath.XPathExpressionException in stack trace or SOAP fault | SOAP services, Spring XML config, JAXB pipelines |
| .NET System.Xml | XPathException: Expression must evaluate to a node-set | ASP.NET SOAP endpoints, WCF, XmlDocument.SelectNodes |
| Ruby Nokogiri | Nokogiri::XML::XPath::SyntaxError in response or log leak | Rails backends, feed processors, XML configuration readers |
Because error messages are often suppressed in production, library fingerprinting from HTTP response headers, server banners and framework cookies is a useful secondary signal. A Java SOAP service frequently reveals its stack through X-Powered-By or a fault envelope even when it suppresses exception bodies. The probe adapts: namespace handling differs between libxml2 and the Java XPath engine, and a single-payload approach creates a silent false negative for every variant it did not model.
XPath injection severity is set by what the confirmed predicate achieves, not by the injection class alone. An authentication bypass that grants a session is critical - arbitrary authentication bypass is the impact, not the mechanism. A blind boolean oracle that leaks only result-set cardinality, with no path to a session or data read, is high while the extraction path is unconfirmed. Blind data extraction character by character via substring() and boolean conditions is confirmed high to critical depending on what the nodes contain. Structural disclosure that leaks node names but no values is medium, and treated as reachable until the extraction path is closed.
The fix is the same regardless of library: never concatenate user input directly into an XPath expression. For simple lookups, rewrite the query so user-supplied values are compared inside fixed quoted literals using library-specific escaping (apostrophe-doubling per the XPath 1.0 specification, or ESAPI XPathEscaper). Where query complexity demands dynamic path segments, pre-validate that input matches a strict allowlist of known node names before any string is assembled. The confirming request and response attach to the finding; the remediation names the expression builder and the escaping function required.