Stručne: Pri bezpečnosti unserialize v PHP platí jednoduché pravidlo: údaje, ktoré prichádzajú zvonku, nikdy nespracúvajte pomocou unserialize(). Použite namiesto toho JSON. Kde musí unserialize() zo starého kódu zostať, obmedzte ho pomocou allowed_classes => false a pravosť údajov overte podpisom.
Čo sa môže pri unserialize() pokaziť
PHP dokáže polia a objekty pomocou serialize() premeniť na reťazec znakov a pomocou unserialize() ich obnoviť. Je to pohodlné na ukladanie údajov do cookies, cache alebo polí databázy. Problém: unserialize() pritom vytvára aj objekty ľubovoľných tried, ktoré sa dajú v aplikácii načítať.
Pri vytváraní a najneskôr pri zničení takéhoto objektu PHP automaticky volá určité metódy, napríklad __wakeup(), __unserialize() alebo __destruct(). Ak triedy v projekte alebo v knižniciach obsahujú takéto metódy so súborovými, databázovými alebo systémovými akciami, útočník ich môže cez zmanipulované údaje zneužiť. Odborníci hovoria o PHP Object Injection. Podľa existujúcich tried siaha účinok od zmazania jednotlivých súborov až po vykonanie škodlivého kódu.
Pre netechnikov zhrnuté: jediné neuvážené volanie na nesprávnom mieste môže pustiť útočníka hlboko do aplikácie. Táto slabina preto už roky patrí k témam OWASP Top 10.
Bezpečná alternatíva: JSON namiesto unserialize
Takmer vo všetkých prípadoch, keď údaje prichádzajú zvonku alebo opúšťajú server, je správnou voľbou JSON. json_decode() vytvára iba polia, čísla, reťazce a logické hodnoty, nie objekty vlastných tried.
<?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.');
}
// Prevziať iba známe polia s očakávanými typmi
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á časť: aj údaje JSON sú vstupy. Prevezmite iba očakávané polia a skontrolujte typy a rozsahy hodnôt, skôr než s nimi budete pracovať.
Keď je unserialize nevyhnutné: allowed_classes a podpis
V starších aplikáciách sú serializované údaje často v databázach alebo cache, ktoré sa nedajú hneď prestaviť. Riziko potom obmedzíte v dvoch krokoch. Po prvé pomocou allowed_classes zabránite vytváraniu objektov. Po druhé podpisom HMAC zaistíte, že údaje skutočne pochádzajú z vašej aplikácie a cestou sa nezmenili.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nikdy v repozitári 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;
}
Ak sa určité triedy predsa potrebujú, uveďte ich jednotlivo v allowed_classes, namiesto povolenia všetkých. Kľúč patrí do konfigurácie servera alebo do úložiska tajomstiev a po incidente sa musí obnoviť.
Existujúce údaje postupne prestaviť na JSON
V rozrastených aplikáciách ležia serializované hodnoty v databáze často roky. Prestavba na jeden krok je potom riskantná. Osvedčil sa prechod: aplikácia číta oba formáty, ale zapisuje už len JSON. Pri čítaní starej hodnoty sa jednorazovo rozbalí s allowed_classes => false a okamžite sa zapíše späť ako JSON. Po určitom čase je väčšina záznamov prestavená a zvyšok vybaví jednorazový skript.
Počas prechodu zaznamenávajte, ako často sa ešte číta starý formát, a aj to, keď podpisy nesedia. Takéto zlyhania môžu naznačovať pokusy o manipuláciu a patria do monitoringu. Až keď sa starý formát už neobjavuje, odstráňte vetvu unserialize() z kódu úplne.
Skrytá deserializácia: session, cache a cesty k súborom
Nie každá deserializácia je viditeľná vo vlastnom kóde. Skontrolujte aj tieto miesta:
- Session: PHP serializuje údaje session automaticky. Pokiaľ útočníci nemôžu zapisovať súbory session, je to nekritické. Oddelené cesty pre session pre každý web popisuje príručka Zabezpečiť session v PHP.
- Serializér session: Upozornenie k CVE-2026-115560 sa týka nezvyčajnej konfigurácie, pri ktorej sa
serialize_handlerza behu prestaví cezini_set()a je aktívny session upload progress. Náprava podľa php.net:session.upload_progress.enabled=0v php.ini alebo poole FPM, potom reštart workerov. - Cache a fronty: Redis, Memcached alebo tabuľky databázy so serializovanými hodnotami sú také bezpečné ako prístup k nim. Nepatria otvorene do siete.
- Súborové funkcie s cestami zo vstupov: Súborovým funkciám nikdy nepredávajte cesty z požiadavky. Obaly streamov ako
phar://môžu inak spustiť neočakávané spracovanie. Zoznam povolených názvov súborov tu chráni spoľahlivejšie než akékoľvek filtrovanie. - Závislosti: Knižnice so známymi zraniteľnosťami rozširujú možnosti útočníka. Udržiavajte ich aktuálne, ako je opísané v príručke Kontrola závislostí Composeru na zraniteľnosti.
Nájsť rizikové miesta v kóde
Prvú inventúru zvládnete jednoduchým hľadaním. Nič nemení na systéme a ukáže všetky volania mimo adresára vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Každý nález posúďte tromi otázkami: odkiaľ údaje pochádzajú? Môže ich používateľ zmeniť? Je nastavené allowed_classes? Prednosť majú nálezy so vstupmi z cookies, $_GET, $_POST, $_REQUEST alebo externých rozhraní. Nahraďte ich JSON a prestavbu otestujte, skôr než ju nasadíte.
Ďalší záchytný bod je v prehliadači. Serializované údaje PHP majú typickú stavbu s identifikátormi typov a údajmi o dĺžke. Ak nájdete také hodnoty v cookies, skrytých poliach formulárov alebo parametroch vlastného webu, aplikácia odovzdala serializované údaje návštevníkovi a pravdepodobne ich očakáva späť. Práve tieto miesta treba preveriť.
Pre vedenie stačí jednoduchá otázka vývojárom alebo agentúre: „Spracúva naša aplikácia niekde serializované údaje, ktoré prichádzajú zvonku, a ak áno, ako sú chránené?“ Jasná odpoveď s uvedením miest je dobré znamenie. Vyhýbavá odpoveď je dôvodom nechať aplikáciu skontrolovať.
Ako konkrétne pomáha PHP SOS?
- Prehľadáme váš kód kvôli deserializácii s cudzími údajmi a každý nález posúdime podľa dosiahnuteľnosti a dosahu.
- Navrhujeme konkrétne prechody na JSON alebo zabezpečené volania a sprevádzame vašich vývojárov pri realizácii.
- Závislosti kontrolujeme na známe zraniteľnosti a stav zdokumentujeme.
- Na želanie necháme aplikáciu nezávisle preveriť v rámci bezpečnostného auditu a penetračného testu.
Časté otázky
Je unserialize() v PHP zásadne nebezpečné?
Nie pre údaje, ktoré vytvára iba vaša vlastná aplikácia a ktoré nikto nemôže zmeniť. Nebezpečné to začne byť, len čo môžu vstup ovplyvniť používatelia alebo tretie strany, napríklad cez cookies, polia formulárov alebo externé rozhrania.
Stačí allowed_classes => false ako ochrana?
Bráni vytváraniu ľubovoľných objektov, a tým uzatvára najdôležitejšiu cestu útoku. Pre vstupy zvonku je JSON napriek tomu lepšia voľba, pretože parser je jednoduchší a neobnovuje objekty.
Pomáha aktualizácia knižníc proti útokom deserializáciou?
Čiastočne. Útoky často využívajú triedy zo závislostí, ktoré pri zničení alebo prebudení objektu vykonávajú akcie. Aktuálne balíky uzatvárajú známe reťazce, skutočnou príčinou je však volanie unserialize s cudzími údajmi.
Súvisí PHP2Shell s deserializáciou?
Obe hlavné zraniteľnosti sú v spracovaní požiadaviek v PHP. Upozornenie k CVE-2026-115560 sa naopak týka nezvyčajnej konfigurácie serializácie session. Náprava je tam session.upload_progress.enabled=0.