W3docs

libxml_set_external_entity_loader()

Aprenda sobre a função libxml_set_external_entity_loader() no PHP para controlar o carregamento de entidades externas em XML e prevenir ataques XXE.

A função libxml_set_external_entity_loader() registra um callback personalizado que os parsers do PHP baseados em libxml (DOMDocument, SimpleXML, XMLReader) chamam sempre que um documento XML tenta carregar uma entidade externa — um arquivo, URL ou DTD referenciado de dentro da marcação. Ao controlar esse callback, você decide quais recursos externos podem ser carregados e quais são bloqueados, o que é a forma padrão e moderna de se defender contra ataques de XML External Entity (XXE). Esta página explica o que são entidades externas, por que elas são perigosas, a assinatura da função no PHP 8 e como escrever um loader seguro com exemplos funcionais.

O Que É uma Entidade Externa (e Por Que XXE É Perigoso)?

Uma entidade externa é um marcador declarado em uma definição de tipo de documento (DTD) que resolve conteúdo de fora do documento XML. Por exemplo:

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

Quando um parser expande &secret;, ele lê /etc/passwd e injeta o conteúdo do arquivo no documento. Um atacante que controla a entrada XML pode, portanto, ler arquivos locais, acessar endpoints de rede interna (SSRF) ou provocar expansão "billion laughs" causando negação de serviço. Bloquear — ou restringir com lista de permissões — o carregamento de entidades externas resolve esse problema.

O Que É a Função libxml_set_external_entity_loader()?

libxml_set_external_entity_loader() é uma função PHP embutida que registra um único callback global. A partir do momento em que é definido, toda solicitação de entidade externa de qualquer parser libxml passa primeiro pelo seu callback. Retornar null do callback informa ao libxml para ignorar a entidade; retornar o conteúdo da entidade (como string ou recurso aberto) permite o carregamento. Foi introduzida no PHP 5.1.0 e continua sendo a abordagem recomendada, pois a função mais antiga libxml_disable_entity_loader() está obsoleta no PHP 8.0 e é amplamente desnecessária no PHP moderno (entidades externas não são carregadas por padrão desde o libxml 2.9 / PHP 8.0).

Sintaxe

libxml_set_external_entity_loader(?callable $resolver_function): void

Passar null remove um loader previamente registrado e restaura o comportamento padrão.

Parâmetros

ParâmetroDescrição
resolver_functionUm callable, ou null para redefinir. O callback recebe três argumentos e deve retornar o conteúdo da entidade (uma string), um resource aberto ou null para bloquear o carregamento.

A assinatura do callback no PHP 8.0+ é:

function (?string $public_id, ?string $system_id, array $context): string|resource|null
  • $public_id — o identificador público da entidade (frequentemente null).
  • $system_id — o URI/caminho para o qual a entidade aponta (ex.: file:///etc/passwd).
  • $context — um array com chaves como directory, intSubName, extSubURI e extSubSystem descrevendo o contexto de análise.

Valor de Retorno

A própria libxml_set_external_entity_loader() retorna void (retornava true antes do PHP 8.0).

Como Usar libxml_set_external_entity_loader()

Defina um callback, registre-o e então faça a análise. A política segura mais simples é bloquear tudo retornando 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";
?>

A referência &secret; resolve para uma string vazia porque nosso loader retornou null, então /etc/passwd nunca é lido.

Lista de Permissões de Fontes Confiáveis

Se sua aplicação realmente precisa de algumas entidades externas (por exemplo, um DTD local que acompanha seu código), permita apenas os caminhos em que você confia e rejeite todo o 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";
?>

Aqui, qualquer systemId que não resolva para um arquivo real dentro de schemas/ retorna null e é bloqueado, enquanto arquivos de esquema locais confiáveis carregam normalmente.

Nota: O loader é global e se aplica a todas as análises libxml na requisição. Redefina-o com libxml_set_external_entity_loader(null) quando terminar se outro código na mesma requisição depende do comportamento padrão.

Erros Comuns e Armadilhas

  • Retornar o tipo errado. Retorne uma string, um resource aberto ou null — retornar false ou true não é válido e pode gerar um TypeError no PHP 8.
  • Esquecer que é global. O último loader registrado prevalece para toda a requisição; bibliotecas que definem seu próprio loader podem substituir o seu.
  • Assumir que ainda precisa de libxml_disable_entity_loader(). No PHP 8+, entidades externas estão desativadas por padrão; use esta função apenas quando precisar de controle personalizado sobre o carregamento.
  • Confiar em $systemId cegamente. Sempre valide o caminho/URL antes de lê-lo, ou você reintroduz a vulnerabilidade XXE/SSRF que estava tentando fechar.

Conclusão

libxml_set_external_entity_loader() oferece um único ponto de controle para toda entidade externa que um parser libxml tenta carregar, tornando-a a defesa moderna e recomendada contra XXE e SSRF no processamento XML em PHP. Bloqueie tudo retornando null, ou crie uma lista de permissões apenas para os recursos locais em que você confia. Para o conjunto de ferramentas libxml mais amplo, veja as funções relacionadas abaixo.

Veja Também

Prática

Prática
Qual é o principal objetivo da função PHP libxml_set_external_entity_loader()?
Qual é o principal objetivo da função PHP libxml_set_external_entity_loader()?
Was this page helpful?