Aktuel situation: PHP2Shell. To kritiske PHP-sårbarheder (CVSS 9,2) tillader skadelig kode uden login. Rettelser siden 21.10.2026.Hvad der skal gøres nu →

Forhindr XML External Entities (XXE) i PHP

Hvordan udviklere behandler XML sikkert i PHP: libxml's standardadfærd, risikable indstillinger som LIBXML_NOENT, beskyttelse med entity-loader og LIBXML_NO_XXE og typiske skjulte XML-indgange.

Status 5 min. læsetidSikker udvikling

Kort sagt: XXE i PHP forhindrer I ved at indlæse XML fra fremmede kilder uden indstillingerne LIBXML_NOENT, LIBXML_DTDLOAD og LIBXML_DTDVALID, afvise dokumenter med egen DTD og sætte en entity-loader, der nægter eksterne ressourcer. Siden libxml 2.9.0 erstatter PHP som standard ikke længere entities, derfor er libxml_disable_entity_loader() forældet siden PHP 8.0. Fra PHP 8.4 med libxml 2.13 står desuden LIBXML_NO_XXE til rådighed.

En gammel sårbarhed, der ikke forsvinder

XML External Entities, kort XXE, anses af mange udviklere for et afsluttet emne. PHP har med moderne libxml-versioner slået indlæsning af eksterne entities fra som standard, og den gamle beskyttelsesfunktion blev markeret som forældet med PHP 8.0. Alligevel dukker XXE-sårbarheder fortsat op ved kontroller.

Årsagen ligger næsten altid i en af tre situationer: Nogen har sat en indstilling for at løse et importproblem og dermed tændt entity-erstatningen igen. Et bibliotek gør det internt. Eller applikationen kører på et ældre system, hvis PHP er bygget med en meget gammel libxml.

XXE er farlig, fordi XML-parseren dermed indlæser indhold, som i dokumentet kun er refereret: lokale filer eller adresser på nettet. Mulige følger er udlæsning af konfigurationsfiler, forespørgsler til interne systemer ligesom ved SSRF og overbelastning af serveren.

Hvad der gælder siden libxml 2.9 og PHP 8.0

PHP-manualen beskriver situationen sådan: Fra libxml 2.9.0 er erstatning af entities deaktiveret som standard. Eksterne entities behøver derfor ikke længere særskilt at slås fra, medmindre interne entity-referencer skal opløses med bestemte indstillinger. Heraf fremgår denne oversigt:

Indstilling Virkning Risiko ved fremmed XML
LIBXML_NOENT erstatter entities ifølge manualen kan den lette XXE-angreb
LIBXML_DTDLOAD indlæser det eksterne subset aktiverer indlæsning af eksterne entities
LIBXML_DTDVALID validerer mod DTD'en kan lette XXE-angreb
LIBXML_NONET forbyder netværksadgang ved indlæsning beskyttende ekstraindstilling
LIBXML_NO_XXE slår eksterne entities fra ved erstatning beskyttende, fra PHP 8.4 og libxml 2.13.0
LIBXML_PARSEHUGE ophæver parserens interne grænser letter overbelastning, kun til troværdige data

Det vildledende navn på LIBXML_NOENT er en kendt faldgrube: Det lyder som "ingen entities", men bevirker det modsatte.

For udviklere betyder det: Risikabelt er ikke indlæsningen af XML i sig selv, men ethvert sted, der bevidst ændrer standardadfærden. En søgning efter disse konstanter i koden er derfor den hurtigste vej til en første vurdering, før enhver omfattende kontrol.

Afvær XXE i PHP sikkert: et mønster for DOMDocument

Følgende eksempel kombinerer flere beskyttelseslag. Det sætter en entity-loader, der nægter enhver ekstern ressource, indlæser uden risikable indstillinger og afviser dokumenter med egen DTD:

<?php
declare(strict_types=1);

function ladeFremdesXml(string $xml): DOMDocument
{
    // Nægt som udgangspunkt eksterne ressourcer
    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;
}

Ifølge PHP-manualen blokerer en returværdi null i loaderen indlæsningen af den eksterne ressource. OWASP påpeger, at det ikke er nok at overgive null direkte til libxml_set_external_entity_loader(); der skal en funktion til, som returnerer null. For SimpleXML og XMLReader gælder de samme indstillinger. Hvor det overhovedet er muligt, anbefaler OWASP helt at afvise DTD'er.

Tip

Kan I selv bestemme dataformatet, er JSON til udveksling mellem egne systemer som regel det enklere valg. Til fremmede formater som SAML eller Office-filer er XML uundgåeligt.

Find skjulte XML-indgange

