XXE prevention: check how your app parses XML

If your app reads XML from outside, its parser may follow instructions hidden in the document. XXE prevention means making sure no parser in your app resolves external entities from untrusted input. This guide explains what the attack can do, how to find every place your code parses XML, how to set each parser up the way OWASP describes, and how to prove it with a harmless test on a staging copy.

What XXE is and what it can do

XML lets a document declare entities: named pieces of content that the parser fills in. An external entity tells the parser to fetch that content from somewhere else, such as a file path or a URL. If the document comes from an attacker and your parser resolves it, the parser does the fetch on your server, for them.

The OWASP XML External Entity Prevention Cheat Sheet names three results:

  • Local files exposed. The entity points at a file on your server, and its contents end up in a response or a stored record.
  • Server-side request forgery. The entity points at a URL, so your server makes a request the attacker chose, including to internal addresses. See SSRF testing for that side of the problem.
  • Resources exhausted. The document makes the parser do far more work than it should.
Diagram of what XXE can do and OWASP's general guidance: disable DTDs, no external entities or DTD loading, XInclude off

What XXE can do, and OWASP's general rules. Simplified from the OWASP XML External Entity Prevention Cheat Sheet.

Find every place your app parses XML

You cannot fix a parser you do not know about. Search your code and dependencies for XML parsing, not only the endpoints that say XML on the label:

  • API endpoints that accept application/xml or text/xml.
  • Features that read uploaded files, where a format is XML underneath.
  • Imports, feeds and integrations that receive XML from partners.
  • Schema validation and XSLT style sheet processing. OWASP points out that both can load outside resources too.
  • Libraries that wrap a parser for you. OWASP notes that some of them replace the safe settings you gave the parser underneath.

For Java, OWASP lists ready-made Semgrep rules that flag parsers created without the setting that rejects DOCTYPE declarations. A static check like that finds parsers faster than reading code by hand.

XXE prevention: OWASP's rules for every parser

Settings differ by language and library, but the rules are the same everywhere:

  1. Disable DTDs whenever possible. A DTD is the part of a document where entities are declared. If your parser can reject any document with a DOCTYPE declaration, turn that on.
  2. If you need DTDs, turn off external entity resolution and external DTD loading, and limit how far entities can expand.
  3. Keep XInclude off unless you truly need it. It can load resources without any DTD.
  4. Restrict outside access in schema validation and XSLT, not only in parsing.
  5. Fail closed. If a security setting is not supported, treat that as a configuration failure. Do not go on parsing untrusted XML half protected.

One limit to know: OWASP says preventing XXE does not prevent every XML denial-of-service attack. Entity expansion and other resource problems are covered in its separate XML Security Cheat Sheet.

Language notes from the cheat sheet

A few points per stack, all from OWASP's sheet. Read your own language's section before you change settings.

  • Java. For DOM and SAX parsers, reject DOCTYPE declarations. For StAX, set both the DTD and the external entity properties: DTD processing is on by default, and the external entity default is not specified. setExpandEntityReferences(false) is not a substitute for blocking outside resources. For JAXB, hand it a hardened StAX reader instead of raw XML.
  • Python. For untrusted XML, use the defusedxml parsing functions in place of the standard library ones, and set forbid_dtd=True where you do not need DTDs. Keep Python and its Expat library up to date.
  • PHP. With libxml 2.9.0 or later, entity substitution is off by default. Do not turn on LIBXML_NOENT, LIBXML_DTDLOAD or LIBXML_DTDVALID for untrusted XML without blocking outside resources.
  • .NET. Safety depends on the parser and the framework version. ASP.NET apps also need httpRuntime targetFramework set to 4.5.2 or later in Web.config, or they risk unsafe defaults.

The pattern across all four: defaults vary, so set the safe option on purpose and check it took effect.

Worked example: test one XML endpoint safely

Say your app has an import feature at POST /api/import that accepts an XML list of products and shows a summary. Use a staging copy, never production, and only systems you own or are allowed to test.

  1. Baseline. Send a small valid XML file and note the summary.
  2. DOCTYPE probe. Send the same file with a DOCTYPE declaration that defines a harmless internal entity, and use it in a product name. If the request is rejected, DTDs are disabled: the best result. If the entity's text shows up in the summary, DTDs are processed, so check external entities next.
  3. External entity probe. Define an entity that points at a URL on a test server you control, and use it in a product name. Watch that server's log. A request from your app's address means the parser resolves external entities, even if nothing shows in the response.
  4. Fix and retest. Apply your parser's safe settings, then send the same three requests again. The DOCTYPE should be rejected, or at least no request should reach your test server.

Never point a test entity at a real secret on the server. Proving the parser fetches anything is enough.

Keep XML input safe after the fix

Parsers return through new features. Add a check to code review for any new XML parsing, wrapper library or upload format, and keep the retest from the worked example as an automated test. Treat the data that comes out of the parser like any other input, with the same input validation rules. If your app also turns other formats back into objects, the insecure deserialization checks are close cousins.

Frequently asked questions

Is XXE still a risk with modern libraries?

It depends on the library and its version. Some, such as PHP with libxml 2.9.0 or later, have entity substitution off by default, while others, such as Java's StAX, process DTDs by default. Set the safe option on purpose and test it.

Does disabling external entities stop every XML attack?

No. OWASP says XXE prevention does not stop every XML denial-of-service attack, so also limit entity expansion and request size, as its XML Security Cheat Sheet describes.

What is blind XXE?

It is when the parser resolves an external entity but nothing appears in the response. You detect it by pointing the entity at a test server you control and watching for the incoming request.

What is the safest setting?

Disabling DTDs entirely, and rejecting any document with a DOCTYPE where the parser supports it. OWASP lists that first.

Get started

Search your code for XML parsers today, list each one with its settings, and run the three probes on staging against the most exposed one. Fix what resolves, then retest.

If you want a second pair of eyes, whitehatstoic's security testing covers web app and API review, with a written report, fixes and a retest after fixes. It is scoped after a short call, and no test can promise to find every weakness.

whitehatstoic's Cybersecurity and AI safety testing card listing web app and API review, prompt injection tests and retest after fixes

Building something that has to be safe? Book a meeting with whitehatstoic: tell us the product, the deadline and your biggest worry, and we reply with a plan and a price.

0 likes

Comments

No comments yet.

Sign in or make an account to comment.