# Nezabezpečená deserializace v PHP: unserialize a bezpečnost

> Bezpečné použití PHP unserialize: proč jsou objekty ze vstupů uživatelů nebezpečné, jak chrání JSON, allowed_classes a podpisy a kde hledat riziková místa.

Zdroj: https://phpsos.de/cs/ratgeber/deserialisierung-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**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
<?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
<?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](https://phpsos.de/cs/ratgeber/session-sicherheit-php/).
- **Serializátor session:** Upozornění k [CVE-2026-115560](https://phpsos.de/cs/php2shell/cve-2026-115560/) se týká neobvyklé konfigurace, při níž se `serialize_handler` za běhu přepíná přes `ini_set()` a je aktivní session upload progress. Náprava podle php.net: `session.upload_progress.enabled=0` v 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](https://phpsos.de/cs/ratgeber/composer-abhaengigkeiten-pruefen/).

## 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`:

```bash
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](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Č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.

## Zdroje

- [OWASP Cheat Sheet Series (Deserialization)](https://cheatsheetseries.owasp.org/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [php.net: bezpečnostní oznámení](https://www.php.net/)
