Choose your stack

Stacks without a card

How it happens

  • Input is concatenated into the query text: //users/user[username/text()='" + name + "']. A value containing a quote and or '1'='1 closes the literal and rewrites the predicate.
  • The result controls authentication: an injected tautology makes the login query match a user, so the attacker signs in without a password, or enumerates roles blindly by observing which payloads return a match.
  • Escaping is attempted and fails. XPath 1.0 has no escape sequence inside string literals, so doubling or backslashing quotes does not reliably neutralize them, and a payload can switch delimiters or avoid quotes entirely.
  • One field is parameterized and another is not. The username is bound as a variable after a review, but the password (or a second search field) is still concatenated, so the injection simply moves.
  • A blocklist of characters or keywords is used instead of parameterization, and is bypassed with encodings, functions or structure the list did not anticipate.

Fix principles

  • Parameterize the query: compile the XPath with named variables ($username, $password) and bind their values through the engine's variable mechanism, never by building the query string.
  • In Java, set an XPathVariableResolver on the XPath (or XPathExpression) and reference $variables in the expression. In .NET, supply variables through a variable-bearing XsltContext (IXsltContextVariable) rather than concatenating into XPathNavigator.Select.
  • Apply it to every field and every query: login, search, counts and any secondary lookup. Parameterizing one input and concatenating another leaves the weakness in place.
  • Keep the authentication decision in code, not in the query's truthiness: fetch the user by a bound username, then verify the password with a constant-time comparison of a stored verifier, so a matching-node trick cannot stand in for a correct password.
  • Do not rely on escaping or denylists for XPath 1.0; there is no safe quoting of a string literal, so variables are the control.
  • Where the data is relational, consider whether XML and XPath are needed at all; a database with parameterized queries removes this class.

The scenario

A staff directory and login are backed by an XML file of users (username, password, role). POST /login authenticates with an XPath query; GET /directory?name= searches it. The fixed build binds the inputs as XPath variables; the vulnerable build concatenates them, so ' or '1'='1 logs in as the first user; the wrong build parameterizes the username but still concatenates the password, so the same payload works through the password field.

How the cards prove it

  • Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong implementations must fail. run-all.mjs does this for every card.
  • The exploit tests send ' or '1'='1 (and similar) in the username and in the password, and a wildcard in the directory search; each must be refused or return only true matches. The functional tests keep correct credentials and an exact-name search working.
  • On your own endpoint, try an injected tautology in every input that reaches the XPath, including secondary search and filter fields, and confirm it neither authenticates nor widens the result set.
  • Static analysis helps: Semgrep and CodeQL have XPath-injection rules that flag a query built from a variable. Each card's scannerRules records the rule IDs and what they found; the regression tests remain the control for the wrong implementation, where one field is parameterized and another is not.

Identifiers

CWE-643 (Improper Neutralization of Data within XPath Expressions) is the weakness the cards show: untrusted data becomes part of an XPath query. CWE-91 (XML Injection / Blind XPath Injection) is its parent. Both sit under CWE-943 (Improper Neutralization of Special Elements in Data Query Logic), which is in OWASP Top 10:2025 A05 Injection (whose CWE list includes CWE-643). MITRE lists no CAPEC on the CWE-643 page, so capec is left empty; the general attack pattern is CAPEC-83 (XPath Injection), which MITRE associates with the parent CWE-91. The 2025 CWE Top 25 does not rank CWE-643.

References

  1. OWASP Top 10:2025 A05 Injection
  2. CWE-643: Improper Neutralization of Data within XPath Expressions ('XPath Injection')
  3. CWE-91: XML Injection (aka Blind XPath Injection)
  4. OWASP XPath Injection
  5. OWASP Injection Prevention Cheat Sheet

Guides on this weakness