How external entities in XML get abused, and how to stop it
XML parsers with external entity processing enabled can be abused for file disclosure or SSRF. We explain how XXE works, why many modern libraries disable it by default today, and how to explicitly harden DOMDocument in PHP.
Table of Contents
- 1. What Is an XXE Attack?
- 2. How an XXE Attack Works in Detail
- 3. Why Many Modern Libraries Already Protect Against It
- 4. Common Entry Points for XXE
- 5. Consequences of a Successful XXE Attack
- 6. Explicit Hardening in PHP
- 7. The Special Case of SVG Uploads
- 8. Testing and Code Review for XXE
- 9. Best Practices and Checklist
- 10. Summary
- 11. FAQ
1. What Is an XXE Attack?
XML allows a document to define its own entities, similar to variables. External entities take that a step further and can point at an external resource, such as a local file or a URL. If an XML parser processes these external entities without the developer having deliberately configured it that way, the foundation for an XXE attack is in place.
An attacker defines an external entity in the XML document that points at a sensitive local file such as /etc/passwd or an internal configuration file, then references that entity somewhere whose content ends up visible in the application's response.
2. How an XXE Attack Works in Detail
A typical malicious XML document defines an external entity in the DOCTYPE section using the SYSTEM keyword, followed by a path or a URL. Once that entity is referenced in a text node, a vulnerable parser replaces the reference with the content of the referenced resource and builds it directly into the parsed document tree.
On top of the libxml configuration, the fixed version in the example below rejects any document that contains a DOCTYPE at all, since an invoice or order XML file never legitimately needs a doctype definition. This combination of configuration and business level validation shrinks the attack surface to a minimum, even if the behavior of a future libxml version were to change.
<?php
declare(strict_types=1);
namespace App\Service;
final class InvoiceXmlImporter
{
// Vulnerable: external entities and network access are not disabled
public function importVulnerable(string $xmlContent): \DOMDocument
{
$document = new \DOMDocument();
$document->loadXML($xmlContent);
return $document;
}
// Fixed: explicitly block external entities, DTDs, and network access
public function importSecured(string $xmlContent): \DOMDocument
{
$document = new \DOMDocument();
// Since PHP 8, libxml disables external entities by default,
// explicit hardening still matters as defense in depth.
$previousSetting = libxml_use_internal_errors(true);
$success = $document->loadXML($xmlContent, LIBXML_NONET);
if ($success === false || $document->doctype !== null) {
libxml_use_internal_errors($previousSetting);
throw new \InvalidArgumentException('XML document with DOCTYPE is not allowed.');
}
libxml_use_internal_errors($previousSetting);
return $document;
}
}
3. Why Many Modern Libraries Already Protect Against It
Since PHP 8.0, the underlying libxml library no longer processes external entities automatically by default, after older PHP versions required an explicit call to libxml_disable_entity_loader. That function is now marked deprecated, since its purpose has largely been absorbed by the new default behavior.
Similar shifts happened across many other language ecosystems: modern XML libraries ship with XXE-safe defaults, since the vulnerability appeared so often over the years that opting in felt safer than opting out. Caution is still warranted, though, since older library versions, legacy code, or alternative XML parsers like SimpleXML can remain vulnerable under certain configurations.
4. Common Entry Points for XXE
XXE shows up anywhere an application accepts and parses XML data from the outside: file uploads in XML, SVG, or DOCX format, SOAP interfaces, RSS and Atom feed imports, and configuration imports delivered as XML. SVG files in particular get overlooked often, even though they are technically full XML documents with DOCTYPE support.
Even seemingly internal interfaces are affected, for example an XML-based import of product data or invoices from an ERP system, if the uploaded file does not exclusively come from trusted internal systems but can, for example, be uploaded by external suppliers.
5. Consequences of a Successful XXE Attack
A successful XXE attack often allows reading arbitrary local files that the PHP process has read access to, including configuration files with database credentials or private keys. If the parser also allows network access, XXE can additionally be used for SSRF attacks against internal systems.
In particularly unfortunate configurations, so-called billion laughs attacks are even possible, where nested entity definitions expand exponentially and push the server into a denial of service condition through memory or CPU exhaustion.
6. Explicit Hardening in PHP
Even though PHP 8 disables external entities by default, it is still worth explicitly using LIBXML_NONET to block any network access performed by the parser, along with a deliberate rejection of documents containing a DOCTYPE for any format that never legitimately needs one.
SimpleXML relies on the same underlying libxml settings as DOMDocument, since both sit on top of the same C library. When XML is processed through simplexml_load_string, the same flags such as LIBXML_NONET should be set and the same DOCTYPE check should be applied just as consistently.
7. The Special Case of SVG Uploads
SVG files are often treated like regular image files in upload forms, but technically they are full XML and can contain DOCTYPE definitions with external entities. If an uploaded SVG file gets parsed server side, for a preview or a resize for example, the same XXE hardening needs to apply as for any other XML import.
A robust additional measure is sanitizing uploaded SVG files before delivery with a dedicated sanitizer library that consistently strips DOCTYPE definitions, script tags, and external references, rather than relying solely on parser configuration.
8. Testing and Code Review for XXE
A targeted test sends a document containing an external entity that points at a known, harmless local file, such as a PHP configuration file, to every XML-processing endpoint and checks whether its content shows up in the response. It also helps to test with an entity pointing at an internal test URL to rule out SSRF through XXE.
During code review, it is worth specifically searching for loadXML, simplexml_load_string, and SOAP calls, to confirm that every one of these call sites either runs on a current PHP version with safe default behavior or is explicitly hardened with LIBXML_NONET and a DOCTYPE rejection.
9. Best Practices and Checklist
Every place that parses XML from a source that is not fully trusted should explicitly set LIBXML_NONET, reject documents containing a DOCTYPE whenever the format has no legitimate need for one, and run on a current PHP version with safe libxml default behavior.
It also helps to handle SVG uploads with special care, add an automated XXE test for every XML endpoint, and keep the underlying libxml version updated regularly as part of a robust defense against XXE.
| Entry Point | Example | Risk | Mitigation |
|---|---|---|---|
| XML file upload | Invoice import via XML | Local file disclosure | Reject DOCTYPE, LIBXML_NONET |
| SVG upload | Profile picture in SVG format | XXE via image endpoint | SVG sanitizer plus parser hardening |
| SOAP interface | External partner integration | SSRF via XML payload | Disable network access in the parser |
| Feed import | Reading an RSS/Atom feed | Denial of service via billion laughs | Limit entity expansion, block DOCTYPE |
Mironsoft
Security audits, OWASP-compliant hardening, and secure architecture
Applications that actually hold up against a real attack attempt?
We review existing applications for classic OWASP vulnerabilities, insecure authentication, and missing input validation, then build an architecture that structurally reduces attack surface instead of just patching individual symptoms.
Security Audit
Systematically checking OWASP Top 10, auth flows, and input validation for vulnerabilities.
Secure Architecture
Building rate limiting, encryption, and access controls correctly from the ground up.
Incident Readiness
Establishing logging, monitoring, and response processes for when things go wrong.
10. Summary
XXE
Root Cause
Parser processes external entities in XML documents.
Detection
Test with an entity referencing a known local file.
Fix
LIBXML_NONET, DOCTYPE rejection, current PHP version.
Prevention
Code review for loadXML calls, SVG sanitizing.