Kort sagt: För PHP unserialize-säkerhet gäller en enkel regel: Bearbeta aldrig data som kommer utifrån med unserialize(). Ta JSON i stället. Där unserialize() måste vara kvar i äldre kod, begränsa det med allowed_classes => false och kontrollera dataens äkthet med en signatur.
Vad som kan gå fel med unserialize()
PHP kan med serialize() omvandla arrayer och objekt till en teckensträng och med unserialize() återställa dem. Det är bekvämt för att lagra data i cookies, cacher eller databasfält. Problemet: unserialize() skapar då också objekt av godtyckliga klasser som kan laddas i applikationen.
När ett sådant objekt skapas och senast när det förstörs anropar PHP automatiskt vissa metoder, till exempel __wakeup(), __unserialize() eller __destruct(). Innehåller klasser i projektet eller i bibliotek sådana metoder med fil-, databas- eller systemåtgärder kan en angripare missbruka dem via manipulerade data. Fackfolk talar om PHP Object Injection. Beroende på vilka klasser som finns sträcker sig verkan från att radera enskilda filer till att köra skadlig kod.
Sammanfattat för icke-tekniker: Ett enda obetänksamt anrop på fel ställe kan släppa in en angripare långt in i applikationen. Den här svagheten hör därför sedan år till ämnena i OWASP Top 10.
Det säkra alternativet: JSON i stället för unserialize
För nästan alla fall där data kommer utifrån eller lämnar servern är JSON rätt val. json_decode() skapar bara arrayer, tal, teckensträngar och sanningsvärden, inga objekt av egna 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.');
}
// Ta bara över kända fält med förväntade typer
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Viktig är den andra delen: Även JSON-data är indata. Ta bara över förväntade fält och kontrollera typer och värdeintervall innan ni arbetar med dem.
När unserialize är oundvikligt: allowed_classes och signatur
I äldre applikationer ligger serialiserade data ofta i databaser eller cacher som inte går att ställa om direkt. Då begränsar ni risken i två steg. Först förhindrar ni med allowed_classes att objekt skapas. Sedan säkerställer ni med en HMAC-signatur att dataen verkligen kommer från er applikation och inte har ändrats på vägen.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // aldrig i kodförvaret
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;
}
Behövs ändå vissa klasser, ange dem en och en i allowed_classes i stället för att tillåta alla. Nyckeln hör hemma i serverkonfigurationen eller i ett hemlighetslager och måste förnyas efter en incident.
Ställ gradvis om befintliga data till JSON
I etablerade applikationer ligger serialiserade värden ofta i åratal i databasen. En omställning i ett enda steg är då riskabel. En övergång har visat sig fungera: Applikationen läser båda formaten men skriver bara JSON. När ett gammalt värde läses packas det en gång upp med allowed_classes => false och skrivs direkt tillbaka som JSON. Efter en tid är de flesta poster omställda, och ett engångsskript tar hand om resten.
Logga under övergången hur ofta det gamla formatet fortfarande läses, och även när signaturer inte stämmer. Sådana misslyckanden kan tyda på manipulationsförsök och hör hemma i övervakningen. Först när inget gammalt format dyker upp längre tar ni bort unserialize()-grenen helt ur koden.
Dold deserialisering: sessioner, cacher och filsökvägar
Inte all deserialisering syns i den egna koden. Kontrollera också de här ställena:
- Sessioner: PHP serialiserar sessionsdata automatiskt. Så länge angripare inte kan skriva sessionsfilerna är det ukritiskt. Åtskilda sessionssökvägar per webbplats beskriver guiden Säkra sessioner i PHP.
- Sessionsserialiserare: Meddelandet om CVE-2026-115560 berör en ovanlig konfiguration där
serialize_handlerställs om under körning viaini_set()och session upload progress är aktivt. Åtgärd enligt php.net:session.upload_progress.enabled=0i php.ini eller FPM-pool, därefter omstart av workers. - Cacher och köer: Redis, Memcached eller databastabeller med serialiserade värden är bara så säkra som åtkomsten till dem. De hör inte hemma öppet på nätet.
- Filfunktioner med sökvägar från indata: Lämna aldrig sökvägar från förfrågan till filfunktioner. Stream wrappers som
phar://kan annars utlösa oväntad bearbetning. En tillåtelselista för filnamn skyddar här tillförlitligare än all filtrering. - Beroenden: Bibliotek med kända sårbarheter vidgar en angripares möjligheter. Håll dem aktuella, som beskrivs i guiden Kontrollera Composer-beroenden efter sårbarheter.
Hitta riskfyllda ställen i koden
En första inventering lyckas med en enkel sökning. Den ändrar ingenting i systemet och visar alla anrop utanför katalogen vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Bedöm varje träff med tre frågor: Varifrån kommer dataen? Kan en användare ändra den? Är allowed_classes satt? Träffar med indata från cookies, $_GET, $_POST, $_REQUEST eller externa gränssnitt har företräde. Ersätt dem med JSON och testa omställningen innan ni rullar ut den.
En ytterligare hållpunkt finns i webbläsaren. Serialiserade PHP-data har en typisk uppbyggnad med typbeteckningar och längdangivelser. Hittar ni sådana värden i cookies, dolda formulärfält eller parametrar på er egen webbplats har applikationen lämnat serialiserade data till besökaren och förväntar sig sannolikt få tillbaka dem. Just de ställena ska granskas.
För ledningen räcker en enkel fråga till utvecklare eller byrå: ”Bearbetar vår applikation någonstans serialiserade data som kommer utifrån, och i så fall hur är de skyddade?” Ett tydligt svar med fyndplatser är ett gott tecken. Ett undvikande svar är ett skäl att låta granska applikationen.
Hur hjälper PHP SOS konkret?
- Vi söker igenom er kod efter deserialisering med främmande data och bedömer varje fynd efter nåbarhet och påverkan.
- Vi föreslår konkreta omställningar till JSON eller skyddade anrop och stöttar era utvecklare vid genomförandet.
- Vi granskar beroenden efter kända sårbarheter och dokumenterar nivån.
- På begäran låter vi granska applikationen oberoende inom ramen för en säkerhetsgranskning och ett penetrationstest.
Vanliga frågor
Är unserialize() i PHP principiellt osäkert?
Inte för data som bara er egen applikation skapar och som ingen kan ändra. Det blir farligt så fort användare eller tredje part kan påverka indata, till exempel via cookies, formulärfält eller externa gränssnitt.
Räcker allowed_classes => false som skydd?
Det förhindrar att godtyckliga objekt skapas och stänger därmed den viktigaste angreppsvägen. För indata utifrån är JSON ändå det bättre valet, eftersom tolken är enklare och inte återskapar objekt.
Hjälper en uppdatering av bibliotek mot deserialiseringsangrepp?
Delvis. Angrepp utnyttjar ofta klasser ur beroenden som utför åtgärder när ett objekt förstörs eller väcks till liv. Aktuella paket stänger kända kedjor, men den egentliga orsaken är anropet av unserialize med främmande data.
Har PHP2Shell något med deserialisering att göra?
De två huvudsårbarheterna finns i PHP:s hantering av förfrågningar. Meddelandet om CVE-2026-115560 berör däremot en ovanlig konfiguration av sessionsserialiseringen. Åtgärden där är session.upload_progress.enabled=0.