Trumpai: XXE PHP užkertate kelią įkeldami XML iš svetimų šaltinių be parinkčių LIBXML_NOENT, LIBXML_DTDLOAD ir LIBXML_DTDVALID, atmesdami dokumentus su sava DTD ir nustatydami entity loader, kuris atsisako išorinių išteklių. Nuo libxml 2.9.0 PHP pagal numatytuosius nustatymus entity nebekeičia, todėl libxml_disable_entity_loader() nuo PHP 8.0 pasenusi. Nuo PHP 8.4 su libxml 2.13 papildomai yra LIBXML_NO_XXE.
Sena spraga, kuri neišnyksta
XML External Entities, trumpai XXE, daugeliui kūrėjų yra išspręsta tema. Su modernia libxml versija PHP išorinių entity įkėlimą pagal numatytuosius nustatymus išjungė, o sena apsaugos funkcija su PHP 8.0 pažymėta kaip pasenusi. Vis dėlto XXE spragos auditų metu ir toliau atsiranda.
Priežastis beveik visada viena iš trijų situacijų: kažkas nustatė parinktį importo problemai išspręsti ir taip vėl įjungė entity keitimą. Biblioteka tai daro viduje. Arba programa veikia senoje sistemoje, kurios PHP sukompiliuotas su labai sena libxml.
XXE pavojingas, nes XML analizatorius įkelia turinį, kuris dokumente tik nurodytas: vietinius failus ar tinklo adresus. Galimos pasekmės: konfigūracijos failų nuskaitymas, užklausos į vidines sistemas panašiai kaip SSRF ir serverio perkrovimas.
Kas galioja nuo libxml 2.9 ir PHP 8.0
PHP vadovas padėtį aprašo taip: nuo libxml 2.9.0 entity keitimas pagal numatytuosius nustatymus išjungtas. Todėl išorinių entity atskirai išjungti nebereikia, nebent vidinės entity nuorodos turi būti išskleidžiamos su tam tikromis parinktimis. Iš to išplaukia ši apžvalga:
| Parinktis | Poveikis | Rizika svetimam XML |
|---|---|---|
LIBXML_NOENT |
keičia entity | pasak vadovo, gali palengvinti XXE atakas |
LIBXML_DTDLOAD |
įkelia išorinį poaibį | aktyvina išorinių entity įkėlimą |
LIBXML_DTDVALID |
validuoja pagal DTD | gali palengvinti XXE atakas |
LIBXML_NONET |
draudžia tinklo prieigą įkeliant | apsauginė papildoma parinktis |
LIBXML_NO_XXE |
keičiant išjungia išorines entity | apsauginė, nuo PHP 8.4 ir libxml 2.13.0 |
LIBXML_PARSEHUGE |
panaikina vidines analizatoriaus ribas | palengvina perkrovimą, tik patikimiems duomenims |
Klaidinantis LIBXML_NOENT pavadinimas yra gerai žinomi spąstai: jis skamba kaip „jokių entity“, bet veikia atvirkščiai.
Kūrėjams tai reiškia: rizikinga ne pats XML įkėlimas, o kiekviena vieta, kuri sąmoningai keičia numatytąjį elgesį. Todėl šių konstantų paieška kode yra greičiausias kelias į pirmą įvertinimą, dar prieš bet kokią sudėtingą patikrą.
XXE PHP saugiai atremti: DOMDocument šablonas
Toliau pateiktas pavyzdys derina kelis apsaugos sluoksnius. Jis nustato entity loader, kuris atsisako bet kokio išorinio ištekliaus, įkelia be rizikingų parinkčių ir atmeta dokumentus su sava DTD:
<?php
declare(strict_types=1);
function ladeFremdesXml(string $xml): DOMDocument
{
// Iš principo atsisakyti išorinių išteklių
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;
}
Pasak PHP vadovo, grąžinimo reikšmė null loaderyje blokuoja išorinio ištekliaus įkėlimą. OWASP nurodo, kad nepakanka tiesiogiai perduoti null į libxml_set_external_entity_loader(); reikia funkcijos, kuri grąžina null. SimpleXML ir XMLReader galioja tos pačios parinktys. Kur tik įmanoma, OWASP rekomenduoja DTD atmesti visiškai.
Jei duomenų formatą galite nustatyti patys, mainams tarp savų sistemų JSON dažniausiai paprastesnis pasirinkimas. Svetimiems formatams, kaip SAML ar Office failai, XML lieka neišvengiamas.
Paslėptų XML įėjimų paieška
Daugelis programų apdoroja daugiau XML, nei galvoja jų kūrėjai. Peržiūrėkite šį sąrašą:
- Sąsajos: SOAP klientai ir serveriai, XML-RPC, mokėjimo ir siuntų paslaugos su XML atsakymais.
- Prisijungimas: vieningas prisijungimas (single sign-on) su SAML remiasi XML dokumentais, ateinančiais iš išorės.
- Importai: produktų duomenys, srautai (feed), svetainių žemėlapiai, katalogų formatai.
- Įkėlimai: SVG paveikslėliai yra XML; Office formatai kaip DOCX ir XLSX viduje susideda iš XML failų.
- Bibliotekos: Composer paketai dokumentams, lentelėms ar SAML, viduje naudojantys libxml.
Kode padeda dvi paieškos:
grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"
Radiniai vendor/ nėra priežastis panikai, bet yra proga patikrinti bibliotekos versiją ir saugumo pranešimus.
SVG įkėlimuose XXE yra tik viena iš kelių rizikų, nes SVG failuose gali būti ir scenarijų. Jei jūsų programai formatas nebūtinas, įkėlimams jo išvis neleiskite. Kitu atveju SVG apdorokite tik pagal aukščiau pateiktą saugų šabloną ir failus pateikite taip, kad naršyklė juose esančių scenarijų nevykdytų.
Atkreipkite dėmesį ir į klaidų pranešimus. Jei programa libxml pranešimus nefiltruotus grąžina naudotojams, juose gali būti apdorojamo turinio dalių. Klaidos priklauso žurnalui, naudotojas gauna bendrą pranešimą; aukščiau pateiktame pavyzdyje libxml_use_internal_errors(true) pasirūpina, kad įspėjimai nepatektų į išvestį. Be to, apribokite gaunamų XML dokumentų dydį dar prieš analizatoriui juos pamatant.
Senos sistemos: kada standartinės apsaugos trūksta
Numatytojo elgesio apsauga priklauso nuo libxml versijos, su kuria PHP sukompiliuotas. Kuri versija veikia, parodo php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' komandinėje eilutėje arba phpinfo() išvestis apsaugotoje aplinkoje. Reikšmę patikrinkite kiekvienai serverio PHP versijai, nes CLI ir žiniatinklio serveris gali naudoti skirtingas versijas. Konteinerių atvaizdai atsineša savo libxml; ten tikrinkite veikiančiame konteineryje, o ne pagrindiniame kompiuteryje.
Ypač paveiktos sistemos, kurios ir taip veikia su senomis PHP versijomis. PHP 8.1 ir senesnėms po PHP2Shell oficialių PHP projekto atnaujinimų nebėra; ką tai reiškia, aprašo PHP 8.1 ir senesnės: kas dabar galioja. Senos sistemos su pasenusia libxml ir atviromis interpretatoriaus spragomis neužtenka apsaugoti tik kodu, ją reikia perkelti. Giminingas rizikas apdorojant svetimus duomenis aprašo Nesaugi deserializacija PHP, vidinių tikslų kvietimus Server-Side Request Forgery PHP prevencija.
Kaip PHP SOS padeda konkrečiai?
- Randame visas vietas, kuriose jūsų programa ir jos bibliotekos apdoroja XML iš svetimų šaltinių.
- Pašaliname rizikingas analizatoriaus parinktis ir įdiegiame centrinę, apsaugotą įkėlimo funkciją.
- Tikriname libxml ir PHP versijas visuose serveriuose ir senose sistemose planuojame migraciją.
- Pakeitimus išbandome nekenksmingais bandomaisiais dokumentais ir rezultatą dokumentuojame.
Savos programinės įrangos patikrą aprašo paslauga Saugumo auditas ir įsiskverbimo testas.
Dažni klausimai
Ar PHP programos nuo PHP 8 automatiškai apsaugotos nuo XXE?
Numatytuoju atveju iš esmės taip, nes libxml nuo 2.9.0 versijos entity nebekeičia. Tačiau apsauga išnyksta, kai tik kodas nustato parinktis kaip LIBXML_NOENT, LIBXML_DTDLOAD ar LIBXML_DTDVALID arba PHP sukompiliuotas su labai sena libxml versija.
Ar libxml_disable_entity_loader dar turėčiau kviesti?
Ne. Funkcija nuo PHP 8.0 pasenusi, nes išorinių entity įkėlimas pagal numatytuosius nustatymus išjungtas. Papildomai apsaugai PHP vadovas rekomenduoja libxml_set_external_entity_loader, o nuo PHP 8.4 su tinkama libxml konstantą LIBXML_NO_XXE.
Kur mano programa apskritai apdoroja XML?
Dažnai netikėtose vietose: SOAP sąsajose, vieningame prisijungime (single sign-on) su SAML, RSS srautuose, svetainių žemėlapiuose, produktų importuose, SVG įkėlimuose ir Office failuose, kurie viduje susideda iš XML. Ir bibliotekos gali apdoroti XML, nors tai nematoma savame kode.
Kas yra Billion-Laughs ataka?
Dokumentas su įdėtais entity, kuris išskleidžiamas milžiniškai išauga ir sunaudoja atmintį bei skaičiavimo laiką. Ji priklauso tai pačiai šeimai kaip XXE. Apsaugo entity keitimo išjungimas ir dokumentų su sava DTD atmetimas.