Trenutno stanje: PHP2Shell. Dvije kritične PHP ranjivosti (CVSS 9.2) omogućuju zlonamjerni kod bez prijave. Zakrpe od 21. 10. 2026.Što sada učiniti →

Spriječiti XML External Entities (XXE) u PHP-u

Kako razvojni inženjeri sigurno obrađuju XML u PHP-u: zadano ponašanje libxml-a, rizične opcije poput LIBXML_NOENT, zaštita pomoću Entity Loadera i LIBXML_NO_XXE te tipični skriveni XML ulazi.

Stanje 5 min čitanjaSiguran razvoj

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
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:

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. 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, a dohvate internih odredišta Spriječiti Server-Side Request Forgery u PHP-u.

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.

Č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

Hitni kontakt

Svaki sat se računa. Pišite nam odmah.

Ukratko opišite sustav, verziju PHP-a i opažanje. Javit ćemo Vam se, razjasniti situaciju i raditi na daljinu dok incident nije zaključen i dokumentiran.

Hitna pomoć