Error-based

When an XPath processor returns its error messages to the client, a deliberately forced failure can carry node text out inside the error string. The reliable form of this is specific to XPath 2.0 and XQuery engines (Saxon, BaseX, eXist-db, the .NET 2.0 processors): their type constructors validate their argument and name the offending value when it fails. XPath 1.0 engines such as libxml2 do not raise on bad coercions, so against those the error channel confirms and fingerprints injection but does not pull values; the technique below therefore targets a 2.0-capable processor, identified first by the fingerprinting payloads at the end.

Why a cast leaks data#

A cast or type constructor like xs:integer(...) parses its argument and, on failure, reports what it could not convert. Feeding node text into a numeric constructor where the text is not a valid number raises a dynamic error whose message quotes the value, for example FORG0001: Cannot convert string "s3cr3t" to xs:integer. The password is now in the error.

Forcing a cast failure#

Inject so the surrounding expression stays valid up to the constructor, then push the target node into it:

code
' or xs:integer((//user[1]/pass)) or '

When the password is non-numeric, the engine aborts with a FORG0001-style message containing the value. Keep each fragment small and unambiguous with substring:

code
' or xs:integer(substring((//user[1]/pass),1,20)) or '

Advance the offset to read past the first twenty characters, and change the path to move between nodes:

code
' or xs:integer((//user[2]/@role)) or '

Where a value happens to be numeric, cast it to a type it cannot satisfy instead, such as xs:date(...) or xs:QName(...), so the conversion still fails and reports the text.

Placing text directly with fn:error#

Engines that expose fn:error() let an attacker build the fault string, embedding node text with no reliance on a conversion quirk:

code
' or error(xs:QName('x'), string(//user[1]/pass)) or '

The processor surfaces the supplied description verbatim, so the node text appears in the error channel directly.

Leaking structure#

Before pulling values, the same mechanism reveals the shape of the document. Forcing name() or local-name() through a failing cast reports element names one node at a time:

code
' or xs:integer(name(/*[1])) or '

The conversion error names the root element; walking indices with name(/*[1]/*[position()=N]) maps the tree so value extraction knows where to aim.

Fingerprinting the engine first#

Error-based value extraction only works on a 2.0 engine, so confirm the processor before committing to casts. A deliberately malformed expression returns an engine-specific parse error:

code
'
"]
count(//

An unbalanced quote or bracket produces a message whose exact wording identifies the engine (libxml2, MSXML, Saxon, .NET). If the fingerprint is a 1.0-only engine such as libxml2, fall back to boolean-blind extraction through the predicate; if it is a 2.0 engine, the cast-failure channel above reads values directly.

References#

Cookie Consent

We use cookies to enhance your experience. Learn more