Mange applikationer behandler mere XML, end deres udviklere tror. Gennemgå denne liste:

  1. Grænseflader: SOAP-klienter og -servere, XML-RPC, betalings- og forsendelsestjenester med XML-svar.
  2. Login: Single sign-on med SAML bygger på XML-dokumenter, der kommer udefra.
  3. Importer: Produktdata, feeds, sitemaps, kataloggformater.
  4. Uploads: SVG-billeder er XML; Office-formater som DOCX og XLSX består internt af XML-filer.
  5. Biblioteker: Composer-pakker til dokumenter, regneark eller SAML, der internt bruger libxml.

I koden hjælper to søgninger:

grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"

Fund i vendor/ er ingen grund til panik, men en anledning til at kontrollere bibliotekets version og sikkerhedsmeddelelser.

Ved SVG-uploads er XXE kun en af flere risici, for SVG-filer kan også indeholde scripts. Har jeres applikation ikke tvingende brug for formatet, så tillad det slet ikke til uploads. Ellers behandler I SVG kun med det sikre mønster ovenfor og leverer filerne så, at browseren ikke udfører indeholdte scripts.

Vær desuden opmærksom på fejlmeddelelser. Returnerer en applikation libxml's meddelelser ufiltreret til brugere, kan der stå dele af det behandlede indhold i dem. Fejl hører hjemme i loggen, brugeren får en generel meddelelse; i eksemplet ovenfor sørger libxml_use_internal_errors(true) for, at advarsler ikke havner i outputtet. Begræns desuden størrelsen af modtagne XML-dokumenter, før parseren overhovedet ser dem.

Ældre systemer: Når standardbeskyttelsen mangler

Beskyttelsen gennem standardadfærden hænger på den libxml-version, PHP er bygget med. Hvilken version der kører, viser php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' på kommandolinjen eller outputtet fra phpinfo() i et beskyttet miljø. Kontrollér værdien for hver PHP-version på serveren, for CLI og webserver kan bruge forskellige builds. Container-images medbringer deres egen libxml; dér kontrollerer I i den kørende container, ikke på værten.

Særligt berørt er systemer, der alligevel kører på gamle PHP-versioner. For PHP 8.1 og ældre findes der efter PHP2Shell ikke længere officielle opdateringer fra PHP-projektet; hvad det betyder, beskriver PHP 8.1 og ældre: Hvad der gælder nu. Et ældre system med forældet libxml og åbne fortolkersårbarheder bør ikke blot sikres i koden, men migreres. Beslægtede risici ved behandling af fremmede data beskriver Usikker deserialisering i PHP, hentning af interne mål Forhindr Server-Side Request Forgery i PHP.

Hvordan hjælper PHP SOS konkret?

  • Vi finder alle steder, hvor jeres applikation og dens biblioteker behandler XML fra fremmede kilder.
  • Vi fjerner risikable parserindstillinger og indbygger en central, sikret indlæsningsfunktion.
  • Vi kontrollerer libxml- og PHP-versioner på alle servere og planlægger migrering af ældre systemer.
  • Vi tester ændringerne med harmløse testdokumenter og dokumenterer resultatet.

Kontrol af egen software beskriver ydelsen Sikkerhedsaudit og penetrationstest.

Ofte stillede spørgsmål

Er PHP-applikationer automatisk beskyttet mod XXE siden PHP 8?

I standardtilfældet i vid udstrækning, fordi libxml fra version 2.9.0 ikke længere erstatter entities. Beskyttelsen bortfalder dog, så snart koden sætter indstillinger som LIBXML_NOENT, LIBXML_DTDLOAD eller LIBXML_DTDVALID, eller PHP er bygget med en meget gammel libxml-version.

Skal jeg stadig kalde libxml_disable_entity_loader?

Nej. Funktionen er forældet siden PHP 8.0, fordi indlæsning af eksterne entities som standard er slået fra. Til ekstra beskyttelse anbefaler PHP-manualen libxml_set_external_entity_loader og fra PHP 8.4 med passende libxml konstanten LIBXML_NO_XXE.

Hvor behandler min applikation overhovedet XML?

Ofte på uventede steder: SOAP-grænseflader, single sign-on med SAML, RSS-feeds, sitemaps, produktimporter, SVG-uploads og Office-filer, der internt består af XML. Også biblioteker kan behandle XML, uden at det er synligt i den egen kode.

Hvad er billion-laughs-angrebet?

Et dokument med indlejrede entities, der vokser enormt ved opløsningen og æder hukommelse og regnetid. Det hører til samme familie som XXE. Beskyttelse giver afbrydelse af entity-erstatning og afvisning af dokumenter med egen DTD.

Kilder

Akut kontakt

Hver time tæller. Skriv til os nu.

Beskriv kort system, PHP-version og observation. Vi vender tilbage, afklarer situationen og arbejder på afstand, indtil hændelsen er afsluttet og dokumenteret.

Akut hjælp