Situazione attuale: PHP2Shell. Due vulnerabilità critiche di PHP (CVSS 9,2) consentono codice dannoso senza autenticazione. Patch dal 21/10/2026.Che cosa fare ora →

Prevenire gli XML External Entities (XXE) in PHP

Come gli sviluppatori elaborano XML in PHP in sicurezza: comportamento predefinito di libxml, opzioni rischiose come LIBXML_NOENT, protezione con entity loader e LIBXML_NO_XXE e tipici ingressi XML nascosti.

Stato 6 min di letturaSviluppo sicuro

In breve: l'XXE in PHP si previene caricando XML da fonti esterne senza le opzioni LIBXML_NOENT, LIBXML_DTDLOAD e LIBXML_DTDVALID, rifiutando i documenti con una DTD propria e impostando un entity loader che neghi le risorse esterne. Da libxml 2.9.0 PHP non sostituisce più le entità per impostazione predefinita, perciò libxml_disable_entity_loader() è obsoleta da PHP 8.0. Da PHP 8.4 con libxml 2.13 è disponibile inoltre LIBXML_NO_XXE.

Una vecchia vulnerabilità che non scompare

Gli XML External Entities, in breve XXE, per molti sviluppatori sono un tema chiuso. Con le versioni moderne di libxml PHP ha disattivato per impostazione predefinita il caricamento di entità esterne, e la vecchia funzione di protezione è stata contrassegnata come obsoleta con PHP 8.0. Eppure le vulnerabilità XXE continuano a emergere nelle verifiche.

Il motivo sta quasi sempre in una di tre situazioni: qualcuno ha impostato un'opzione per risolvere un problema di importazione e ha così riattivato la sostituzione delle entità. Una libreria lo fa internamente. Oppure l'applicazione gira su un sistema legacy il cui PHP è stato compilato con una libxml molto vecchia.

L'XXE è pericoloso perché il parser XML carica contenuti che nel documento sono solo referenziati: file locali o indirizzi in rete. Possibili conseguenze sono la lettura di file di configurazione, richieste a sistemi interni simili a quelle dell'SSRF e il sovraccarico del server.

Che cosa vale da libxml 2.9 e PHP 8.0

Il manuale PHP descrive la situazione così: da libxml 2.9.0 la sostituzione delle entità è disattivata per impostazione predefinita. Le entità esterne non devono quindi più essere disattivate espressamente, a meno che non si vogliano risolvere riferimenti a entità interne con determinate opzioni. Ne risulta questo quadro:

Opzione Effetto Rischio con XML esterno
LIBXML_NOENT Sostituisce le entità Secondo il manuale può facilitare attacchi XXE
LIBXML_DTDLOAD Carica il subset esterno Attiva il caricamento di entità esterne
LIBXML_DTDVALID Valida rispetto alla DTD Può facilitare attacchi XXE
LIBXML_NONET Vieta gli accessi di rete durante il caricamento Opzione aggiuntiva protettiva
LIBXML_NO_XXE Disattiva le entità esterne durante la sostituzione Protettiva, da PHP 8.4 e libxml 2.13.0
LIBXML_PARSEHUGE Elimina i limiti interni del parser Facilita il sovraccarico, solo per dati affidabili

Il nome fuorviante di LIBXML_NOENT è una trappola nota: suona come «nessuna entità», ma ottiene l'opposto.

Per gli sviluppatori questo significa che non è rischioso il caricamento di XML in sé, ma ogni punto che modifica deliberatamente il comportamento predefinito. Una ricerca di queste costanti nel codice è quindi la via più rapida per una prima valutazione, ancor prima di qualsiasi verifica onerosa.

Respingere l'XXE in PHP in sicurezza: uno schema per DOMDocument

L'esempio seguente combina più livelli di protezione. Imposta un entity loader che nega ogni risorsa esterna, carica senza opzioni rischiose e rifiuta i documenti con una DTD propria:

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Negare in via di principio le risorse esterne
    libxml_set_external_entity_loader(
        static fn (?string $public, string $system, array $context) => null
    );

    $optionen = LIBXML_NONET;
    if (defined('LIBXML_NO_XXE')) {
        $optionen |= LIBXML_NO_XXE;
    }

    $vorher = libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $ok = $dom->loadXML($xml, $optionen);
    libxml_use_internal_errors($vorher);

    if (!$ok) {
        throw new InvalidArgumentException('XML ungültig.');
    }
    if ($dom->doctype !== null) {
        throw new InvalidArgumentException('Dokumente mit DTD werden nicht angenommen.');
    }
    return $dom;
}

Secondo il manuale PHP, un valore di ritorno null nel loader blocca il caricamento della risorsa esterna. OWASP segnala che non basta passare null direttamente a libxml_set_external_entity_loader(); serve una funzione che restituisca null. Per SimpleXML e XMLReader valgono le stesse opzioni. Dove possibile, OWASP raccomanda di rifiutare del tutto le DTD.

Consiglio

Se può determinare lei stessa il formato dei dati, per lo scambio tra sistemi propri JSON è di solito la scelta più semplice. Per formati esterni come SAML o file Office, XML resta inevitabile.

