CWE-611 / OWASP A05

XML External Entity (XXE) Injection (CWE-611): File Disclosure, SSRF & Denial of Service

How unhardened XML parsers resolve external entity URI references (file://, http://) to leak sensitive server files and trigger internal network port scans.

💡 Plain English Explainer (ELI5)

XML has an old, obscure feature called 'External Entities' that acts like a shortcut variable. When parsing an XML document, you can define a variable that tells the computer: 'Whenever you see &secret;, load the file /etc/passwd from the server hard drive and put its text here'. If your XML parser has this turned on, anyone uploading an XML file can read any file on your computer.

Core Concepts & Key Terms

Document Type Definition (DTD)
An XML specification defining document structure and entity substitutions.
External Entity
An entity whose value is loaded from an external URI (`SYSTEM 'file:///path'` or `'http://host'`).
Billion Laughs Attack (XML Entity Expansion)
A denial of service attack where exponentially expanding nested entities consume gigabytes of server memory.
Out-of-Band XXE (OOB XXE)
Exfiltrating file contents via DNS lookups or HTTP requests when XML output is not directly reflected in the response.

Step-by-Step Attack Flow

Step 1

1. Attacker Crafts XML Document

The attacker prepares an XML document containing a custom DOCTYPE declaration defining an external system entity pointing to file:///etc/passwd.

Step 2

2. Uploading XML Payload

The attacker uploads the XML via an invoice upload endpoint, SAML authentication handler, or legacy SOAP API.

Step 3

3. Parser Resolves Entity

The default parser engine resolves the &xxe; reference by reading /etc/passwd from the local filesystem.

Step 4

4. Sensitive Data Extraction

The server includes the parsed XML data in its response, leaking root configuration files or cloud credentials to the attacker.

Source Code: Flaw vs. Secure Implementation

VULNERABLE PATTERN
<!-- VULNERABLE: Malicious XML Payload with External Entity -->
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [  
  <!ELEMENT foo ANY >
  <!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<user>
  <name>&xxe;</name>
</user>

# VULNERABLE PYTHON PARSER
from lxml import etree

def parse_xml_invoice(xml_data):
    # CRITICAL: Default lxml or unconfigured parsers resolve external entities!
    parser = etree.XMLParser(resolve_entities=True)
    root = etree.fromstring(xml_data, parser=parser)
    return root.find("name").text # Returns contents of /etc/passwd!
HARDENED DEFENSE
# SECURE: Explicitly Disable External DTD Resolution
from defusedxml import ElementTree as SafeET
from lxml import etree

# 1. BEST: Use hardened libraries specifically built to reject XXE & Billion Laughs
def parse_safe_invoice(xml_data):
    root = SafeET.fromstring(xml_data)
    return root.find("name").text

# 2. STANDARD: Manually configure lxml to completely disallow DTDs and entities
def parse_hardened_lxml(xml_data):
    parser = etree.XMLParser(
        resolve_entities=False,       # Disallow entity resolution
        no_network=True,              # Disallow network lookups (SSRF defense)
        dtd_validation=False,         # Disable DTD validation
        load_dtd=False                # Refuse loading external DTDs
    )
    root = etree.fromstring(xml_data, parser=parser)
    return root.find("name").text

Engineering Hardening Checklist

← Browse Full Security Directory Explore Vulnerability Playbooks →