# Spriječiti XML External Entities (XXE) u PHP-u

> Spriječiti XXE u PHP-u: što vrijedi od libxml 2.9 i PHP-a 8.0, koje su zastavice rizične, LIBXML_NO_XXE od PHP-a 8.4 i kako pronaći XML ulaze.

Izvor: https://phpsos.de/hr/blog/xxe-in-php/  
Stanje: 25.10.2026  
Pružatelj: PHP SOS, PHP hitna pomoć i sigurnost poslužitelja. Hitna pomoć putem WhatsAppa: https://wa.me/491736494914?text=Pozdrav%20PHP%20SOS%2C%20trebamo%20pomo%C4%87%20s%20na%C5%A1im%20PHP%20poslu%C5%BEiteljem.%20Tvrtka%3A%20%E2%80%A6%20Web-stranica%3A%20%E2%80%A6

**Ukratko:** XXE u PHP-u sprječavate tako da XML iz stranih izvora učitavate bez opcija `LIBXML_NOENT`, `LIBXML_DTDLOAD` i `LIBXML_DTDVALID`, odbijate dokumente s vlastitim DTD-om i postavite Entity Loader koji odbija vanjske resurse. Od libxml-a 2.9.0 PHP zadano više ne zamjenjuje entitete, zato je `libxml_disable_entity_loader()` od PHP-a 8.0 zastarjela. Od PHP-a 8.4 s libxml-om 2.13 dostupna je dodatno i `LIBXML_NO_XXE`.

## Stara ranjivost koja ne nestaje

XML External Entities, skraćeno XXE, mnogim se razvojnim inženjerima čine riješenom temom. PHP je s modernim verzijama libxml-a učitavanje vanjskih entiteta zadano isključio, a stara zaštitna funkcija označena je kao zastarjela s PHP-om 8.0. Unatoč tome XXE ranjivosti i dalje se pojavljuju u provjerama.

Razlog je gotovo uvijek u jednoj od tri situacije: netko je postavio opciju da riješi problem uvoza i time ponovno uključio zamjenu entiteta. Biblioteka to radi interno. Ili aplikacija radi na starom sustavu čiji je PHP izgrađen s vrlo starim libxml-om.

XXE je opasan jer XML parser pritom učitava sadržaje koji su u dokumentu samo referencirani: lokalne datoteke ili adrese u mreži. Moguće posljedice su čitanje konfiguracijskih datoteka, zahtjevi prema internim sustavima slično kao kod SSRF-a i preopterećenje poslužitelja.

## Što vrijedi od libxml-a 2.9 i PHP-a 8.0

PHP priručnik stanje opisuje ovako: od libxml-a 2.9.0 zamjena entiteta zadano je deaktivirana. Vanjske entitete stoga više ne treba posebno isključivati, osim ako se unutarnje reference na entitete trebaju razriješiti određenim opcijama. Iz toga proizlazi ovaj pregled:

| Opcija | Učinak | Rizik kod stranog XML-a |
|---|---|---|
| `LIBXML_NOENT` | zamjenjuje entitete | prema priručniku može olakšati XXE napade |
| `LIBXML_DTDLOAD` | učitava vanjski podskup | aktivira učitavanje vanjskih entiteta |
| `LIBXML_DTDVALID` | validira prema DTD-u | može olakšati XXE napade |
| `LIBXML_NONET` | zabranjuje mrežne pristupe pri učitavanju | zaštitna dodatna opcija |
| `LIBXML_NO_XXE` | isključuje vanjske entitete pri zamjeni | zaštitna, od PHP-a 8.4 i libxml-a 2.13.0 |
| `LIBXML_PARSEHUGE` | ukida unutarnja ograničenja parsera | olakšava preopterećenje, samo za pouzdane podatke |

Zavaravajući naziv opcije `LIBXML_NOENT` poznata je zamka: zvuči kao „bez entiteta“, a djeluje suprotno.

Za razvojne inženjere to znači: rizično nije samo učitavanje XML-a, nego svako mjesto koje svjesno mijenja zadano ponašanje. Potraga za tim konstantama u kodu stoga je najbrži put do prve procjene, prije svake složenije provjere.

## Sigurno obraniti XXE u PHP-u: uzorak za DOMDocument

Sljedeći primjer kombinira više zaštitnih slojeva. Postavlja Entity Loader koji odbija svaki vanjski resurs, učitava bez rizičnih opcija i odbija dokumente s vlastitim DTD-om:

```php
<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Vanjske resurse u načelu odbiti
    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;
}
```

Prema PHP priručniku povratna vrijednost `null` u loaderu blokira učitavanje vanjskog resursa. OWASP upozorava da nije dovoljno `null` izravno predati funkciji `libxml_set_external_entity_loader()`; potrebna je funkcija koja vraća `null`. Za SimpleXML i XMLReader vrijede iste opcije. Gdje god je moguće, OWASP preporučuje potpuno odbijati DTD-ove.

> **Savjet:**
> Ako sami možete odrediti format podataka, JSON je za razmjenu između vlastitih sustava obično jednostavniji izbor. Za strane formate poput SAML-a ili Office datoteka XML ostaje neizbježan.

## Pronaći skrivene XML ulaze

Mnoge aplikacije obrađuju više XML-a nego što njihovi razvojni inženjeri misle. Prođite ovaj popis:

