Skip to content

XPath Injection

XPath Injection

Description

XPath injection is a type of attack that can change the intent of an XPath query that is executed on an application’s backend. An application might be vulnerable to this attack if special characters are injected into a user-supplied input value, that input is not filtered and is concatenated with other strings to construct an XPath query, which is executed against an XML document.

Impacts of this attack can include bypassing authentication logic, or the disclosure of sensitive data within the XML document being queried.

Examples

Java

 String xmlInput = "<users>" +
            "<user><username>admin</username><password>admin@123</password></user>" +
            "<user><username>guest</username><password>guest@123</password></user>" +
            "</users>";

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlInput)));

//Input example : 
String inputUsername = "admin' or '1'='1' or '";
String inputPassword = "randomPassword";

XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();

String query = String.format("//user[username/text()='%s' and password/text()='%s']", inputUsername, inputPassword);
XPathExpression expr = xpath.compile(query);

Object result = expr.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;

System.out.println(nodes.getLength() > 0 ? "Authenticated" : "Authentication Failed");

Javascript

const express = require('express');
const libxml = require('libxmljs');

const app = express();
const xmlInput = `<users>
  <user><username>admin</username><password>admin@123</password></user>
  <user><username>guest</username><password>guest@123</password></user>
</users>`;

app.post('/authenticate', (req, res) => {
  const { username, password } = req.body;

  const xmlDoc = libxml.parseXml(xmlInput);
  const query = `//user[username/text()='${username}' and password/text()='${password}']`;
  const nodes = xmlDoc.find(query);

  res.send(nodes.length > 0 ? 'Authenticated' : 'Authentication Failed');
});

Php

<?php
$xmlInput = "<users>" .
    "<user><username>admin</username><password>admin@123</password></user>" .
    "<user><username>guest</username><password>guest@123</password></user>" .
    "</users>";

//Input example : 
$inputUsername = "admin' or '1'='1' or '";
$inputPassword = "randomPassword";

$doc = new DOMDocument();
$doc->loadXML($xmlInput);

$xpath = new DOMXPath($doc);

$query = "//user[username/text()='$inputUsername' and password/text()='$inputPassword']";
$nodes = $xpath->query($query);

echo ($nodes->length > 0) ? "Authenticated" : "Authentication Failed";
?>

Recommendation

User input should be strictly validated before being incorporated into XPath queries. In most cases, it will be appropriate to accept input containing only short alphanumeric strings. At the very least, input containing any XPath metacharacters such as " ' / @ = * [ ] ( and ) should be rejected.

Subsequently, encoding user input using HTML entities can provide an additional layer of defense against potential attacks by rendering special characters inert. However, a superior approach, if available, is to utilize parameterized XPath queries. This method, akin to SQL's parameterized queries, involves inserting user input as a variable into the query. In doing so, any special characters are either handled to avoid query syntax modification or cause the query to fail, thereby enhancing security."

Examples

Java

String xmlInput = "<users>" +
        "<user><username>admin</username><password>admin@123</password></user>" +
        "<user><username>guest</username><password>guest@123</password></user>" +
        "</users>";

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlInput)));

//Input example :
String inputUsername = "admi'n";
String inputPassword = "admin@//'123";

// Sanitize inputs 
inputUsername = inputUsername.replaceAll("[^a-zA-Z0-9]", "");

XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();

// Using parameterized queries
String query = "//user[username/text()=? and password/text()=?]";
XPathExpression expr = xpath.compile(query);

expr.setXPathVariableResolver((varName) -> {
    if (varName.equals("username")) {
        return inputUsername;
    } else if (varName.equals("password")) {
        return inputPassword;
    }
    return null;
});

Object result = expr.evaluate(doc, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;

System.out.println(nodes.getLength() > 0 ? "Authenticated" : "Authentication Failed");

Javascript

const express = require('express');
const libxml = require('libxmljs');

const app = express();
const xmlInput = `<users>
  <user><username>admin</username><password>admin@123</password></user>
  <user><username>guest</username><password>guest@123</password></user>
</users>`;

app.post('/authenticate', (req, res) => {
  let { username, password } = req.body;

  const xmlDoc = libxml.parseXml(xmlInput);
  const query = `//user[username/text()=$username and password/text()=$password]`;
  const nodes = xmlDoc.find(query, { $username: username, $password: password });

  res.send(nodes.length > 0 ? 'Authenticated' : 'Authentication Failed');
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Php

<?php
$xmlInput = "<users>" .
    "<user><username>admin</username><password>admin@123</password></user>" .
    "<user><username>guest</username><password>guest@123</password></user>" .
    "</users>";

$inputUsername = "admin";
$inputPassword = "admin@123";

// Sanitize inputs to avoid ' and " 
$inputUsername = htmlspecialchars($inputUsername, ENT_QUOTES, 'UTF-8');
$inputPassword = htmlspecialchars($inputPassword, ENT_QUOTES, 'UTF-8');

$doc = new DOMDocument();
$doc->loadXML($xmlInput);

$xpath = new DOMXPath($doc);

// Using parameterized queries
$query = "//user[username/text()='" . $inputUsername . "' and password/text()='" . $inputPassword . "']";
$nodes = $xpath->query($query);

echo ($nodes->length > 0) ? "Authenticated" : "Authentication Failed";
?>

Standards

  • GDPR:
    • ART_32
  • PCI_STANDARDS:
    • REQ_6_4
    • REQ_6_5