W3docs

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

Параметры

ПараметрТипОписание
$disablebooltrue отключает загрузку внешних сущностей; 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.

Практика

Практика
Какова функция метода libxml_disable_entity_loader() в PHP?
Какова функция метода libxml_disable_entity_loader() в PHP?
Was this page helpful?