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
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
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.
- Session-serialiserer: Meddelelsen om CVE-2026-115560 berører en usædvanlig konfiguration, hvor
serialize_handleromstilles under kørslen viaini_set(), og session upload-progress er aktiv. Afhjælpning ifølge php.net:session.upload_progress.enabled=0i 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.
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:
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.
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.