# Prevence XML External Entities (XXE) v PHP

> XXE v PHP: co platí od libxml 2.9 a PHP 8.0, které příznaky jsou riziková, LIBXML_NO_XXE od PHP 8.4 a jak najít vstupy XML ve vaší aplikaci.

Zdroj: https://phpsos.de/cs/blog/xxe-in-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručně:** XXE v PHP zabráníte tak, že XML z cizích zdrojů načítáte bez voleb `LIBXML_NOENT`, `LIBXML_DTDLOAD` a `LIBXML_DTDVALID`, odmítáte dokumenty s vlastní DTD a nastavíte entity loader, který externí zdroje odepře. Od libxml 2.9.0 PHP entity ve výchozím stavu nenahrazuje, proto je `libxml_disable_entity_loader()` od PHP 8.0 zastaralá. Od PHP 8.4 s libxml 2.13 je navíc k dispozici `LIBXML_NO_XXE`.

## Stará zranitelnost, která nemizí

XML External Entities, zkráceně XXE, považuje mnoho vývojářů za vyřešené téma. PHP s moderními verzemi libxml načítání externích entit ve výchozím stavu vypnulo a stará ochranná funkce byla s PHP 8.0 označena jako zastaralá. Přesto se zranitelnosti XXE při kontrolách dál objevují.

Důvod je téměř vždy jedna ze tří situací: někdo nastavil volbu k vyřešení problému s importem a tím znovu zapnul nahrazování entit. Knihovna to dělá interně. Nebo aplikace běží na starém systému, jehož PHP bylo sestaveno s velmi starou libxml.

XXE je nebezpečné, protože parser XML přitom dotahuje obsah, který je v dokumentu jen odkazován: lokální soubory nebo adresy v síti. Možnými důsledky jsou čtení konfiguračních souborů, požadavky na interní systémy podobně jako u SSRF a přetížení serveru.

## Co platí od libxml 2.9 a PHP 8.0

Příručka PHP popisuje situaci takto: od libxml 2.9.0 je nahrazování entit ve výchozím stavu vypnuto. Externí entity proto není nutné zvlášť vypínat, pokud se interní odkazy na entity nemají rozvíjet určitými volbami. Z toho vyplývá tento přehled:

| Volba | Účinek | Riziko u cizího XML |
|---|---|---|
| `LIBXML_NOENT` | nahrazuje entity | podle příručky může útoky XXE usnadnit |
| `LIBXML_DTDLOAD` | načítá externí podmnožinu | aktivuje načítání externích entit |
| `LIBXML_DTDVALID` | validuje proti DTD | může útoky XXE usnadnit |
| `LIBXML_NONET` | zakazuje síťové přístupy při načítání | ochranná doplňková volba |
| `LIBXML_NO_XXE` | vypíná externí entity při nahrazování | ochranná, od PHP 8.4 a libxml 2.13.0 |
| `LIBXML_PARSEHUGE` | ruší vnitřní limity parseru | usnadňuje přetížení, jen pro důvěryhodná data |

Zavádějící název `LIBXML_NOENT` je známá past: zní jako „žádné entity“, ale způsobuje opak.

Pro vývojáře z toho plyne: riziková není sama o sobě načítání XML, ale každé místo, které výchozí chování vědomě mění. Hledání těchto konstant v kódu je proto nejrychlejší cesta k první orientaci, ještě před jakoukoli náročnou kontrolou.

## Bezpečná obrana proti XXE v PHP: vzor pro DOMDocument

Následující příklad kombinuje několik ochranných vrstev. Nastaví entity loader, který odepře každý externí zdroj, načítá bez rizikových voleb a odmítá dokumenty s vlastní DTD:

```php
<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Externí zdroje zásadně odepřít
    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;
}
```

Podle příručky PHP blokuje návratová hodnota `null` v loaderu načtení externího zdroje. OWASP upozorňuje, že nestačí předat `null` přímo funkci `libxml_set_external_entity_loader()`; je třeba funkce, která `null` vrací. Pro SimpleXML a XMLReader platí stejné volby. Kdekoli je to možné, OWASP doporučuje DTD odmítat úplně.

> **Tip:**
> Pokud můžete formát dat určit sami, je JSON pro výměnu mezi vlastními systémy zpravidla jednodušší volbou. U cizích formátů jako SAML nebo soubory Office zůstává XML nevyhnutelné.

## Nalezení skrytých vstupů XML

Mnoho aplikací zpracovává víc XML, než si jejich vývojáři myslí. Projděte tento seznam:

1. **Rozhraní:** klienti a servery SOAP, XML-RPC, platební a dopravní služby s odpověďmi v XML.
2. **Přihlášení:** single sign-on přes SAML je založen na dokumentech XML, které přicházejí zvenčí.
3. **Importy:** data produktů, kanály, mapy webu, formáty katalogů.
4. **Uploady:** obrázky SVG jsou XML; formáty Office jako DOCX a XLSX se interně skládají ze souborů XML.
5. **Knihovny:** balíčky Composeru pro dokumenty, tabulky nebo SAML, které interně používají libxml.

