Stručně: U bezpečnosti PHP unserialize platí jednoduché pravidlo: data, která přicházejí zvenčí, nikdy nezpracovávat pomocí unserialize(). Použijte místo toho JSON. Kde unserialize() ze starého kódu musí zůstat, omezte ho pomocí allowed_classes => false a pravost dat ověřujte podpisem.
Co se může u unserialize() pokazit
PHP umí pomocí serialize() převést pole a objekty na řetězec a pomocí unserialize() je znovu obnovit. Je to pohodlné pro ukládání dat do cookies, cache nebo polí databáze. Problém: unserialize() přitom vytváří i objekty libovolných tříd, které lze v aplikaci načíst.
Při vytvoření a nejpozději při zničení takového objektu PHP automaticky volá určité metody, například __wakeup(), __unserialize() nebo __destruct(). Pokud třídy v projektu nebo v knihovnách obsahují takové metody s akcemi se soubory, databází nebo systémem, může je útočník pomocí zmanipulovaných dat zneužít. Odborníci hovoří o PHP Object Injection. Podle existujících tříd sahá účinek od smazání jednotlivých souborů po spuštění škodlivého kódu.
Pro netechniky shrnuto: jediné nepromyšlené volání na špatném místě může útočníka pustit hluboko do aplikace. Tato slabina proto už léta patří k tématům OWASP Top 10.
Bezpečná alternativa: JSON místo unserialize
Pro téměř všechny případy, kdy data přicházejí zvenčí nebo opouštějí server, je JSON správnou volbou. json_decode() vytváří jen pole, čísla, řetězce a logické hodnoty, žádné objekty vlastních tříd.
<?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.');
}
// Převzít jen známá pole s očekávanými typy
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Důležitá je druhá část: i data JSON jsou vstupy. Přebírejte jen očekávaná pole a před dalším zpracováním kontrolujte typy a rozsahy hodnot.
Když je unserialize nevyhnutelné: allowed_classes a podpis
Ve starších aplikacích bývají serializovaná data často v databázích nebo cache, které nelze okamžitě přestavět. Pak riziko omezíte ve dvou krocích. Za prvé pomocí allowed_classes zabráníte tomu, aby se vytvářely objekty. Za druhé podpisem HMAC zajistíte, že data skutečně pocházejí z vaší aplikace a cestou se nezměnila.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nikdy v repozitáři kódu
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;
}
Pokud jsou přece jen potřeba určité třídy, uveďte je jednotlivě v allowed_classes, místo abyste povolili všechny. Klíč patří do konfigurace serveru nebo do úložiště tajných údajů a po incidentu se musí obnovit.
Postupný přechod stávajících dat na JSON
U rozrostlých aplikací leží serializované hodnoty v databázi často řadu let. Přechod na jeden zátah je pak riskantní. Osvědčil se postupný přechod: aplikace čte oba formáty, ale zapisuje už jen JSON. Při čtení staré hodnoty se jednorázově rozbalí s allowed_classes => false a okamžitě se uloží zpět jako JSON. Po určité době je většina záznamů převedena a zbytek vyřídí jednorázový skript.
Během přechodu protokolujte, jak často se ještě čte starý formát, a také když podpisy nesedí. Takové neúspěchy mohou ukazovat na pokusy o manipulaci a patří do monitoringu. Teprve když se starý formát už neobjevuje, odstraňte větev unserialize() z kódu úplně.
Skrytá deserializace: session, cache a cesty k souborům
Ne každá deserializace stojí viditelně ve vlastním kódu. Zkontrolujte i tato místa:
- Session: PHP serializuje data session automaticky. Dokud útočníci nemohou soubory session zapisovat, je to nekritické. Oddělené cesty session pro každý web popisuje příručka Zabezpečení relací v PHP.
- Serializátor session: Upozornění k CVE-2026-115560 se týká neobvyklé konfigurace, při níž se
serialize_handlerza běhu přepíná přesini_set()a je aktivní session upload progress. Náprava podle php.net:session.upload_progress.enabled=0v php.ini nebo poolu FPM, poté restart workerů. - Cache a fronty: Redis, Memcached nebo databázové tabulky se serializovanými hodnotami jsou tak bezpečné jako přístup k nim. Nepatří otevřeně do sítě.
- Souborové funkce s cestami ze vstupů: Souborovým funkcím nikdy nepředávejte cesty z požadavku. Obálky streamů jako
phar://mohou jinak vyvolat neočekávané zpracování. Seznam povolených názvů souborů chrání spolehlivěji než jakékoli filtrování. - Závislosti: Knihovny se známými zranitelnostmi rozšiřují možnosti útočníka. Udržujte je aktuální, jak popisuje příručka Kontrola závislostí Composeru na zranitelnosti.
Jak najít riziková místa v kódu
První inventuru zvládnete jednoduchým hledáním. Nic v systému nemění a ukáže všechna volání mimo adresář vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Každý nález vyhodnoťte třemi otázkami: Odkud data pocházejí? Může je uživatel změnit? Je nastaveno allowed_classes? Přednost mají nálezy se vstupy z cookies, $_GET, $_POST, $_REQUEST nebo externích rozhraní. Nahraďte je JSON a přechod otestujte, než ho nasadíte.
Další vodítko je v prohlížeči. Serializovaná data PHP mají typickou strukturu s označením typů a údaji o délce. Pokud takové hodnoty najdete v cookies, skrytých polích formulářů nebo parametrech vlastního webu, předala aplikace návštěvníkovi serializovaná data a pravděpodobně je očekává zpět. Právě tato místa je třeba zkontrolovat.
Pro vedení stačí jednoduchá otázka na vývojáře nebo agenturu: „Zpracovává naše aplikace někde serializovaná data, která přicházejí zvenčí, a pokud ano, jak jsou chráněna?“ Jasná odpověď s konkrétními místy je dobré znamení. Vyhýbavá odpověď je důvod nechat aplikaci zkontrolovat.
Jak konkrétně pomáhá PHP SOS?
- Prohledáváme váš kód na deserializaci cizích dat a každý nález hodnotíme podle dosažitelnosti a dopadu.
- Navrhujeme konkrétní přechod na JSON nebo zabezpečená volání a provázíme vaše vývojáře při provedení.
- Kontrolujeme závislosti na známé zranitelnosti a dokumentujeme stav.
- Na přání necháme aplikaci nezávisle ověřit v rámci bezpečnostního auditu a penetračního testu.
Časté dotazy
Je unserialize() v PHP zásadně nebezpečné?
Ne pro data, která vytváří jen vaše vlastní aplikace a která nikdo nemůže změnit. Nebezpečné se to stává, jakmile mohou vstup ovlivnit uživatelé nebo třetí strany, například přes cookies, pole formulářů nebo externí rozhraní.
Stačí jako ochrana allowed_classes => false?
Brání tomu, aby vznikaly libovolné objekty, a uzavírá tak nejdůležitější útočnou cestu. Pro vstupy zvenčí je ale JSON přesto lepší volba, protože parser je jednodušší a neobnovuje objekty.
Pomáhá aktualizace knihoven proti útokům na deserializaci?
Částečně. Útoky často využívají třídy ze závislostí, které při zničení nebo probuzení objektu provádějí akce. Aktuální balíčky uzavírají známé řetězce, skutečnou příčinou je ale volání unserialize s cizími daty.
Souvisí PHP2Shell s deserializací?
Obě hlavní zranitelnosti leží ve zpracování požadavků v PHP. Upozornění k CVE-2026-115560 se naproti tomu týká neobvyklé konfigurace serializace session. Nápravou je tam session.upload_progress.enabled=0.