# Usikker deserialisering i PHP: unserialize og sikkerhed

> Brug PHP unserialize sikkert: hvorfor objekter fra brugerinput er farlige, hvordan JSON, allowed_classes og signaturer beskytter, og hvor I skal lede.

Kilde: https://phpsos.de/da/ratgeber/deserialisierung-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:** Ved PHP-unserialize-sikkerhed gælder en enkel regel: Behandl aldrig data, der kommer udefra, med `unserialize()`. Brug i stedet JSON. Hvor `unserialize()` fra ældre kode må blive, begrænser I det med `allowed_classes => false` og kontrollerer dataenes ægthed med en signatur.

## Hvad der kan gå galt med unserialize()

PHP kan med `serialize()` omdanne arrays og objekter til en tegnstreng og med `unserialize()` genskabe dem. Det er bekvemt til at lægge data i cookies, caches eller databasefelter. Problemet: `unserialize()` opretter dermed også objekter af vilkårlige klasser, som kan indlæses i applikationen.

Ved oprettelsen og senest ved ødelæggelsen af et sådant objekt kalder PHP automatisk bestemte metoder, f.eks. `__wakeup()`, `__unserialize()` eller `__destruct()`. Indeholder klasser i projektet eller i biblioteker sådanne metoder med fil-, database- eller systemhandlinger, kan en angriber misbruge dem via manipulerede data. Fagfolk taler om PHP Object Injection. Afhængigt af de eksisterende klasser rækker virkningen fra sletning af enkelte filer til kørsel af skadelig kode.

Sammenfattet for ikke-teknikere: Et eneste ubetænksomt kald det forkerte sted kan lukke en angriber vidt ind i applikationen. Denne svaghed hører derfor i årevis til emnerne i OWASP Top 10.

## Det sikre alternativ: JSON i stedet for unserialize

I næsten alle tilfælde, hvor data kommer udefra eller forlader serveren, er JSON det rette valg. `json_decode()` opretter kun arrays, tal, tegnstrenge og sandhedsværdier, ingen objekter af egne klasser.

```php
<?php
declare(strict_types=1);

function leseEinstellungen(string $eingabe): array
{
    try {
        $daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
    } catch (JsonException $e) {
        throw new InvalidArgumentException('Ungültige Daten.');
    }

    if (!is_array($daten)) {
        throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
    }

    // Overtag kun kendte felter med forventede typer
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}
```

Vigtig er den anden del: Også JSON-data er input. Overtag kun forventede felter, og kontrollér typer og værdiområder, før I arbejder med dem.

## Når unserialize er uundgåeligt: allowed_classes og signatur

I ældre applikationer ligger serialiserede data ofte i databaser eller caches, som ikke kan omstilles med det samme. Så begrænser I risikoen i to trin. For det første forhindrer I med `allowed_classes`, at objekter oprettes. For det andet sikrer I med en HMAC-signatur, at dataene faktisk stammer fra jeres applikation og ikke er ændret på vejen.

```php
<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // aldrig i kode-repositoriet

function verpacken(array $daten): string
{
    $roh = serialize($daten);
    return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}

function auspacken(string $paket): array
{
    [$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
    $roh = base64_decode($b64, true);

    if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
        throw new RuntimeException('Signatur ungültig.');
    }

    $daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
    if (!is_array($daten)) {
        throw new RuntimeException('Unerwartetes Format.');
    }
    return $daten;
}
```

Hvis bestemte klasser alligevel er nødvendige, så nævn dem enkeltvis i `allowed_classes` i stedet for at tillade alle. Nøglen hører hjemme i serverkonfigurationen eller et secret-lager og skal fornyes efter en hændelse.

### Omstil eksisterende data trinvis til JSON

I applikationer, der er vokset gennem årene, ligger serialiserede værdier ofte i årevis i databasen. En omstilling i ét skridt er da risikabel. En overgang har vist sit værd: Applikationen læser begge formater, men skriver kun JSON. Ved læsning af en gammel værdi pakkes den én gang ud med `allowed_classes => false` og skrives straks tilbage som JSON. Efter nogen tid er de fleste poster omstillet, og et engangsscript klarer resten.

Log under overgangen, hvor ofte det gamle format stadig læses, og også når signaturer ikke passer. Sådanne fejlslag kan pege på manipulationsforsøg og hører hjemme i overvågningen. Først når der ikke længere dukker et gammelt format op, fjerner I `unserialize()`-grenen helt fra koden.

## Skjult deserialisering: sessioner, caches og filstier

Ikke enhver deserialisering står synligt i egen kode. Kontrollér også disse steder:

