Текуща ситуация: PHP2Shell. Две критични уязвимости в PHP (CVSS 9,2) позволяват злонамерен код без вход. Корекции от 21.10.2026 г.Какво да направите сега →

Предотвратяване на XML External Entities (XXE) в PHP

Как разработчиците обработват XML сигурно в PHP: стандартно поведение на libxml, рискови опции като LIBXML_NOENT, защита с Entity Loader и LIBXML_NO_XXE и типични скрити входове за XML.

Актуалност 5 мин. четенеСигурна разработка

Накратко: XXE в PHP предотвратявате, като зареждате XML от чужди източници без опциите LIBXML_NOENT, LIBXML_DTDLOAD и LIBXML_DTDVALID, отхвърляте документи със собствена DTD и задавате Entity Loader, който отказва външни ресурси. От libxml 2.9.0 PHP вече не замества entities по подразбиране, затова libxml_disable_entity_loader() е остаряла от PHP 8.0. От PHP 8.4 с libxml 2.13 допълнително е налична LIBXML_NO_XXE.

Стара уязвимост, която не изчезва

XML External Entities, накратко XXE, за много разработчици са приключена тема. PHP изключи зареждането на външни entities по подразбиране при съвременни версии на libxml, а старата защитна функция беше маркирана като остаряла с PHP 8.0. Въпреки това XXE уязвимости продължават да се появяват при проверки.

Причината почти винаги е в една от три ситуации: някой е задал опция, за да реши проблем с импорт, и така е включил отново замяната на entities. Библиотека го прави вътрешно. Или приложението работи на стара система, чийто PHP е изграден с много стара libxml.

XXE е опасна, защото XML парсерът при това зарежда съдържание, което в документа е само реферирано: локални файлове или адреси в мрежата. Възможни последици са прочитане на конфигурационни файлове, заявки към вътрешни системи подобно на SSRF и претоварване на сървъра.

Какво важи от libxml 2.9 и PHP 8.0

Наръчникът на PHP описва положението така: от libxml 2.9.0 замяната на entities е деактивирана по подразбиране. Външните entities затова вече не се налага да се изключват отделно, освен ако вътрешни препратки към entities трябва да се разрешават с определени опции. От това следва този преглед:

Опция Действие Риск при чужд XML
LIBXML_NOENT замества entities според наръчника може да улесни XXE атаки
LIBXML_DTDLOAD зарежда външното подмножество активира зареждането на външни entities
LIBXML_DTDVALID валидира спрямо DTD може да улесни XXE атаки
LIBXML_NONET забранява мрежови достъпи при зареждане допълнителна защитна опция
LIBXML_NO_XXE изключва външните entities при замяната защитна, от PHP 8.4 и libxml 2.13.0
LIBXML_PARSEHUGE премахва вътрешните ограничения на парсера улеснява претоварване, само за надеждни данни

Подвеждащото име на LIBXML_NOENT е позната клопка: звучи като „без entities“, но има обратен ефект.

За разработчиците това означава: рисково не е самото зареждане на XML, а всяко място, което съзнателно променя стандартното поведение. Търсенето на тези константи в кода затова е най-бързият път към първа оценка, още преди всяка трудоемка проверка.

Сигурна защита от XXE в PHP: образец за DOMDocument

Следващият пример комбинира няколко защитни слоя. Той задава Entity Loader, който отказва всеки външен ресурс, зарежда без рискови опции и отхвърля документи със собствена DTD:

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // По принцип отказвайте външните ресурси
    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;
}

Според наръчника на PHP върната стойност null в Loader-а блокира зареждането на външния ресурс. OWASP посочва, че не е достатъчно да се предаде null директно на libxml_set_external_entity_loader(); нужна е функция, която връща null. За SimpleXML и XMLReader важат същите опции. Където е възможно, OWASP препоръчва DTD да се отхвърлят изцяло.

Съвет

Ако можете сами да определите формата на данните, JSON за обмен между собствени системи обикновено е по-простият избор. За чужди формати като SAML или офис файлове XML остава неизбежен.

Откриване на скритите входове за XML

Много приложения обработват повече XML, отколкото предполагат разработчиците им. Преминете през този списък:

  1. Интерфейси: SOAP клиенти и сървъри, XML-RPC, платежни и куриерски услуги с XML отговори.
  2. Вход в системата: Single Sign-on със SAML се основава на XML документи, които идват отвън.
  3. Импорти: продуктови данни, емисии (feeds), sitemap-ове, формати на каталози.
  4. Качвания: SVG изображенията са XML; офис формати като DOCX и XLSX вътрешно се състоят от XML файлове.
  5. Библиотеки: Composer пакети за документи, таблици или SAML, които вътрешно ползват libxml.

В кода помагат две търсения:

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

Намерени места във vendor/ не са повод за паника, но са причина да проверите версията и съобщенията за сигурност на библиотеката.

При SVG качванията XXE е само един от няколко риска, защото SVG файловете могат да съдържат и скриптове. Ако Вашето приложение не се нуждае непременно от формата, изобщо не го допускайте за качвания. В противен случай обработвайте SVG само със сигурния образец по-горе и доставяйте файловете така, че браузърът да не изпълнява съдържащите се скриптове.

Обърнете внимание и на съобщенията за грешка. Ако едно приложение връща съобщенията на libxml нефилтрирани на потребителите, в тях могат да се съдържат части от обработваното съдържание. Грешките принадлежат в журнала, потребителят получава общо съобщение; в примера по-горе libxml_use_internal_errors(true) гарантира, че предупрежденията не попадат в изхода. Ограничете освен това размера на получаваните XML документи, преди парсерът изобщо да ги види.

Стари системи: кога липсва стандартната защита

Защитата чрез стандартното поведение зависи от версията на libxml, с която е изграден PHP. Коя версия работи, показва php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' в командния ред или изходът на phpinfo() в защитена среда. Проверете стойността за всяка версия на PHP на сървъра, защото CLI и уеб сървърът могат да ползват различни изграждания. Образите на контейнери носят собствена libxml; там проверявайте в работещия контейнер, а не на хоста.

Особено засегнати са системите, които така или иначе работят на стари версии на PHP. За PHP 8.1 и по-стари след PHP2Shell няма повече официални актуализации от проекта PHP; какво означава това, описва PHP 8.1 и по-стари: какво важи сега. Стара система с остаряла libxml и отворени уязвимости в интерпретатора трябва не само да бъде защитена в кода, а да бъде мигрирана. Сродни рискове при обработката на чужди данни описва Несигурна десериализация в PHP, а извикванията на вътрешни цели Предотвратяване на Server-Side Request Forgery в PHP.

Как помага PHP SOS конкретно?

  • Намираме всички места, където Вашето приложение и библиотеките му обработват XML от чужди източници.
  • Премахваме рисковите опции на парсера и въвеждаме централна, защитена функция за зареждане.
  • Проверяваме версиите на libxml и PHP на всички сървъри и при стари системи планираме миграцията.
  • Тестваме промените с безвредни тестови документи и документираме резултата.

Проверката на собствен софтуер описва услугата Одит на сигурността и тест за проникване.

Често задавани въпроси

Защитени ли са PHP приложенията автоматично от XXE от PHP 8 насам?

В стандартния случай до голяма степен, защото libxml от версия 2.9.0 вече не замества entities. Защитата отпада обаче, щом кодът зададе опции като LIBXML_NOENT, LIBXML_DTDLOAD или LIBXML_DTDVALID или PHP е изграден с много стара версия на libxml.

Трябва ли още да извиквам libxml_disable_entity_loader?

Не. Функцията е остаряла от PHP 8.0, защото зареждането на външни entities е изключено по подразбиране. За допълнителна защита наръчникът на PHP препоръчва libxml_set_external_entity_loader, а от PHP 8.4 с подходяща libxml и константата LIBXML_NO_XXE.

Къде изобщо моето приложение обработва XML?

Често на неочаквани места: SOAP интерфейси, Single Sign-on със SAML, RSS емисии, sitemap-ове, импорти на продукти, SVG качвания и офис файлове, които вътрешно се състоят от XML. И библиотеките могат да обработват XML, без това да е видимо в собствения код.

Какво е атаката Billion Laughs?

Документ с вложени entities, който при разрешаване нараства огромно и изразходва памет и изчислително време. Тя принадлежи към същото семейство като XXE. Защита дават изключването на замяната на entities и отхвърлянето на документи със собствена DTD.

Източници

Незабавен контакт

Всеки час има значение. Пишете ни сега.

Опишете накратко системата, версията на PHP и какво сте забелязали. Ние се свързваме с Вас, уточняваме ситуацията и работим отдалечено, докато инцидентът бъде приключен и документиран.

Незабавна помощ