Skip to content

XPath Injection

What does this mean ?

XPath Injection attacks, like SQL Injection, occur when a website leverages user-supplied information to generate an XPath query for XML data. XPath injection is a sort of attack in which a malicious input can grant unauthorised access to or expose sensitive information such as the structure and content of an XML document. It happens when the query string is built using the user's input.

What can happen ?

An attacker can learn how the XML data is formatted or gain access to data that they would not ordinarily have by delivering purposely incorrect information into the web site. If the XML data is utilized for authentication, they may even be able to elevate their privileges on the website (such as an XML based user file).

Recommendation

  • You must utilize a parameterized XPath interface if one is available, or escape the user input to make it safe to use in a dynamically created query, just as you would for SQL injection.
  • If you use quotes to terminate untrusted input in a dynamically built XPath query, you must escape that quote in the untrusted input to prevent the untrusted data from attempting to escape the quoted context.

Sample Code

Vulnerable :

using System;
using System.Xml;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2091XPathInjectionNoncompliant : Controller
    {
        public XmlDocument doc { get; set; }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Authenticate(string user, string pass)
        {
            String expression = "/users/user[@name='" + user + "' and @pass='" + pass + "']"; // Unsafe

            // An attacker can bypass authentication by setting user to this special value
            // user = "' or 1=1 or ''='";

            return Content(doc.SelectSingleNode(expression) != null ? "success" : "fail"); // Noncompliant
        }

    }
}

Non Vulnerable :

using System;
using System.Text.RegularExpressions;
using System.Xml;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationDotNetCore.Controllers
{
    public class RSPEC2091XPathInjectionCompliant : Controller
    {
        public XmlDocument doc { get; set; }

        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Authenticate(string user, string pass)
        {
            // Restrict the username and password to letters only
            if (!Regex.IsMatch(user, "^[a-zA-Z]+$") || !Regex.IsMatch(pass, "^[a-zA-Z]+$"))
            {
                return BadRequest();
            }

            String expression = "/users/user[@name='" + user + "' and @pass='" + pass + "']"; // Compliant
            return Content(doc.SelectSingleNode(expression) != null ? "success" : "fail");
        }

    }
}

Vulnerable :

public boolean authenticate(javax.servlet.http.HttpServletRequest request, javax.xml.xpath.XPath xpath, org.w3c.dom.Document doc) throws XPathExpressionException {
  String user = request.getParameter("user");
  String pass = request.getParameter("pass");

  String expression = "/users/user[@name='" + user + "' and @pass='" + pass + "']"; // Unsafe

  // An attacker can bypass authentication by setting user to this special value
  user = "' or 1=1 or ''='";

  return (boolean)xpath.evaluate(expression, doc, XPathConstants.BOOLEAN); // Noncompliant
}

Non Vulnerable :

public boolean authenticate(javax.servlet.http.HttpServletRequest request, javax.xml.xpath.XPath xpath, org.w3c.dom.Document doc) throws XPathExpressionException {
  String user = request.getParameter("user");
  String pass = request.getParameter("pass");

  String expression = "/users/user[@name=$user and @pass=$pass]";

  xpath.setXPathVariableResolver(v -> {
    switch (v.getLocalPart()) {
      case "user":
        return user;
      case "pass":
        return pass;
      default:
        throw new IllegalArgumentException();
    }
  });

  return (boolean)xpath.evaluate(expression, doc, XPathConstants.BOOLEAN);
}

Vulnerable :

$user = $_GET["user"];
$pass = $_GET["pass"];

$doc = new DOMDocument();
$doc->load("test.xml");
$xpath = new DOMXPath($doc);

$expression = "/users/user[@name='" . $user . "' and @pass='" . $pass . "']";
$xpath->evaluate($expression); // Noncompliant

Non Vulnerable :

$user = $_GET["user"];
$pass = $_GET["pass"];

$doc = new DOMDocument();
$doc->load("test.xml");
$xpath = new DOMXPath($doc);

$user = str_replace("'", "'", $user);
$pass = str_replace("'", "'", $pass);

$expression = "/users/user[@name='" . $user . "' and @pass='" . $pass . "']";
$xpath->evaluate($expression); // Compliant

Vulnerable :

const express = require('express');
const xpath = require('xpath');
const app = express();

app.get('/some/route', function(req, res) {
  let userName = req.param("userName");

  // BAD: Use user-provided data directly in an XPath expression
  let badXPathExpr = xpath.parse("//users/user[login/text()='" + userName + "']/home_dir/text()");
  badXPathExpr.select({
    node: root
  });
});

Non Vulnerable :

const express = require('express');
const xpath = require('xpath');
const app = express();

app.get('/some/route', function(req, res) {
  let userName = req.param("userName");

  // GOOD: Embed user-provided data using variables
  let goodXPathExpr = xpath.parse("//users/user[login/text()=$userName]/home_dir/text()");
  goodXPathExpr.select({
    node: root,
    variables: { userName: userName }
  });
});

References