# Ako zabrániť XML External Entities (XXE) v PHP

> Zabráňte XXE v PHP: čo platí od libxml 2.9 a PHP 8.0, ktoré príznaky sú riskantné, LIBXML_NO_XXE od PHP 8.4 a ako nájsť vstupy XML.

Zdroj: https://phpsos.de/sk/blog/xxe-in-php/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručne:** XXE v PHP zabránite tak, že XML z cudzích zdrojov načítate bez možností `LIBXML_NOENT`, `LIBXML_DTDLOAD` a `LIBXML_DTDVALID`, dokumenty s vlastným DTD odmietnete a nastavíte Entity Loader, ktorý externé zdroje odmieta. Od libxml 2.9.0 PHP štandardne entity nenahrádza, preto je `libxml_disable_entity_loader()` od PHP 8.0 zastaraná. Od PHP 8.4 s libxml 2.13 je navyše k dispozícii `LIBXML_NO_XXE`.

## Stará zraniteľnosť, ktorá nezmizne

XML External Entities, skrátene XXE, považuje veľa vývojárov za vyriešenú tému. PHP s modernými verziami libxml štandardne vypol načítavanie externých entít a stará ochranná funkcia bola v PHP 8.0 označená ako zastaraná. Napriek tomu sa zraniteľnosti XXE v kontrolách stále objavujú.

Dôvod je takmer vždy v jednej z troch situácií: niekto nastavil možnosť na vyriešenie problému s importom a tým opäť zapol nahrádzanie entít. Knižnica to robí interne. Alebo aplikácia beží na starom systéme, ktorého PHP bolo zostavené s veľmi starou libxml.

XXE je nebezpečné, pretože parser XML pritom dosťahuje obsah, na ktorý sa v dokumente len odkazuje: lokálne súbory alebo adresy v sieti. Možné následky sú čítanie konfiguračných súborov, požiadavky na interné systémy podobne ako pri SSRF a preťaženie servera.

## Čo platí od libxml 2.9 a PHP 8.0

Príručka PHP opisuje situáciu takto: od libxml 2.9.0 je nahrádzanie entít štandardne vypnuté. Externé entity sa preto už nemusia osobitne vypínať, pokiaľ sa interné odkazy na entity nemajú rozlišovať pomocou určitých možností. Z toho vyplýva tento prehľad:

| Možnosť | Účinok | Riziko pri cudzom XML |
|---|---|---|
| `LIBXML_NOENT` | nahrádza entity | podľa príručky môže útoky XXE uľahčiť |
| `LIBXML_DTDLOAD` | načítava externú podmnožinu | aktivuje načítavanie externých entít |
| `LIBXML_DTDVALID` | validuje voči DTD | môže útoky XXE uľahčiť |
| `LIBXML_NONET` | zakazuje sieťové prístupy pri načítavaní | ochranná prídavná možnosť |
| `LIBXML_NO_XXE` | vypína externé entity pri nahrádzaní | ochranná, od PHP 8.4 a libxml 2.13.0 |
| `LIBXML_PARSEHUGE` | ruší interné hranice parsera | uľahčuje preťaženie, len pre dôveryhodné údaje |

Zavádzajúci názov `LIBXML_NOENT` je známa pasca: znie ako „žiadne entity“, no spôsobuje opak.

Pre vývojárov to znamená: riskantné nie je samotné načítanie XML, ale každé miesto, ktoré štandardné správanie vedome mení. Hľadanie týchto konštánt v kóde je preto najrýchlejšou cestou k prvému odhadu, ešte pred akoukoľvek náročnou kontrolou.

## Bezpečná obrana proti XXE v PHP: vzor pre DOMDocument

Nasledujúci príklad kombinuje viacero ochranných vrstiev. Nastaví Entity Loader, ktorý odmieta každý externý zdroj, načíta bez riskantných možností a odmietne dokumenty s vlastným DTD:

```php
<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Externé zdroje zásadne odmietať
    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;
}
```

Podľa príručky PHP blokuje návratová hodnota `null` v loaderi načítanie externého zdroja. OWASP upozorňuje, že nestačí odovzdať `null` priamo funkcii `libxml_set_external_entity_loader()`; potrebuje sa funkcia, ktorá vracia `null`. Pre SimpleXML a XMLReader platia rovnaké možnosti. Kdekoľvek je to možné, OWASP odporúča DTD odmietať úplne.

> **Tip:**
> Ak môžete dátový formát určiť sami, je JSON pre výmenu medzi vlastnými systémami spravidla jednoduchšou voľbou. Pre cudzie formáty ako SAML alebo súbory Office je XML nevyhnutné.

## Nájdenie skrytých vstupov XML

Mnohé aplikácie spracúvajú viac XML, než si ich vývojári myslia. Prejdite si tento zoznam:

1. **Rozhrania:** klienti a servery SOAP, XML-RPC, platobné a prepravné služby s odpoveďami XML.
2. **Prihlásenie:** jednotné prihlásenie cez SAML sa zakladá na dokumentoch XML, ktoré prichádzajú zvonka.
3. **Importy:** údaje o produktoch, kanály, sitemapy, katalógové formáty.
4. **Uploady:** obrázky SVG sú XML; formáty Office ako DOCX a XLSX interne pozostávajú zo súborov XML.
5. **Knižnice:** balíky Composer pre dokumenty, tabuľky alebo SAML, ktoré interne používajú libxml.