V kódu pomohou dvě vyhledávání:

```bash
grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"
```

Nálezy ve `vendor/` nejsou důvodem k panice, ale podnětem ke kontrole verze a bezpečnostních hlášení knihovny.

U nahrávání SVG je XXE jen jedním z několika rizik, protože soubory SVG mohou obsahovat i skripty. Pokud váš program formát nutně nepotřebuje, nepovolujte ho pro uploady vůbec. Jinak SVG zpracovávejte jen výše uvedeným bezpečným vzorem a soubory poskytujte tak, aby prohlížeč obsažené skripty nespouštěl.

Věnujte pozornost také chybovým hlášením. Pokud aplikace vrací uživatelům hlášení libxml nefiltrovaně, mohou v nich být části zpracovávaného obsahu. Chyby patří do logu, uživatel dostane obecné hlášení; ve výše uvedeném příkladu zajišťuje `libxml_use_internal_errors(true)`, že se varování nedostanou do výstupu. Omezte navíc velikost přijatých dokumentů XML dříve, než je parser vůbec uvidí.

## Starší systémy: kdy výchozí ochrana chybí

Ochrana výchozím chováním závisí na verzi libxml, s níž bylo PHP sestaveno. Jaká verze běží, ukáže `php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;'` na příkazové řádce nebo výstup `phpinfo()` v chráněném prostředí. Hodnotu ověřte pro každou verzi PHP na serveru, protože CLI a webserver mohou používat různé buildy. Image kontejnerů přinášejí vlastní libxml; tam kontrolujte v běžícím kontejneru, ne na hostiteli.

Obzvlášť zasaženy jsou systémy, které tak jako tak běží na starých verzích PHP. Pro PHP 8.1 a starší už po PHP2Shell neexistují oficiální aktualizace projektu PHP; co to znamená, popisuje stránka [PHP 8.1 a starší: co platí nyní](https://phpsos.de/cs/php2shell/php-8-1-und-aelter/). Starý systém se zastaralou libxml a otevřenými zranitelnostmi interpretu by neměl být jen zabezpečen v kódu, ale zmigrován. Související rizika při zpracování cizích dat popisuje [Nezabezpečená deserializace v PHP](https://phpsos.de/cs/ratgeber/deserialisierung-php/), stahování interních cílů [Prevence Server-Side Request Forgery v PHP](https://phpsos.de/cs/blog/ssrf-in-php/).

## Jak konkrétně pomáhá PHP SOS?

- Najdeme všechna místa, kde vaše aplikace a její knihovny zpracovávají XML z cizích zdrojů.
- Odstraníme riziková nastavení parseru a zavedeme centrální zabezpečenou funkci pro načítání.
- Zkontrolujeme verze libxml a PHP na všech serverech a u starších systémů naplánujeme migraci.
- Změny otestujeme neškodnými kontrolními dokumenty a výsledek zdokumentujeme.

Kontrolu vlastního softwaru popisuje služba [Bezpečnostní audit a penetrační test](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Časté dotazy

**Jsou aplikace PHP od PHP 8 před XXE chráněny automaticky?**  
Ve výchozím stavu převážně ano, protože libxml od verze 2.9.0 entity už nenahrazuje. Ochrana však odpadá, jakmile kód nastaví volby jako LIBXML_NOENT, LIBXML_DTDLOAD nebo LIBXML_DTDVALID, nebo je PHP sestaveno s velmi starou verzí libxml.

**Mám ještě volat libxml_disable_entity_loader?**  
Ne. Funkce je od PHP 8.0 zastaralá, protože načítání externích entit je ve výchozím stavu vypnuto. Pro další ochranu doporučuje příručka PHP libxml_set_external_entity_loader a od PHP 8.4 s vhodnou libxml konstantu LIBXML_NO_XXE.

**Kde moje aplikace vůbec zpracovává XML?**  
Často na nečekaných místech: rozhraní SOAP, single sign-on přes SAML, kanály RSS, mapy webu, importy produktů, nahrané soubory SVG a soubory Office, které se interně skládají z XML. Také knihovny mohou zpracovávat XML, aniž by to bylo ve vlastním kódu vidět.

**Co je útok Billion Laughs?**  
Dokument s vnořenými entitami, který se při rozvinutí enormně rozroste a spotřebuje paměť i výpočetní čas. Patří do stejné rodiny jako XXE. Ochranu poskytuje vypnutí nahrazování entit a odmítání dokumentů s vlastní DTD.

## Zdroje

- [Příručka PHP: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [Příručka PHP: konstanty 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)
