Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

Evitar XML External Entities (XXE) em PHP

Como os programadores processam XML em PHP com segurança: comportamento por defeito da libxml, opções arriscadas como LIBXML_NOENT, proteção com entity loader e LIBXML_NO_XXE e entradas XML ocultas típicas.

Atualizado em 6 min de leituraDesenvolvimento seguro

Em resumo: Evita o XXE em PHP carregando XML de fontes externas sem as opções LIBXML_NOENT, LIBXML_DTDLOAD e LIBXML_DTDVALID, rejeitando documentos com DTD própria e definindo um entity loader que recuse recursos externos. Desde a libxml 2.9.0, o PHP deixou de substituir entities por defeito, razão pela qual libxml_disable_entity_loader() está obsoleta desde o PHP 8.0. A partir do PHP 8.4 com libxml 2.13, está também disponível LIBXML_NO_XXE.

Uma falha antiga que não desaparece

As XML External Entities, abreviadamente XXE, são para muitos programadores um tema arrumado. O PHP desativou por defeito o carregamento de entities externas com as versões modernas da libxml, e a antiga função de proteção foi marcada como obsoleta com o PHP 8.0. Mesmo assim, as falhas XXE continuam a aparecer nas auditorias.

A razão está quase sempre numa de três situações: alguém definiu uma opção para resolver um problema de importação e voltou assim a ativar a substituição de entities. Uma biblioteca fá-lo internamente. Ou a aplicação corre num sistema legado cujo PHP foi compilado com uma libxml muito antiga.

O XXE é perigoso porque o analisador de XML carrega conteúdos que no documento são apenas referenciados: ficheiros locais ou endereços na rede. As consequências possíveis são a leitura de ficheiros de configuração, pedidos a sistemas internos semelhantes ao SSRF e a sobrecarga do servidor.

O que vale desde a libxml 2.9 e o PHP 8.0

O manual do PHP descreve a situação assim: a partir da libxml 2.9.0, a substituição de entities está desativada por defeito. As entities externas já não precisam, por isso, de ser desligadas expressamente, a não ser que as referências internas a entities devam ser resolvidas com determinadas opções. Daqui resulta esta visão geral:

Opção Efeito Risco com XML externo
LIBXML_NOENT substitui entities segundo o manual, pode facilitar ataques XXE
LIBXML_DTDLOAD carrega o subconjunto externo ativa o carregamento de entities externas
LIBXML_DTDVALID valida contra a DTD pode facilitar ataques XXE
LIBXML_NONET proíbe acessos à rede durante o carregamento opção de proteção adicional
LIBXML_NO_XXE desativa entities externas na substituição protetora, a partir do PHP 8.4 e libxml 2.13.0
LIBXML_PARSEHUGE remove limites internos do analisador facilita a sobrecarga, só para dados de confiança

O nome enganador de LIBXML_NOENT é uma armadilha conhecida: soa a “sem entities”, mas faz o contrário.

Para os programadores isto significa: o risco não está em carregar XML em si, mas em cada ponto que altera deliberadamente o comportamento por defeito. Uma pesquisa por estas constantes no código é, por isso, o caminho mais rápido para uma primeira avaliação, antes de qualquer verificação dispendiosa.

Defender-se do XXE em PHP: um padrão para DOMDocument

O exemplo seguinte combina várias camadas de proteção. Define um entity loader que recusa qualquer recurso externo, carrega sem opções arriscadas e rejeita documentos com DTD própria:

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Recusar, por princípio, recursos externos
    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;
}

Segundo o manual do PHP, um valor de retorno null no loader bloqueia o carregamento do recurso externo. A OWASP adverte que não basta passar null diretamente a libxml_set_external_entity_loader(); é necessária uma função que devolva null. As mesmas opções valem para SimpleXML e XMLReader. Sempre que possível, a OWASP recomenda rejeitar por completo as DTD.

Dica

Se puder determinar o próprio formato dos dados, o JSON é normalmente a escolha mais simples para a troca entre sistemas próprios. Para formatos externos como SAML ou ficheiros Office, o XML continua a ser inevitável.

Encontrar entradas XML ocultas

