libxml_disable_entity_loader()
Функция libxml_disable_entity_loader() в PHP: защита от XXE-атак, история устаревания и безопасные альтернативы в современном PHP.
Функция PHP libxml_disable_entity_loader() использовалась для включения и отключения загрузки внешних сущностей для всех XML-документов, обрабатываемых расширением libxml. На этой странице описывается, что делала данная функция, от какой атаки типа XML External Entity (XXE) она защищала, почему была удалена и как писать безопасный код парсинга XML в современном PHP.
Важно: libxml_disable_entity_loader() была объявлена устаревшей в PHP 8.0 и удалена в PHP 8.1. При использовании PHP 8.1 или новее её вызов вызывает фатальную ошибку. Вместо неё используйте безопасные альтернативы, описанные ниже.
Что делала libxml_disable_entity_loader()
libxml_disable_entity_loader() — встроенная утилита PHP, которая переключала глобальный флаг внутри libxml — библиотеки на языке C, лежащей в основе DOMDocument и SimpleXML в PHP. При установленном флаге libxml отказывалась разрешать внешние сущности: ссылки внутри XML-документа, указывающие на внешние ресурсы — локальные файлы (file:///etc/passwd) или удалённые URL.
Блокировка такого разрешения была стандартным способом защиты от атак XML External Entity (XXE). При XXE-атаке злоумышленник отправляет XML-нагрузку, в DOCTYPE которой объявлена сущность, указывающая на чувствительный ресурс:
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>Если парсер разрешит &xxe;, содержимое /etc/passwd окажется в распознанном документе — и приложение может вернуть его в ответе, записать в лог или сохранить. Тот же приём позволяет проводить подделку запросов на стороне сервера (SSRF, при указании сущности на внутренний URL) и атаку типа «отказ в обслуживании» (бомба расширения сущностей «миллиард смехов»).
Синтаксис
libxml_disable_entity_loader(bool $disable = true): boolПараметры
| Параметр | Тип | Описание |
|---|---|---|
$disable | bool | true отключает загрузку внешних сущностей; false снова включает её. По умолчанию true. |
Возвращаемое значение
Возвращает предыдущее значение флага в виде boolean, что позволяло восстановить предшествующее состояние после единичного парсинга.
Устаревшее использование
В PHP 7.x и более ранних версиях защита парсинга выглядела следующим образом. Вызов обёрнут в function_exists(), чтобы тот же код продолжал работать на PHP 8.1+, где функция больше не существует:
<?php
// Disable external entities (deprecated in PHP 8.0, removed in 8.1).
if (function_exists('libxml_disable_entity_loader')) {
libxml_disable_entity_loader(true);
}
// Load an XML file into a DOMDocument object.
$doc = new DOMDocument();
if (!$doc->load('example.xml')) {
die('Failed to load XML file.');
}
?>Распространённым, более безопасным паттерном было сохранение предыдущего значения и его восстановление после парсинга, чтобы отключение сущностей не распространялось на другие операции парсинга в рамках запроса:
<?php
$previous = libxml_disable_entity_loader(true);
$doc = new DOMDocument();
$doc->loadXML($untrustedXml);
// Restore the global flag for the rest of the request.
libxml_disable_entity_loader($previous);
?>Безопасные альтернативы в современном PHP
В современном PHP произошли два изменения, которые сделали функцию ненужной:
- libxml 2.9+ отключает загрузку внешних сущностей по умолчанию. Начиная с этой версии (поставляемой с PHP уже много лет), внешние сущности DTD не разрешаются без явного указания. Именно поэтому функция стала избыточной и была удалена.
LIBXML_NONETдаёт поэтапный контроль. Вместо переключения глобального флага вы передаёте флаг конкретному вызову загрузки, что блокирует сетевой доступ в процессе парсинга:
<?php
$doc = new DOMDocument();
// Parse without ever touching the network — no SSRF, no remote entities.
$doc->load('example.xml', LIBXML_NONET);
?>Если необходимо поддерживать DTD, но при этом требуется усиленная защита, избегайте LIBXML_DTDLOAD / LIBXML_NOENT на недоверенных данных, так как эти флаги повторно включают то поведение, на которое опирается XXE. Самый безопасный вариант по умолчанию — просто не передавать их:
<?php
$doc = new DOMDocument();
// Default flags (0): no entity substitution, no DTD loading from untrusted XML.
$doc->loadXML($untrustedXml);
// SimpleXML follows the same secure default.
$xml = simplexml_load_string($untrustedXml);
?>Чтобы перехватывать ошибки парсинга вместо вывода необработанных предупреждений, комбинируйте загрузку с libxml_use_internal_errors() и считывайте их через libxml_get_errors().
Когда это может понадобиться?
libxml_disable_entity_loader() встречается только при чтении или сопровождении устаревшего кода PHP 7. Для любого нового кода:
- На PHP 8.1+ никаких специальных действий для защиты от внешних сущностей не требуется — безопасное поведение уже применяется по умолчанию. Добавьте
LIBXML_NONET, если также нужно блокировать сетевой доступ. - Переносите старый код? Замените
libxml_disable_entity_loader(true)на флагLIBXML_NONETпри каждом вызове загрузки или оберните вызов вfunction_exists(), чтобы он не выполнялся на новых версиях PHP.
Заключение
libxml_disable_entity_loader() когда-то была основным средством защиты от XXE-атак, однако опиралась на ненадёжный глобальный флаг и была удалена начиная с PHP 8.1. Современный PHP безопасен по умолчанию благодаря libxml 2.9+, а LIBXML_NONET обеспечивает явный контроль на уровне каждого парсинга. Подробнее о стеке XML в PHP читайте в обзоре расширения libxml и разделе работа с XML DOM.