Diagnosing XXE Exposure in Java XML Parsers: Checks, Fixes, and Escalation
Recognize XXE exposure in Java XML parsers, trace it to parser settings, apply targeted fixes, and verify with a canary payload.
23 Sept 2025, 06:18 UTC

When to suspect XXE rather than an ordinary parse error
XML External Entity (XXE) abuse happens when a parser reads a DOCTYPE declaration from untrusted XML and resolves an entity that points outside the document: a local file, an internal HTTP endpoint, or another system resource. The parser is doing what the XML specification permits. The defect is that the application handed it untrusted input with entity resolution still enabled.
Conditions worth investigating:
- Response bodies or logs contain content the application never read, such as lines from
/etc/passwdor a Windowswin.ini. - Outbound HTTP/S connections from the parsing host to internal addresses or cloud metadata endpoints that no business logic should call.
- Parser exceptions such as
SAXParseExceptionorIOExceptionwhose message names a DTD or URL the application never configured. - Documents that parse successfully but arrive with elements missing, because a required external DTD could not be fetched.
Symptom-to-cause table
| Symptom | Likely cause | First check |
|---|---|---|
| File contents appear in output | External general entity resolved from a file:// SYSTEM identifier | Read the factory setup for entity-resolution settings |
| Outbound request to an internal host | External entity or external parameter entity used for SSRF | Capture egress during a controlled parse |
| Exception naming an unreachable DTD | Parser tried to fetch an external DTD | Confirm whether the DTD is actually required |
| No exception, entity value empty | Expansion disabled; parser silently ignored the entity | Confirm the setting is intentional and documented |
| Same payload behaves differently per service | Different parser library or version | Inventory factories and dependency versions |
Ordered checks
- Inventory parser entry points. Run from the repository root with read access to source:
Expected result: a short list of classes. Every hit is a candidate.grep -Rn "SAXParserFactory\|DocumentBuilderFactory\|XMLInputFactory\|SchemaFactory\|TransformerFactory" --include=*.java .TransformerFactoryandSchemaFactoryare commonly missed because they are not obviously XML parsers. - Read the configuration around each factory. Check whether DTD support and external entities are disabled before the parser is created. For JAXP SAX or DOM:
SAXParserFactory f = SAXParserFactory.newInstance(); f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); f.setFeature("http://xml.org/sax/features/external-general-entities", false); f.setFeature("http://xml.org/sax/features/external-parameter-entities", false); f.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); f.setXIncludeAware(false);FEATURE_SECURE_PROCESSINGalone is not a complete XXE control on every implementation; the DOCTYPE and external-entity features are the ones that matter. These feature URIs are implementation-specific. IfsetFeaturethrowsParserConfigurationException, that implementation does not support the feature and you need a different control rather than assuming it worked. - Handle StAX and Woodstox separately. The StAX API uses properties, not features:
SettingXMLInputFactory xif = XMLInputFactory.newInstance(); xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);SUPPORT_DTDto false is the stronger control, but it also blocks internal entity expansion, which can break documents that rely on DTD-defined defaults. - Check dependency versions. Run:
Record the versions. Some older Xerces and Woodstox releases default to resolving external entities. Upgrading helps, but explicit configuration is still required because defaults vary by integration and by how the library is wrapped.mvn dependency:tree -Dincludes=*xerces*,*woodstox*,*xml-apis* - Verify with a canary payload in a test, not in production. Point the entity at a file you create yourself:
Assert that parsing throws, or that the resulting text does not contain the canary string. Never aim a test entity at a real credential file or a live internal service.String xml = "<!DOCTYPE r [ <!ENTITY x SYSTEM \"file:///tmp/xxe-canary.txt\"> ]><r>&x;</r>"; DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilder db = dbf.newDocumentBuilder(); // Expect a SAXParseException mentioning DOCTYPE, not the canary file contents.
Fixes matched to what you found
- DOCTYPE is never needed: disallow DOCTYPE declarations. This is the strongest and simplest control, and it also blocks internal entity expansion.
- DTD needed for internal defaults only: keep DTD support, disable external general and parameter entities, and install an
EntityResolverthat returns an empty stream for any external identifier instead of fetching it. - Signatures or encryption are involved: XML Signature and XML Encryption can legitimately require external references. Do not blanket-disable. Restrict resolution to an allowlist of trusted URIs and reject everything else.
- Legacy parser with unsafe defaults: upgrade the library, then still apply the explicit features above. A newer default is not a substitute for configuration you can audit.
- Multiple services affected: fix at a shared parsing utility or at the gateway rather than at each call site, so the control cannot drift.
Verification and limitations
After changing configuration, rerun the canary test and, in staging, capture egress while parsing the payload. The signal is the absence of a connection to the canary host, plus a parse failure that names the DOCTYPE. Then rerun functional tests that use real documents, because DTD-dependent documents are the most likely to break.
Limitations to keep in view: feature URIs differ between JAXP implementations and some features are silently ignored rather than rejected, so a passing test proves the path you exercised and not every parser in the application. Disabling DTDs breaks documents that use entity-defined defaults, and some signature or encryption profiles depend on external references. If you cannot monitor egress, you cannot confirm the SSRF half of the fix.
Escalate when
- Untrusted XML must be accepted and DTDs cannot be disabled because of a signed or encrypted payload format.
- The same finding appears across multiple services or in a shared library, indicating a systemic configuration gap.
setFeaturethrows for the controls you need and no supported library version provides them.- You cannot establish egress monitoring to confirm that external fetches have stopped.
In those cases, consider stripping DOCTYPE declarations at an API gateway before the document reaches the application, or moving parsing into a service with strict outbound network policy.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.