# Forhindr XML External Entities (XXE) i PHP

> Forhindr XXE i PHP: hvad der gælder siden libxml 2.9 og PHP 8.0, hvilke flag der er risikable, LIBXML_NO_XXE fra PHP 8.4 og hvordan I finder XML-indgange.

Kilde: https://phpsos.de/da/blog/xxe-in-php/  
Status: 25.10.2026  
Udbyder: PHP SOS, PHP-nødhjælp og serversikkerhed. Akut hjælp på WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20har%20brug%20for%20hj%C3%A6lp%20med%20vores%20PHP-server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**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
<?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:

```bash
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](https://phpsos.de/da/php2shell/php-8-1-und-aelter/). 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](https://phpsos.de/da/ratgeber/deserialisierung-php/), hentning af interne mål [Forhindr Server-Side Request Forgery i PHP](https://phpsos.de/da/blog/ssrf-in-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](https://phpsos.de/da/leistungen/sicherheitsaudit-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

- [PHP-manual: libxml_disable_entity_loader](https://www.php.net/manual/de/function.libxml-disable-entity-loader.php)
- [PHP-manual: libxml-konstanter](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)
