W3docs

libxml_set_external_entity_loader()

Scopri la funzione libxml_set_external_entity_loader() in PHP per controllare il caricamento di entità esterne XML e difendersi dagli attacchi XXE.

La funzione libxml_set_external_entity_loader() registra un callback personalizzato che i parser PHP basati su libxml (DOMDocument, SimpleXML, XMLReader) invocano ogni volta che un documento XML cerca di caricare un'entità esterna — un file, un URL o un DTD referenziato dall'interno del markup. Controllando quel callback si decide quali risorse esterne possono essere caricate e quali vengono bloccate: questo è il modo standard e a prova di futuro per difendersi dagli attacchi XML External Entity (XXE). Questa pagina spiega cosa sono le entità esterne, perché sono pericolose, la firma della funzione su PHP 8 e come scrivere un loader sicuro con esempi funzionanti.

Che cos'è un'entità esterna (e perché XXE è pericoloso)?

Un'entità esterna è un segnaposto dichiarato in una definizione del tipo di documento (DTD) che si risolve in contenuto proveniente dall'esterno del documento XML. Ad esempio:

<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>

Quando un parser espande &secret;, legge /etc/passwd e inietta il contenuto del file nel documento. Un attaccante che controlla l'input XML può quindi leggere file locali, raggiungere endpoint di rete interni (SSRF) o innescare un'espansione denial-of-service "billion laughs". Bloccare — o applicare una whitelist rigorosa — al caricamento di entità esterne chiude questa vulnerabilità.

Che cos'è la funzione libxml_set_external_entity_loader()?

libxml_set_external_entity_loader() è una funzione PHP integrata che registra un unico callback globale. Dal momento in cui viene impostata, ogni richiesta di entità esterna da qualsiasi parser libxml passa prima attraverso il callback. Restituire null dal callback indica a libxml di ignorare l'entità; restituire il contenuto dell'entità (come stringa o risorsa aperta) ne consente il caricamento. È stata introdotta in PHP 5.1.0 e rimane l'approccio consigliato perché la precedente libxml_disable_entity_loader() è deprecata in PHP 8.0 ed è in gran parte superflua nelle versioni moderne di PHP (le entità esterne non vengono caricate per impostazione predefinita da libxml 2.9 / PHP 8.0).

Sintassi

libxml_set_external_entity_loader(?callable $resolver_function): void

Passare null rimuove un loader precedentemente registrato e ripristina il comportamento predefinito.

Parametri

ParametroDescrizione
resolver_functionUn callable, oppure null per azzerare. Il callback riceve tre argomenti e deve restituire il contenuto dell'entità (una string), una resource aperta, o null per bloccare il caricamento.

La firma del callback su PHP 8.0+ è:

function (?string $public_id, ?string $system_id, array $context): string|resource|null
  • $public_id — l'identificatore pubblico dell'entità (spesso null).
  • $system_id — l'URI/percorso a cui punta l'entità (es. file:///etc/passwd).
  • $context — un array con chiavi come directory, intSubName, extSubURI e extSubSystem che descrivono il contesto di parsing.

Valore restituito

libxml_set_external_entity_loader() restituisce void (restituiva true prima di PHP 8.0).

Come usare libxml_set_external_entity_loader()

Definire un callback, registrarlo, poi eseguire il parsing. La policy sicura più semplice è bloccare tutto restituendo sempre null:

<?php
// Block every external entity request.
libxml_set_external_entity_loader(
  static fn (?string $publicId, ?string $systemId, array $context): ?string => null
);

$xml = <<<'XML'
<?xml version="1.0"?>
<!DOCTYPE data [
  <!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>
XML;

$doc = new DOMDocument();
$doc->loadXML($xml);

// The entity was blocked, so it expands to nothing.
echo "Loaded value: [" . $doc->documentElement->textContent . "]\n";
echo "Done without reading any external file.\n";
?>

Il riferimento &secret; si risolve in una stringa vuota perché il nostro loader ha restituito null, quindi /etc/passwd non viene mai letto.

Whitelist delle sorgenti attendibili

Se la tua applicazione necessita legittimamente di alcune entità esterne (ad esempio un DTD locale distribuito con il codice), consenti solo i percorsi di cui ti fidi e rifiuta tutto il resto:

<?php
function trusted_entity_loader(?string $publicId, ?string $systemId, array $context)
{
  // Only allow files inside our own schema directory.
  $allowedDir = __DIR__ . '/schemas/';

  if ($systemId === null) {
    return null;
  }

  $path = str_starts_with($systemId, 'file://')
    ? substr($systemId, 7)
    : $systemId;

  $real = realpath($path);
  if ($real !== false && str_starts_with($real, realpath($allowedDir))) {
    return file_get_contents($real); // Trusted: load it.
  }

  return null; // Everything else is blocked.
}

libxml_set_external_entity_loader('trusted_entity_loader');
echo "Loader registered: only ./schemas/ files may be resolved.\n";
?>

Qui qualsiasi systemId che non si risolve in un file reale all'interno di schemas/ restituisce null e viene bloccato, mentre i file di schema locali attendibili vengono caricati normalmente.

Nota: Il loader è globale e si applica a ogni parsing libxml nella richiesta. Reimpostalo con libxml_set_external_entity_loader(null) al termine se altro codice nella stessa richiesta si affida al comportamento predefinito.

Errori comuni e insidie

  • Restituire il tipo sbagliato. Restituisci una string, una resource aperta o null — restituire false o true non è valido e può generare un TypeError su PHP 8.
  • Dimenticare che è globale. L'ultimo loader registrato vince per l'intera richiesta; le librerie che impostano il proprio loader possono sovrascrivere il tuo.
  • Supporre di aver ancora bisogno di libxml_disable_entity_loader(). Su PHP 8+ le entità esterne sono disabilitate per impostazione predefinita; usa questa funzione solo quando hai bisogno di un controllo personalizzato sul caricamento.
  • Fidarsi ciecamente di $systemId. Valida sempre il percorso/URL prima di leggerlo, altrimenti reintroduci la vulnerabilità XXE/SSRF che stavi cercando di chiudere.

Conclusione

libxml_set_external_entity_loader() fornisce un unico punto di controllo per ogni entità esterna che un parser libxml tenta di caricare, rendendola la difesa moderna e consigliata contro XXE e SSRF nell'elaborazione XML in PHP. Blocca tutto restituendo null, oppure aggiungi alla whitelist solo le risorse locali di cui ti fidi. Per la panoramica completa del toolkit libxml, consulta le funzioni correlate di seguito.

Vedi anche

Esercitazione

Pratica
Qual è lo scopo principale della funzione PHP libxml_set_external_entity_loader()?
Qual è lo scopo principale della funzione PHP libxml_set_external_entity_loader()?
Was this page helpful?