Safeguard
Application Security•

CWE-91 XML Injection: How It Happens and How to Fix It

XML injection lets attacker input change the structure of an XML document or query. Here is how CWE-91 shows up in code and how to stop it.

Safeguard Security Team
4 min read

CWE-91, XML Injection (also known as Blind XPath Injection), is what happens when a product does not properly neutralize the special elements XML uses. An attacker can then change the syntax, content or commands of the XML before the end system processes it.

It hits any code that builds XML by gluing strings together: SOAP requests, config files written at runtime, import and export features, and anything that feeds user input into an XPath query. MITRE lists it as not language specific, so it is not a Java problem or a PHP problem. It is a string handling problem.

How it happens

XML gives certain characters a job. MITRE names the reserved words and characters <, >, a double quote and &. If those arrive inside user data and you write them straight into a document, the parser reads them as markup, not as text.

Here is the vulnerable pattern in Python.

# Vulnerable: user input goes straight into the XML string
def build_profile(name, role):
    return "<user><name>" + name + "</name><role>" + role + "</role></user>"

# name = "bob</name><role>admin</role><name>x"
# The document now carries a second, attacker chosen role element.

The same thing happens when input is placed into an XPath expression. The attacker supplies quote characters and extra conditions, and the query returns more than it should. "Blind" XPath injection is the version where the attacker learns the answer one true or false response at a time.

What an attacker gains

MITRE lists the consequences as confidentiality, integrity and availability impact. In concrete terms that means:

  • Reading application data the query was never meant to expose.
  • Modifying application data by adding or overriding elements and attributes.
  • Executing unauthorized code or commands, if the downstream system treats the injected content as instructions.

How to find it

MITRE notes that static analysis (SAST) can find some instances by modeling data flow from input to the place where XML or XPath is built. Add two simple checks of your own:

  1. Search the codebase for string concatenation or formatting that contains angle brackets or element names.
  2. Send <, & and a quote through every field that ends up in an XML message and watch whether the output is well formed and unchanged in meaning.

How to fix it

MITRE's mitigation for this weakness is input validation with an "accept known good" strategy. Assume all input is malicious. Define what a valid value looks like (length, type, range, syntax, business rules) and reject or transform anything else. Do not rely only on blocking bad characters, because a denylist tends to miss something.

In practice, pair that with a proper XML library so the structure is never built by hand.

import xml.etree.ElementTree as ET

def build_profile(name, role):
    allowed_roles = {"viewer", "editor"}
    if role not in allowed_roles:
        raise ValueError("unknown role")
    user = ET.Element("user")
    ET.SubElement(user, "name").text = name   # library escapes < > &
    ET.SubElement(user, "role").text = role
    return ET.tostring(user, encoding="unicode")

The role is checked against a fixed list, and the name is set as text, so the library escapes it. For XPath, use your library's variable binding instead of concatenating the value into the expression.

Quick check

Why is blocking the characters < and & not enough on its own?

MITRE warns against relying only on looking for malicious input. A denylist is likely to miss at least one case, especially when the environment changes. Validate against what is allowed, then let a library handle escaping.

What is the "blind" part of Blind XPath Injection?

The attacker cannot see query output directly, so they infer data from differences in the application's responses to crafted input.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.