1. **Sučelja:** SOAP klijenti i poslužitelji, XML-RPC, usluge plaćanja i dostave s XML odgovorima.
2. **Prijava:** Single Sign-On putem SAML-a temelji se na XML dokumentima koji dolaze izvana.
3. **Uvozi:** podaci o proizvodima, feedovi, sitemapovi, formati kataloga.
4. **Uploadi:** SVG slike su XML; Office formati poput DOCX i XLSX interno se sastoje od XML datoteka.
5. **Biblioteke:** Composer paketi za dokumente, tablice ili SAML koji interno koriste libxml.

U kodu pomažu dvije pretrage:

```bash
grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"
```

Nalazišta u `vendor/` nisu razlog za paniku, ali jesu povod da provjerite verziju i sigurnosne objave biblioteke.

Kod SVG uploada XXE je samo jedan od više rizika, jer SVG datoteke mogu sadržavati i skripte. Ako Vaša aplikacija taj format nužno ne treba, uopće ga ne dopuštajte za uploade. Inače SVG obrađujte samo sigurnim uzorkom iznad i datoteke isporučujte tako da preglednik ne izvršava sadržane skripte.

Pazite i na poruke o pogreškama. Ako aplikacija poruke libxml-a vraća korisnicima nefiltrirane, u njima mogu stajati dijelovi obrađivanog sadržaja. Pogreške pripadaju u zapisnik, korisnik dobiva opću poruku; u gornjem primjeru `libxml_use_internal_errors(true)` osigurava da upozorenja ne dospiju u izlaz. Ograničite i veličinu primljenih XML dokumenata prije nego ih parser uopće vidi.

## Stari sustavi: kad nedostaje standardna zaštita

Zaštita zadanim ponašanjem ovisi o verziji libxml-a s kojom je PHP izgrađen. Koja verzija radi, pokazuje `php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;'` na naredbenoj liniji ili ispis `phpinfo()` u zaštićenom okruženju. Vrijednost provjerite za svaku PHP verziju na poslužitelju, jer CLI i web-poslužitelj mogu koristiti različite buildove. Container imageovi donose vlastiti libxml; tamo provjeravajte u pokrenutom containeru, a ne na hostu.

Osobito su pogođeni sustavi koji ionako rade na starim PHP verzijama. Za PHP 8.1 i starije nakon PHP2Shella više nema službenih updatea PHP projekta; što to znači, opisuje [PHP 8.1 i starije: što sada vrijedi](https://phpsos.de/hr/php2shell/php-8-1-und-aelter/). Stari sustav sa zastarjelim libxml-om i otvorenim ranjivostima interpretera ne bi trebalo samo osigurati u kodu, nego migrirati. Srodne rizike pri obradi stranih podataka opisuje [Nesigurna deserijalizacija u PHP-u](https://phpsos.de/hr/ratgeber/deserialisierung-php/), a dohvate internih odredišta [Spriječiti Server-Side Request Forgery u PHP-u](https://phpsos.de/hr/blog/ssrf-in-php/).

## Kako PHP SOS konkretno pomaže?

- Pronalazimo sva mjesta na kojima Vaša aplikacija i njezine biblioteke obrađuju XML iz stranih izvora.
- Uklanjamo rizične opcije parsera i ugrađujemo središnju, osiguranu funkciju učitavanja.
- Provjeravamo verzije libxml-a i PHP-a na svim poslužiteljima i kod starih sustava planiramo migraciju.
- Promjene testiramo bezopasnim probnim dokumentima i dokumentiramo rezultat.

Provjeru vlastitog softvera opisuje usluga [Sigurnosni audit i penetracijski test](https://phpsos.de/hr/leistungen/sicherheitsaudit-penetrationstest/).


## Česta pitanja

**Jesu li PHP aplikacije od PHP-a 8 automatski zaštićene od XXE-a?**  
U zadanom slučaju uglavnom jesu, jer libxml od verzije 2.9.0 više ne zamjenjuje entitete. Zaštita otpada čim kod postavi opcije poput LIBXML_NOENT, LIBXML_DTDLOAD ili LIBXML_DTDVALID ili je PHP izgrađen s vrlo starom verzijom libxml-a.

**Trebam li još pozivati libxml_disable_entity_loader?**  
Ne. Funkcija je od PHP-a 8.0 zastarjela jer je učitavanje vanjskih entiteta zadano isključeno. Za dodatnu zaštitu PHP priručnik preporučuje libxml_set_external_entity_loader, a od PHP-a 8.4 uz odgovarajući libxml konstantu LIBXML_NO_XXE.

**Gdje moja aplikacija uopće obrađuje XML?**  
Često na neočekivanim mjestima: SOAP sučelja, Single Sign-On putem SAML-a, RSS feedovi, sitemapovi, uvozi proizvoda, SVG uploadi i Office datoteke koje se interno sastoje od XML-a. I biblioteke mogu obrađivati XML a da to nije vidljivo u vlastitom kodu.

**Što je napad Billion Laughs?**  
Dokument s ugniježđenim entitetima koji pri razrješavanju golemo naraste i troši memoriju i procesorsko vrijeme. Pripada istoj obitelji kao XXE. Zaštitu daju isključivanje zamjene entiteta i odbijanje dokumenata s vlastitim DTD-om.

## Izvori

- [PHP priručnik: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [PHP priručnik: libxml konstante](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)
