Description
XPath expressions can contain conditions, functions, and operators as well as paths that select XML nodes. Concatenating or directly passing request strings into the expression argument of compile, evaluate, evaluateExpression, dom4j selection methods, or Jaxen expression constructors lets an attacker control query structure instead of supplying a data value.
Keep expressions separate from data. In xpath.evaluate(expression, document), the first argument is the expression and the second is the context to query. Declare external values as variables in a fixed expression and supply them through XPathVariableResolver or a dom4j/Jaxen VariableContext.
XPath 1.0 string literals have no universal escape syntax for arbitrary input. Removing quotes, using a generic escape or sanitize helper, applying a format regex, or precompiling the expression does not separate data from expression structure.
Potential impact
- Unauthorized XML access: Changed paths or conditions can select nodes outside the intended filter.
- Security-check bypass: If XPath results drive authentication, authorization, or policy decisions, an attacker may influence those decisions.
- Resource consumption: When an application accepts whole expressions, complex paths and operations can make evaluation expensive. Using XPath alone does not imply a denial-of-service vulnerability.
Remediation
The central rule is to define XPath expressions on the server and bind external input only as data values.
- In Java XPath, use
$namevariables in a fixed expression and supply values throughXPath#setXPathVariableResolver. Set the resolver before compiling or evaluating the expression. - In dom4j and Jaxen, combine a fixed expression with a
VariableContext. - If users must choose structure that variables cannot represent, such as element names, axes, or predicate fragments, map an external key to a small finite set of complete server-defined expressions. Do not append the input string itself.
- Use input validation for business constraints such as length or identifier format, not as a substitute for variable binding.
- Calling
compileafter constructing an untrusted expression is not a safeguard: compilation interprets that string as XPath syntax.
Examples
Java XPath
Before
The request value is inserted between quotes, so profileId can change the condition's structure.
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
final class VulnerableXPathLookup {
static Node findProfile(HttpServletRequest request, Document document)
throws XPathExpressionException {
String profileId = request.getParameter("profileId");
String expression = "/directory/profile[@id='" + profileId + "']";
XPath xpath = XPathFactory.newInstance().newXPath();
return xpath.evaluateExpression(expression, document, Node.class);
}
}
After
The expression stays constant and the request value is bound as a variable. Quotes or XPath tokens in the value remain data rather than expression syntax.
import jakarta.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
final class SafeXPathLookup {
static Node findProfile(HttpServletRequest request, Document document)
throws XPathExpressionException {
String profileId = request.getParameter("profileId");
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setXPathVariableResolver(variable -> switch (variable.getLocalPart()) {
case "profileId" -> profileId;
default -> null;
});
String expression = "/directory/profile[@id=$profileId]";
return xpath.evaluateExpression(expression, document, Node.class);
}
}
XPath is neither thread-safe nor reentrant. Do not repeatedly change a request-specific resolver on a shared XPath object. Use request-scoped objects as shown, or manage concurrent access safely.
dom4j/Jaxen variable binding
dom4j can accept Jaxen's VariableContext when creating an XPath expression.
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.jaxen.SimpleVariableContext;
final class SafeDom4jXPathLookup {
static Node findProfile(Document document, String profileId) {
SimpleVariableContext variables = new SimpleVariableContext();
variables.setVariableValue("profileId", profileId);
XPath xpath = DocumentHelper.createXPath(
"/directory/profile[@id=$profileId]", variables);
return xpath.selectSingleNode(document);
}
}
Selecting server-defined expressions
XPath variables represent data, not syntax such as element names or axes. When users must choose such structure, select a complete server-owned expression instead of validating and concatenating their input.
final class ServerOwnedXPathView {
static String expressionFor(String requestedView) {
return switch (requestedView) {
case "active" -> "/directory/profile[@active='true']";
case "locked" -> "/directory/profile[@locked='true']";
default -> "/directory/profile";
};
}
}
The engine always receives one of the three expressions defined in code. requestedView itself is never inserted into the XPath string.
Arbitrary XPath features and resource limits
If a product requires complete XPath input, such as an administrator diagnostic tool, generic sanitization cannot make meaningful arbitrary expressions safe. Isolate the feature, enforce strong authorization, provide only the required XML data, and limit request frequency and result size.
Java SE 25's jdk.xml.xpathExprGrpLimit and jdk.xml.xpathExprOpLimit limit XPath groups and operators. Keep them within the application's requirements. These limits reduce resource-consumption risk as defense in depth; they do not remove expression injection.
The stable releases checked on September 1, 2026 were dom4j 2.2.0 and Jaxen 2.0.6. Jaxen 2.0.5 replaced recursive core-parser paths with an iterative algorithm and converted remaining stack overflows to JaxenException; 2.0.6 corrected parenthesized-expression evaluation in that algorithm. Dependency updates matter, but they do not restrict the meaning of attacker-selected expressions.
References
- Java SE 25
XPathAPI - Java SE 25
java.xmlmodule and JAXP processing limits - Oracle Java SE 25 Security Developer's Guide
- dom4j 2.2.0 releases
- dom4j 2.2.0
XPathinterface - Jaxen 2.0.6 releases
- Jaxen official release history
- CWE-643: Improper Neutralization of Data within XPath Expressions
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP ASVS 5.0.0 V1.2.4