Here is an example of Java code that is vulnerable to XPath injection:
🥺 Vulnerable Code
String user = request.getParameter("user");
String pass = request.getParameter("pass");
XPath xpath = XPathFactory.newInstance().newXPath();
// Vulnerable: the credentials are concatenated into the XPath expression
String query = "/users/user[name/text()='" + user + "' and pass/text()='" + pass + "']";
NodeList nodes = (NodeList) xpath.evaluate(query, doc, XPathConstants.NODESET);
boolean authenticated = nodes.getLength() > 0;Sending a password of ' or '1'='1 closes the string literal and appends an always-true condition, so the node list is never empty and the login succeeds for any account. The same trick with ' or position()=1 or ' walks the document node by node and lets an attacker read the entire XML file one character at a time.
😎 Secure Code
Here is a version of the same code that is secured against XPath injection:
String user = request.getParameter("user");
String pass = request.getParameter("pass");
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setXPathVariableResolver(name -> {
if ("user".equals(name.getLocalPart())) return user;
if ("pass".equals(name.getLocalPart())) return pass;
throw new IllegalArgumentException("Unknown variable: " + name);
});
// Variables keep the input on the data side of the expression
String query = "/users/user[name/text()=$user and pass/text()=$pass]";
NodeList nodes = (NodeList) xpath.evaluate(query, doc, XPathConstants.NODESET);
boolean authenticated = nodes.getLength() > 0;An XPathVariableResolver binds the values as variables, so quotes and operators in the input are treated as literal text instead of expression syntax. Storing credentials in an XML document is its own problem: move authentication to a real user store with per-user salts and a memory-hard hash such as Argon2id or bcrypt.