libxml_set_external_entity_loader()
Aprende a usar libxml_set_external_entity_loader() en PHP para controlar la carga de entidades externas XML y protegerte contra ataques XXE.
La función libxml_set_external_entity_loader() registra un callback personalizado que los analizadores de PHP basados en libxml (DOMDocument, SimpleXML, XMLReader) invocan cada vez que un documento XML intenta cargar una entidad externa — un archivo, URL o DTD referenciado desde el marcado. Al controlar ese callback, tú decides qué recursos externos pueden cargarse y cuáles se bloquean, lo que constituye la forma estándar y preparada para el futuro de defenderse contra los ataques XML External Entity (XXE). Esta página explica qué son las entidades externas, por qué son peligrosas, la firma de la función en PHP 8 y cómo escribir un cargador seguro con ejemplos funcionales.
¿Qué es una Entidad Externa (y por qué XXE es Peligroso)?
Una entidad externa es un marcador de posición declarado en una definición de tipo de documento (DTD) que se resuelve en contenido proveniente de fuera del documento XML. Por ejemplo:
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY secret SYSTEM "file:///etc/passwd">
]>
<data>&secret;</data>Cuando un analizador expande &secret;, lee /etc/passwd e inyecta el contenido del archivo en el documento. Un atacante que controla la entrada XML puede, por tanto, leer archivos locales, acceder a endpoints de red internos (SSRF) o desencadenar una denegación de servicio mediante expansión "billion laughs". Bloquear — o incluir en una lista blanca estricta — la carga de entidades externas cierra esta vulnerabilidad.
¿Qué es la función libxml_set_external_entity_loader()?
libxml_set_external_entity_loader() es una función PHP incorporada que registra un único callback global. Desde el momento en que se establece, cada solicitud de entidad externa proveniente de cualquier analizador libxml pasa primero por tu callback. Devolver null desde el callback indica a libxml que omita la entidad; devolver el contenido de la entidad (como cadena de texto o recurso abierto) permite su carga. Se introdujo en PHP 5.1.0 y sigue siendo el enfoque recomendado porque la antigua función libxml_disable_entity_loader() está obsoleta en PHP 8.0 y es en gran medida innecesaria en PHP moderno (las entidades externas no se cargan por defecto desde libxml 2.9 / PHP 8.0).
Sintaxis
libxml_set_external_entity_loader(?callable $resolver_function): voidPasar null elimina un cargador previamente registrado y restaura el comportamiento predeterminado.
Parámetros
| Parámetro | Descripción |
|---|---|
resolver_function | Un callable, o null para restablecer. El callback recibe tres argumentos y debe devolver el contenido de la entidad (un string), un resource abierto, o null para bloquear la carga. |
La firma del callback en PHP 8.0+ es:
function (?string $public_id, ?string $system_id, array $context): string|resource|null$public_id— el identificador público de la entidad (frecuentementenull).$system_id— el URI/ruta al que apunta la entidad (p. ej.file:///etc/passwd).$context— un array con claves comodirectory,intSubName,extSubURIyextSubSystemque describen el contexto de análisis.
Valor de Retorno
libxml_set_external_entity_loader() en sí devuelve void (devolvía true antes de PHP 8.0).
Cómo Usar libxml_set_external_entity_loader()
Define un callback, regístralo y luego analiza. La política segura más sencilla es bloquear todo devolviendo siempre 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";
?>La referencia &secret; se resuelve en una cadena vacía porque nuestro cargador devolvió null, por lo que /etc/passwd nunca se lee.
Inclusión en Lista Blanca de Fuentes de Confianza
Si tu aplicación necesita legítimamente algunas entidades externas (por ejemplo, un DTD local que viene con tu código), permite únicamente las rutas en las que confías y rechaza todo lo demás:
<?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";
?>Aquí, cualquier systemId que no se resuelva en un archivo real dentro de schemas/ devuelve null y queda bloqueado, mientras que los archivos de esquema locales de confianza se cargan normalmente.
Nota: El cargador es global y se aplica a cada análisis libxml en la solicitud. Restablécelo con
libxml_set_external_entity_loader(null)cuando hayas terminado si otro código en la misma solicitud depende del comportamiento predeterminado.
Errores Comunes y Advertencias
- Devolver un tipo incorrecto. Devuelve un
string, unresourceabierto onull— devolverfalseotrueno es válido y puede lanzar unTypeErroren PHP 8. - Olvidar que es global. El último cargador registrado gana para toda la solicitud; las bibliotecas que establecen su propio cargador pueden anular el tuyo.
- Asumir que todavía necesitas
libxml_disable_entity_loader(). En PHP 8+, las entidades externas están desactivadas por defecto; usa esta función solo cuando necesites un control personalizado sobre la carga. - Confiar ciegamente en
$systemId. Valida siempre la ruta/URL antes de leerla, o reintroduces el agujero XXE/SSRF que intentabas cerrar.
Conclusión
libxml_set_external_entity_loader() te proporciona un único punto de control para cada entidad externa que un analizador libxml intente cargar, convirtiéndola en la defensa moderna y recomendada contra XXE y SSRF en el procesamiento XML de PHP. Bloquea todo devolviendo null, o incluye en la lista blanca solo los recursos locales en los que confías. Para el conjunto de herramientas libxml más amplio, consulta las funciones relacionadas a continuación.
Véase También
- PHP libxml — descripción general de la extensión libxml y sus constantes.
- libxml_disable_entity_loader() — la forma antigua y obsoleta de desactivar la carga de entidades.
- PHP XML DOM — análisis de XML con
DOMDocument.