V kóde pomôžu dve hľadania:

```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 vo `vendor/` nie sú dôvodom na paniku, ale podnetom skontrolovať verziu a bezpečnostné hlásenia knižnice.

Pri uploadoch SVG je XXE len jedným z viacerých rizík, pretože súbory SVG môžu obsahovať aj skripty. Ak vaša aplikácia formát nutne nepotrebuje, vôbec ho pre uploady nepovoľte. Inak SVG spracúvajte len podľa bezpečného vzoru vyššie a súbory doručujte tak, aby prehliadač obsiahnuté skripty nevykonal.

Dávajte pozor aj na chybové hlásenia. Ak aplikácia vracia hlásenia libxml používateľom nefiltrované, môžu v nich byť časti spracúvaného obsahu. Chyby patria do protokolu, používateľ dostane všeobecné hlásenie; v príklade vyššie zabezpečuje `libxml_use_internal_errors(true)`, aby sa upozornenia nedostali do výstupu. Obmedzte navyše veľkosť prijatých dokumentov XML skôr, než ich parser vôbec uvidí.

## Staré systémy: kedy štandardná ochrana chýba

Ochrana štandardným správaním závisí od verzie libxml, s ktorou bolo PHP zostavené. Ktorá verzia beží, ukáže `php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;'` na príkazovom riadku alebo výstup `phpinfo()` v chránenom prostredí. Hodnotu skontrolujte pre každú verziu PHP na serveri, pretože CLI a webserver môžu používať odlišné zostavenia. Obrazy kontajnerov prinášajú vlastnú libxml; tam kontrolujte v bežiacom kontajneri, nie na hostiteľovi.

Obzvlášť postihnuté sú systémy, ktoré tak či tak bežia na starých verziách PHP. Pre PHP 8.1 a staršie už po PHP2Shell neexistujú oficiálne aktualizácie projektu PHP; čo to znamená, opisuje [PHP 8.1 a staršie: čo platí teraz](https://phpsos.de/sk/php2shell/php-8-1-und-aelter/). Starý systém so zastaranou libxml a otvorenými zraniteľnosťami interpretera by sa nemal len zabezpečiť v kóde, ale migrovať. Príbuzné riziká pri spracovaní cudzích údajov opisuje [Nebezpečná deserializácia v PHP](https://phpsos.de/sk/ratgeber/deserialisierung-php/), sťahovanie interných cieľov [Ako zabrániť Server-Side Request Forgery v PHP](https://phpsos.de/sk/blog/ssrf-in-php/).

## Ako konkrétne pomáha PHP SOS?

- Nájdeme všetky miesta, kde vaša aplikácia a jej knižnice spracúvajú XML z cudzích zdrojov.
- Odstránime riskantné možnosti parsera a zabudujeme centrálnu, zabezpečenú funkciu načítania.
- Skontrolujeme verzie libxml a PHP na všetkých serveroch a pri starých systémoch naplánujeme migráciu.
- Zmeny otestujeme neškodnými kontrolnými dokumentmi a výsledok zdokumentujeme.

Kontrolu vlastného softvéru opisuje služba [Bezpečnostný audit a penetračný test](https://phpsos.de/sk/leistungen/sicherheitsaudit-penetrationstest/).


## Časté otázky

**Sú aplikácie PHP od PHP 8 automaticky chránené pred XXE?**  
V štandardnom prípade vo veľkej miere, pretože libxml od verzie 2.9.0 už entity nenahrádza. Ochrana však zaniká, hneď ako kód nastaví možnosti ako LIBXML_NOENT, LIBXML_DTDLOAD alebo LIBXML_DTDVALID, alebo ak je PHP zostavené s veľmi starou verziou libxml.

**Mám ešte volať libxml_disable_entity_loader?**  
Nie. Funkcia je od PHP 8.0 zastaraná, pretože načítavanie externých entít je štandardne vypnuté. Pre dodatočnú ochranu odporúča príručka PHP libxml_set_external_entity_loader a od PHP 8.4 s vhodnou libxml konštantu LIBXML_NO_XXE.

**Kde moja aplikácia vôbec spracúva XML?**  
Často na nečakaných miestach: rozhrania SOAP, jednotné prihlásenie (single sign-on) cez SAML, kanály RSS, sitemapy, importy produktov, uploady SVG a súbory Office, ktoré interne pozostávajú z XML. XML môžu spracúvať aj knižnice, bez toho, aby to bolo vidieť vo vlastnom kóde.

**Čo je útok Billion Laughs?**  
Dokument s vnorenými entitami, ktorý sa pri rozbaľovaní enormne zväčšuje a spotrebúva pamäť a výpočtový čas. Patrí do tej istej rodiny ako XXE. Ochranu poskytuje vypnutie nahrádzania entít a odmietanie dokumentov s vlastným DTD.

## Zdroje

- [Príručka PHP: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [Príručka PHP: konštanty 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)