Muitas aplicações processam mais XML do que os seus programadores pensam. Percorra esta lista:

  1. Interfaces: clientes e servidores SOAP, XML-RPC, serviços de pagamento e de expedição com respostas em XML.
  2. Início de sessão: o início de sessão único com SAML assenta em documentos XML que vêm do exterior.
  3. Importações: dados de produtos, feeds, sitemaps, formatos de catálogo.
  4. Uploads: as imagens SVG são XML; os formatos Office como DOCX e XLSX são compostos internamente por ficheiros XML.
  5. Bibliotecas: pacotes Composer para documentos, folhas de cálculo ou SAML que usam internamente a libxml.

No código, ajudam duas pesquisas:

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

As ocorrências em vendor/ não são motivo de pânico, mas são um motivo para verificar a versão e os avisos de segurança da biblioteca.

Nos uploads de SVG, o XXE é apenas um de vários riscos, pois os ficheiros SVG podem conter também scripts. Se a sua aplicação não precisa obrigatoriamente do formato, não o admita de todo nos uploads. Caso contrário, processe o SVG apenas com o padrão seguro acima e entregue os ficheiros de modo que o navegador não execute os scripts contidos.

Atenção também às mensagens de erro. Se uma aplicação devolver sem filtro as mensagens da libxml aos utilizadores, podem aí constar partes do conteúdo processado. Os erros pertencem ao registo; o utilizador recebe uma mensagem genérica; no exemplo acima, libxml_use_internal_errors(true) faz com que os avisos não vão parar à saída. Limite também o tamanho dos documentos XML recebidos antes de o analisador sequer os ver.

Sistemas legados: quando falta a proteção por defeito

A proteção pelo comportamento por defeito depende da versão da libxml com que o PHP foi compilado. A versão em uso é mostrada por php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' na linha de comandos ou pela saída de phpinfo() num ambiente protegido. Verifique o valor para cada versão do PHP no servidor, pois a CLI e o servidor web podem usar builds diferentes. As imagens de contentor trazem a sua própria libxml; aí, verifique no contentor em execução, e não no anfitrião.

Estão particularmente afetados os sistemas que já correm em versões antigas do PHP. Para o PHP 8.1 e anteriores, após o PHP2Shell já não há atualizações oficiais do projeto PHP; o que isso significa é descrito em PHP 8.1 e anteriores: o que se aplica agora. Um sistema legado com libxml desatualizada e vulnerabilidades abertas no interpretador não deve ser apenas protegido no código, mas migrado. Riscos relacionados no processamento de dados externos são descritos em Desserialização insegura em PHP: unserialize e segurança, e as consultas a destinos internos em Evitar Server-Side Request Forgery (SSRF) em PHP.

Como ajuda concretamente a PHP SOS?

  • Encontramos todos os pontos em que a sua aplicação e as suas bibliotecas processam XML de fontes externas.
  • Removemos as opções de analisador arriscadas e montamos uma função de carregamento central e protegida.
  • Verificamos as versões da libxml e do PHP em todos os servidores e, nos sistemas legados, planeamos a migração.
  • Testamos as alterações com documentos de teste inofensivos e documentamos o resultado.

A verificação de software próprio é descrita no serviço Auditoria de segurança e teste de intrusão.

Perguntas frequentes

As aplicações PHP estão automaticamente protegidas contra XXE desde o PHP 8?

No caso normal, em grande medida, porque a libxml deixou de substituir entities a partir da versão 2.9.0. Mas a proteção perde-se assim que o código define opções como LIBXML_NOENT, LIBXML_DTDLOAD ou LIBXML_DTDVALID, ou se o PHP estiver compilado com uma versão muito antiga da libxml.

Devo continuar a chamar libxml_disable_entity_loader?

Não. A função está obsoleta desde o PHP 8.0, porque o carregamento de entities externas está desativado por defeito. Para proteção adicional, o manual do PHP recomenda libxml_set_external_entity_loader e, a partir do PHP 8.4 com a libxml adequada, a constante LIBXML_NO_XXE.

Onde é que a minha aplicação processa XML?

Muitas vezes em locais inesperados: interfaces SOAP, início de sessão único com SAML, feeds RSS, sitemaps, importações de produtos, uploads de SVG e ficheiros Office, que internamente são compostos por XML. Também as bibliotecas podem processar XML sem que isso seja visível no código próprio.

O que é o ataque Billion Laughs?

Um documento com entities aninhadas que cresce enormemente ao ser resolvido e consome memória e tempo de cálculo. Pertence à mesma família do XXE. Oferecem proteção a desativação da substituição de entities e a rejeição de documentos com DTD própria.

Fontes

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata