# Предотвратяване на XML External Entities (XXE) в PHP

> Предотвратяване на XXE в PHP: какво важи от libxml 2.9 и PHP 8.0, кои флагове са рискови, LIBXML_NO_XXE от PHP 8.4 и как да откриете входовете с XML.

Източник: https://phpsos.de/bg/blog/xxe-in-php/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** 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
<?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.

В кода помагат две търсения:

```bash
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 и по-стари: какво важи сега](https://phpsos.de/bg/php2shell/php-8-1-und-aelter/). Стара система с остаряла libxml и отворени уязвимости в интерпретатора трябва не само да бъде защитена в кода, а да бъде мигрирана. Сродни рискове при обработката на чужди данни описва [Несигурна десериализация в PHP](https://phpsos.de/bg/ratgeber/deserialisierung-php/), а извикванията на вътрешни цели [Предотвратяване на Server-Side Request Forgery в PHP](https://phpsos.de/bg/blog/ssrf-in-php/).

## Как помага PHP SOS конкретно?

- Намираме всички места, където Вашето приложение и библиотеките му обработват XML от чужди източници.
- Премахваме рисковите опции на парсера и въвеждаме централна, защитена функция за зареждане.
- Проверяваме версиите на libxml и PHP на всички сървъри и при стари системи планираме миграцията.
- Тестваме промените с безвредни тестови документи и документираме резултата.

Проверката на собствен софтуер описва услугата [Одит на сигурността и тест за проникване](https://phpsos.de/bg/leistungen/sicherheitsaudit-penetrationstest/).


## Често задавани въпроси

**Защитени ли са 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: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [Наръчник на PHP: константи на libxml](https://www.php.net/manual/de/libxml.constants.php)
- [OWASP XML External Entity Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html)