- **Sessioner:** PHP serialiserer sessionsdata automatisk. Så længe angribere ikke kan skrive sessionsfilerne, er det ukritisk. Adskilte sessionsstier pr. website beskriver vejledningen [Sikr sessioner i PHP](https://phpsos.de/da/ratgeber/session-sicherheit-php/).
- **Session-serialiserer:** Meddelelsen om [CVE-2026-115560](https://phpsos.de/da/php2shell/cve-2026-115560/) berører en usædvanlig konfiguration, hvor `serialize_handler` omstilles under kørslen via `ini_set()`, og session upload-progress er aktiv. Afhjælpning ifølge php.net: `session.upload_progress.enabled=0` i php.ini eller FPM-pool, derefter genstart af workerne.
- **Caches og køer:** Redis, Memcached eller databasetabeller med serialiserede værdier er kun så sikre som adgangen til dem. De hører ikke åbent ind på nettet.
- **Filfunktioner med stier fra input:** Overgiv aldrig filfunktioner stier fra forespørgslen. Stream wrappers som `phar://` kan ellers udløse uventet behandling. En tilladelsesliste for filnavne beskytter her mere pålideligt end enhver filtrering.
- **Afhængigheder:** Biblioteker med kendte svagheder udvider en angribers muligheder. Hold dem aktuelle, som beskrevet i vejledningen [Kontrollér Composer-afhængigheder for sårbarheder](https://phpsos.de/da/ratgeber/composer-abhaengigkeiten-pruefen/).

## Find risikable steder i koden

En første bestandsopgørelse lykkes med en enkel søgning. Den ændrer intet ved systemet og viser alle kald uden for `vendor`-mappen:

```bash
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'
```

Vurdér hvert fund med tre spørgsmål: Hvor kommer dataene fra? Kan en bruger ændre dem? Er `allowed_classes` sat? Fund med input fra cookies, `$_GET`, `$_POST`, `$_REQUEST` eller eksterne grænseflader har forrang. Erstat dem med JSON, og test omstillingen, før I ruller den ud.

Et yderligere holdepunkt ligger i browseren. Serialiserede PHP-data har en typisk opbygning med typebetegnelser og længdeangivelser. Finder I sådanne værdier i cookies, skjulte formularfelter eller parametre på jeres eget website, har applikationen givet serialiserede data til den besøgende og forventer dem sandsynligvis tilbage. Netop disse steder skal kontrolleres.

For ledelsen er et enkelt spørgsmål til udvikleren eller bureauet nok: „Behandler vores applikation et sted serialiserede data, der kommer udefra, og hvis ja, hvordan er de beskyttet?“ Et klart svar med fundsteder er et godt tegn. Et undvigende svar er en grund til at lade applikationen kontrollere.

## Hvordan hjælper PHP SOS konkret?

- Vi gennemsøger jeres kode for deserialisering med fremmede data og vurderer hvert fund efter tilgængelighed og virkning.
- Vi foreslår konkrete omstillinger til JSON eller sikrede kald og følger jeres udviklere under gennemførelsen.
- Vi kontrollerer afhængigheder for kendte svagheder og dokumenterer status.
- Efter ønske lader vi applikationen kontrollere uafhængigt inden for rammerne af en [sikkerhedsaudit og penetrationstest](https://phpsos.de/da/leistungen/sicherheitsaudit-penetrationstest/).


## Ofte stillede spørgsmål

**Er unserialize() i PHP grundlæggende usikker?**  
Ikke for data, som kun jeres egen applikation producerer, og som ingen kan ændre. Farligt bliver det, så snart brugere eller tredjepart kan påvirke inputtet, f.eks. via cookies, formularfelter eller eksterne grænseflader.

**Er allowed_classes => false nok som beskyttelse?**  
Det forhindrer, at vilkårlige objekter oprettes, og lukker dermed den vigtigste angrebsvej. Til input udefra er JSON alligevel det bedre valg, fordi parseren er enklere og ikke rekonstruerer objekter.

**Hjælper en opdatering af biblioteker mod deserialiseringsangreb?**  
Delvist. Angreb udnytter ofte klasser fra afhængigheder, som udfører handlinger, når et objekt ødelægges eller vækkes. Aktuelle pakker lukker kendte kæder, men den egentlige årsag er unserialize-kaldet med fremmede data.

**Har PHP2Shell noget med deserialisering at gøre?**  
De to hovedsårbarheder ligger i PHP's behandling af forespørgsler. Meddelelsen om CVE-2026-115560 berører derimod en usædvanlig konfiguration af sessionsserialiseringen. Afhjælpning er dér session.upload_progress.enabled=0.

## Kilder

- [OWASP Cheat Sheet Series (Deserialization)](https://cheatsheetseries.owasp.org/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [php.net: Sikkerhedsmeddelelser](https://www.php.net/)