Trovare gli ingressi XML nascosti

Molte applicazioni elaborano più XML di quanto pensino i loro sviluppatori. Scorra questo elenco:

  1. Interfacce: client e server SOAP, XML-RPC, servizi di pagamento e spedizione con risposte XML.
  2. Accesso: il single sign-on con SAML si basa su documenti XML provenienti dall'esterno.
  3. Importazioni: dati di prodotto, feed, sitemap, formati di catalogo.
  4. Upload: le immagini SVG sono XML; i formati Office come DOCX e XLSX sono composti internamente da file XML.
  5. Librerie: pacchetti Composer per documenti, tabelle o SAML che usano libxml internamente.

Nel codice aiutano due ricerche:

grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"

Le occorrenze in vendor/ non sono motivo di panico, ma un'occasione per verificare versione e avvisi di sicurezza della libreria.

Con gli upload SVG l'XXE è solo uno dei vari rischi, perché i file SVG possono contenere anche script. Se la sua applicazione non ha assolutamente bisogno del formato, non lo ammetta affatto per gli upload. Altrimenti elabori gli SVG solo con lo schema sicuro sopra e fornisca i file in modo che il browser non esegua gli script contenuti.

Faccia attenzione anche ai messaggi di errore. Se un'applicazione restituisce senza filtro agli utenti i messaggi di libxml, vi possono comparire parti del contenuto elaborato. Gli errori appartengono al log, l'utente riceve un messaggio generico; nell'esempio sopra libxml_use_internal_errors(true) fa sì che gli avvisi non finiscano nell'output. Limiti inoltre la dimensione dei documenti XML ricevuti, prima che il parser li veda.

Sistemi legacy: quando manca la protezione predefinita

La protezione offerta dal comportamento predefinito dipende dalla versione di libxml con cui PHP è stato compilato. Quale versione giri lo mostra php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' da riga di comando o l'output di phpinfo() in un ambiente protetto. Verifichi il valore per ogni versione di PHP sul server, perché CLI e server web possono usare build diverse. Le immagini container portano con sé una libxml propria; lì si verifica nel container in esecuzione, non sull'host.

Sono particolarmente a rischio i sistemi che girano comunque su vecchie versioni di PHP. Per PHP 8.1 e precedenti, dopo PHP2Shell non esistono più aggiornamenti ufficiali del progetto PHP; che cosa significhi lo descrive PHP 8.1 e precedenti: che cosa vale ora. Un sistema legacy con libxml obsoleta e vulnerabilità aperte nell'interprete andrebbe non solo messo in sicurezza nel codice, ma migrato. I rischi affini nell'elaborazione di dati esterni li descrive Deserializzazione insicura in PHP, le chiamate a destinazioni interne Prevenire il Server-Side Request Forgery in PHP.

Come aiuta concretamente PHP SOS?

  • Troviamo tutti i punti in cui la sua applicazione e le sue librerie elaborano XML da fonti esterne.
  • Rimuoviamo le opzioni rischiose del parser e inseriamo una funzione di caricamento centrale e messa in sicurezza.
  • Verifichiamo le versioni di libxml e PHP su tutti i server e pianifichiamo la migrazione per i sistemi legacy.
  • Testiamo le modifiche con documenti di prova innocui e documentiamo il risultato.

La verifica del software proprio è descritta nel servizio audit di sicurezza e penetration test.

Domande frequenti

Le applicazioni PHP sono protette automaticamente dall'XXE da PHP 8 in poi?

Nel caso standard in larga misura sì, perché libxml dalla versione 2.9.0 non sostituisce più le entità. La protezione viene meno però non appena il codice imposta opzioni come LIBXML_NOENT, LIBXML_DTDLOAD o LIBXML_DTDVALID, oppure se PHP è compilato con una versione molto vecchia di libxml.

Devo ancora richiamare libxml_disable_entity_loader?

No. La funzione è obsoleta da PHP 8.0, perché il caricamento di entità esterne è disattivato per impostazione predefinita. Per una protezione aggiuntiva il manuale PHP raccomanda libxml_set_external_entity_loader e, da PHP 8.4 con una libxml adatta, la costante LIBXML_NO_XXE.

Dove la mia applicazione elabora XML, in realtà?

Spesso in punti inattesi: interfacce SOAP, single sign-on con SAML, feed RSS, sitemap, importazioni di prodotti, upload SVG e file Office, che internamente sono composti da XML. Anche le librerie possono elaborare XML senza che sia visibile nel proprio codice.

Che cos'è l'attacco Billion Laughs?

Un documento con entità annidate che nella risoluzione cresce enormemente e consuma memoria e tempo di calcolo. Appartiene alla stessa famiglia dell'XXE. Offrono protezione la disattivazione della sostituzione delle entità e il rifiuto dei documenti con una DTD propria.

Fonti

Contatto immediato

Ogni ora conta. Ci scriva adesso.

Descriva brevemente sistema, versione di PHP e quanto osservato. Ci faremo sentire, chiariremo la situazione e lavoreremo da remoto fino alla chiusura e alla documentazione dell'incidente.

Aiuto immediato