Cosa fa#
XXE sfrutta il meccanismo delle XML entity esterne: l'attaccante definisce un'entita' che punta a un file locale o a un URL esterno, e il parser XML la espande automaticamente prima di elaborare il documento, restituendo dati che non dovrebbe mai esporre.
TL;DR#
- XXE: il parser XML processa entity esterne definite dall'attaccante nel DOCTYPE del documento.
- File Disclosure:
SYSTEM "file:///etc/passwd"restituisce il contenuto del file nella risposta XML. - SSRF via XXE:
SYSTEM "http://169.254.169.254/"fa richiedere al server metadati AWS o risorse interne. - Billion Laughs: entity che si espandono esponenzialmente → memory exhaustion → DoS del parser.
- Fix principale: disabilitare il processamento di entity esterne nel parser XML.
- PHP 8+: entity loader disabilitato per default. Java: richiedere configurazione esplicita.
Come funziona XML External Entity#
XML supporta le entity come meccanismo di sostituzione: & diventa &, < diventa <. Le entity esterne estendono questo meccanismo permettendo di puntare a risorse esterne tramite SYSTEM. Il parser le risolve automaticamente, ed e' qui che si trova la vulnerabilita': il "sistema" che risolve l'entita' e' il server, non il client.
sequenceDiagram
participant ATK as Attaccante
participant SRV as Server (parser XML)
participant FS as Filesystem
ATK ->> SRV: POST /api/parse\nContent-Type: application/xml\n[payload con SYSTEM "file:///etc/passwd"]
Note over SRV: Parser processa DOCTYPE
SRV ->> FS: Legge /etc/passwd (risolve entity)
FS ->> SRV: Contenuto di /etc/passwd
SRV ->> ATK: Risposta XML con contenuto del file embedded
Payload base per file disclosure:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<data>&xxe;</data>
</root>Il parser XML incontra &xxe;, risolve l'entity definita nel DOCTYPE, legge /etc/passwd dal filesystem e inserisce il contenuto nel tag <data>. La risposta HTTP del server include il file.
File Disclosure#
La variante piu' comune di XXE legge file arbitrari sul filesystem del server tramite il protocollo file://. Il requisito e' che il processo web abbia i permessi di lettura sul file target.
SSRF via XXE#
Quando il parser XML risolve entity con protocollo http:// o https://, il server effettua una request HTTP per conto dell'attaccante. Questo trasforma XXE in SSRF (Server-Side Request Forgery): il server diventa un proxy non intenzionale verso risorse interne.
flowchart LR
ATK([Attaccante]) -- "SYSTEM http://169.254.169.254/..." --> SRV["Server Web (parser XML)"]
SRV -- "Request HTTP interna" --> META["AWS Instance Metadata\n169.254.169.254"]
META -- "IAM credentials, user-data" --> SRV
SRV -- "Risposta con metadati" --> ATK
style ATK fill:#a72626,stroke:#c0392b,color:#fff
style META fill:#154360,stroke:#2471a3,color:#fff
Payload per AWS Instance Metadata:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root><data>&xxe;</data></root>Il server risponde con il nome del ruolo IAM associato all'istanza. Una seconda request a http://169.254.169.254/latest/meta-data/iam/security-credentials/RUOLO-IAM restituisce le credenziali AWS temporanee (AccessKeyId, SecretAccessKey, Token).
Con queste credenziali, l'attaccante puo' autenticarsi all'API AWS con i permessi del ruolo della macchina.
Billion Laughs - DoS tramite Entity Expansion#
Il Billion Laughs attack non richiede accesso a risorse esterne: sfrutta entity che si riferiscono ad altre entity in una catena geometrica, causando un'esplosione del consumo di memoria nel parser.
<?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 lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>lol9 si espande in 10^9 occorrenze di "lol" (3 byte ciascuna) = ~3 GB di dati in memoria. Un documento XML di 1 KB causa il crash del parser per memory exhaustion.
Fix e Hardening#
La difesa principale contro XXE e' disabilitare il processamento delle entity esterne nel parser XML. La configurazione varia per linguaggio e libreria.
PHP - configurazione sicura:
<?php
// Disabilita entity loader (necessario solo su PHP < 8.0)
libxml_disable_entity_loader(true);
$xml = simplexml_load_string(
$input,
'SimpleXMLElement',
LIBXML_NOENT | LIBXML_DTDLOAD // NON usare questi flag: abilitano entity
);
// Configurazione corretta:
$xml = simplexml_load_string(
$input,
'SimpleXMLElement',
0 // Nessun flag pericoloso
);Java - configurazione sicura:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Disabilita DOCTYPE (approccio piu' sicuro)
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// Alternativamente: disabilita solo entity esterne
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);Non basta disabilitare solo external-general-entities in Java: le external-parameter-entities (nel DOCTYPE) sono un vettore separato. Disabilitare entrambe o vietare il DOCTYPE con disallow-doctype-decl.
Scenario Reale#
Un'applicazione enterprise espone un endpoint /api/import che accetta file XML per importare dati di configurazione. Un security researcher invia:
<?xml version="1.0"?>
<!DOCTYPE test [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">]>
<config><name>&xxe;</name></config>La risposta include il nome del ruolo IAM: EC2InstanceRole-prod. Una seconda request con il nome del ruolo recupera AccessKeyId, SecretAccessKey e Token con validita' di 6 ore. Il researcher ha ora accesso all'account AWS con i permessi del ruolo della macchina di produzione.
Scenario Billion Laughs su CI/CD: una pipeline di CI/CD che processa file XML di configurazione riceve un artifact malevolo con entity in catena. Il worker della pipeline crasha per memory exhaustion, blocca tutti i build del team e richiede restart manuale del servizio.
Dev parallel: come un'API che accetta un body JSON con un campo
webhookUrle fa fetch dell'URL per notifiche, senza validare che l'URL non siahttp://127.0.0.1/adminohttp://169.254.169.254/. La logica e' identica a XXE-SSRF: il server diventa proxy non intenzionale verso risorse interne. La difesa e' la stessa: whitelist degli schemi e degli host raggiungibili, mai trust blind dell'input utente come URI.
Collegato a#
- sql-injection — stessa famiglia injection: input non validato che modifica il comportamento del parser
- command-injection — escalation analoga: da lettura file a esecuzione comandi
- xss — altro tipo di injection, stesso principio di sanitizzazione mancante
- csrf — SSRF via XXE e CSRF hanno dinamica simile: il server/browser fa request per conto dell'attaccante


