XML External Entity injection exploits a feature of XML: documents can define entities that reference external resources, and parsers will fetch them.
If the parser resolves that entity, the file's contents end up in the parsed document and often in the response.
The same mechanism reaches network resources, which makes XXE a route to SSRF. Including cloud metadata endpoints, and a way to cause denial of service through recursive entity expansion.
The fix by language
Python. Use defusedxml, which is a drop-in replacement with the dangerous features disabled:
Recent xml.etree versions are safer than they were, but defusedxml is unambiguous and easy to review.
Java, the parser needs configuring explicitly:
disallow-doctype-decl alone covers most of it by rejecting the DTD outright. The same applies to SAXParserFactory, XMLInputFactory and TransformerFactory. Each needs its own configuration.
.NET, modern XmlReader defaults are safe; set DtdProcessing = DtdProcessing.Prohibit explicitly and avoid legacy XmlTextReader.
Node. Most XML libraries don't resolve external entities by default. Check the specific library rather than assuming, particularly wrappers around libxml.
PHP, modern versions with libxml2 2.9+ disable it by default. libxml_disable_entity_loader(true) on older stacks.
Where XML arrives
Less common than it was, and still present: SOAP endpoints, SAML assertions in SSO flows, RSS and sitemap ingestion, SVG uploads, XML configuration import, office document formats, and anything speaking to a legacy system.
SAML is worth singling out. It's XML, it's security-critical, and it's parsed before authentication has happened.
Blind XXE
If the parsed content isn't reflected back, the bug is still exploitable, data can be exfiltrated through an out-of-band channel to a server the attacker controls. Absence of output in the response is not evidence of safety.
Better still, don't accept XML
Where a format is your choice rather than a protocol requirement, JSON has no equivalent mechanism. Reducing XML parsing to the places it's genuinely required removes the class of bug rather than configuring around it.
How to check what you've got
One of the classes scanners handle well. Parser instantiation without the safe features set is a recognisable pattern with a short dataflow. Grep for DocumentBuilderFactory, SAXParser, XMLInputFactory, etree.parse and simplexml_load.