# Несигурна десериализация във PHP: unserialize и сигурност

> Безопасен PHP unserialize: защо обектите от въвеждане от потребителя са опасни, как JSON, allowed_classes и подписите защитават и къде да търсите.

Източник: https://phpsos.de/bg/ratgeber/deserialisierung-php/  
Актуалност: 25.10.2026  
Доставчик: PHP SOS, Спешна помощ за PHP и сигурност на сървъри. Незабавна помощ по WhatsApp: https://wa.me/491736494914?text=%D0%97%D0%B4%D1%80%D0%B0%D0%B2%D0%B5%D0%B9%D1%82%D0%B5%2C%20PHP%20SOS%2C%20%D0%B8%D0%BC%D0%B0%D0%BC%D0%B5%20%D0%BD%D1%83%D0%B6%D0%B4%D0%B0%20%D0%BE%D1%82%20%D0%BF%D0%BE%D0%BC%D0%BE%D1%89%20%D1%81%20%D0%BD%D0%B0%D1%88%D0%B8%D1%8F%20PHP%20%D1%81%D1%8A%D1%80%D0%B2%D1%8A%D1%80.%20%D0%A4%D0%B8%D1%80%D0%BC%D0%B0%3A%20%E2%80%A6%20%D0%A3%D0%B5%D0%B1%D1%81%D0%B0%D0%B9%D1%82%3A%20%E2%80%A6

**Накратко:** При сигурността на unserialize във PHP важи просто правило: данни, които идват отвън, никога не обработвайте с `unserialize()`. Вземете вместо това JSON. Където `unserialize()` от стар код трябва да остане, ограничете го с `allowed_classes => false` и проверявайте автентичността на данните с подпис.

## Какво може да се обърка при unserialize()

PHP може да превръща масиви и обекти със `serialize()` в низ и да ги възстановява с `unserialize()`. Това е удобно за съхраняване на данни в бисквитки, кешове или полета на база данни. Проблемът: `unserialize()` създава при това и обекти на произволни класове, които могат да бъдат заредени в приложението.

При създаването и най-късно при унищожаването на такъв обект PHP автоматично извиква определени методи, например `__wakeup()`, `__unserialize()` или `__destruct()`. Ако класове в проекта или в библиотеките съдържат такива методи с действия върху файлове, база данни или система, атакуващ може да ги използва не по предназначение чрез манипулирани данни. Специалистите говорят за PHP Object Injection. В зависимост от наличните класове ефектът варира от изтриване на отделни файлове до изпълнение на злонамерен код.

Обобщено за нетехнически лица: едно-единствено необмислено извикване на грешното място може да допусне атакуващ дълбоко в приложението. Затова тази слабост от години е сред темите на OWASP Top 10.

## Сигурната алтернатива: JSON вместо unserialize

За почти всички случаи, в които данните идват отвън или напускат сървъра, JSON е правилният избор. `json_decode()` създава само масиви, числа, низове и булеви стойности, но не и обекти на собствени класове.

```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.');
    }

    // Приемат се само известни полета с очаквани типове
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}
```

Важна е втората част: и JSON данните са въвеждане. Приемайте само очакваните полета и проверявайте типовете и диапазоните на стойностите, преди да работите с тях.

## Когато unserialize е неизбежна: allowed_classes и подпис

В по-стари приложения сериализираните данни често са в бази данни или кешове, които не могат веднага да се преустроят. Тогава ограничавате риска на две стъпки. Първо, с `allowed_classes` предотвратявате създаването на обекти. Второ, с HMAC подпис гарантирате, че данните наистина идват от Вашето приложение и не са променени по пътя.

```php
<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // никога в хранилището на кода

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;
}
```

Ако все пак са нужни определени класове, назовете ги поотделно в `allowed_classes`, вместо да допускате всички. Ключът принадлежи в конфигурацията на сървъра или в хранилище за тайни и след инцидент трябва да се подмени.

### Постепенно преминаване на съществуващите данни към JSON

В нараснали приложения сериализирани стойности често лежат с години в базата данни. Преминаване с една стъпка тогава е рисковано. Доказа се преход: приложението чете двата формата, но записва само JSON. При четене на стара стойност тя се разопаковва еднократно с `allowed_classes => false` и веднага се записва обратно като JSON. След известно време повечето записи са преустроени, а еднократен скрипт довършва останалото.

Протоколирайте по време на прехода колко често още се чете старият формат, както и когато подписите не съвпадат. Такива неуспехи могат да сочат към опити за манипулация и принадлежат към наблюдението. Едва когато вече не се появява стар формат, премахнете клона с `unserialize()` изцяло от кода.

## Скрита десериализация: сесии, кешове и пътища до файлове

Не всяка десериализация е видима в собствения код. Проверете и тези места:

- **Сесии:** PHP сериализира данните на сесиите автоматично. Докато атакуващите не могат да записват файловете на сесиите, това е некритично. Разделени пътища за сесиите за всеки уебсайт описва наръчникът [Защита на сесиите в PHP](https://phpsos.de/bg/ratgeber/session-sicherheit-php/).
- **Сериализатор на сесиите:** Съобщението за [CVE-2026-115560](https://phpsos.de/bg/php2shell/cve-2026-115560/) засяга необичайна конфигурация, при която `serialize_handler` се превключва по време на изпълнение чрез `ini_set()`, а upload progress на сесиите е активен. Мярка според php.net: `session.upload_progress.enabled=0` в php.ini или FPM пула, след това рестарт на worker процесите.
- **Кешове и опашки:** Redis, Memcached или таблици на база данни със сериализирани стойности са толкова сигурни, колкото е достъпът до тях. Те не принадлежат отворени в мрежата.
- **Файлови функции с пътища от въведени данни:** Никога не предавайте на файлови функции пътища от заявката. Stream wrapper-и като `phar://` иначе могат да предизвикат неочаквана обработка. Списък с разрешени имена на файлове защитава тук по-надеждно от всяко филтриране.
- **Зависимости:** Библиотеки с известни слабости разширяват възможностите на атакуващия. Поддържайте ги актуални, както е описано в наръчника [Проверка на Composer зависимостите за уязвимости](https://phpsos.de/bg/ratgeber/composer-abhaengigkeiten-pruefen/).

## Откриване на рискови места в кода

Първа инвентаризация се получава с просто търсене. То не променя нищо в системата и показва всички извиквания извън директорията `vendor`:

```bash
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'
```

Оценете всяко съвпадение с три въпроса: откъде идват данните? Може ли потребител да ги промени? Зададена ли е `allowed_classes`? Съвпаденията с данни от бисквитки, `$_GET`, `$_POST`, `$_REQUEST` или външни интерфейси имат предимство. Заменете ги с JSON и тествайте преустройството, преди да го внедрите.

Друга опорна точка е в браузъра. Сериализираните PHP данни имат типична структура с идентификатори на типове и данни за дължина. Ако намерите такива стойности в бисквитки, скрити полета на формуляри или параметри на Вашия собствен уебсайт, приложението е предало сериализирани данни на посетителя и вероятно ги очаква обратно. Именно тези места трябва да се проверят.

За ръководството е достатъчен един прост въпрос към разработчиците или агенцията: „Обработва ли нашето приложение някъде сериализирани данни, които идват отвън, и ако да, как са защитени?“ Ясен отговор с конкретни места е добър знак. Уклончив отговор е причина да се поиска проверка на приложението.

## Как конкретно помага PHP SOS?

- Претърсваме Вашия код за десериализация с чужди данни и оценяваме всяка находка според достъпността и въздействието.
- Предлагаме конкретни преустройства към JSON или обезопасени извиквания и подкрепяме Вашите разработчици при реализацията.
- Проверяваме зависимостите за известни слабости и документираме състоянието.
- При желание организираме независима проверка на приложението в рамките на [одит на сигурността и тест за проникване](https://phpsos.de/bg/leistungen/sicherheitsaudit-penetrationstest/).


## Често задавани въпроси

**Принципно несигурна ли е unserialize() във PHP?**  
Не за данни, които се генерират само от Вашето приложение и които никой не може да промени. Опасно става щом потребители или трети лица могат да влияят на въведеното, например чрез бисквитки, полета на формуляри или външни интерфейси.

**Достатъчно ли е allowed_classes => false като защита?**  
Така се предотвратява създаването на произволни обекти и се затваря най-важният път за атака. За входни данни отвън JSON все пак е по-добрият избор, защото парсерът е по-прост и не възстановява обекти.

**Помага ли актуализацията на библиотеките срещу атаки чрез десериализация?**  
Отчасти. Атаките често използват класове от зависимости, които при унищожаване или събуждане на обект изпълняват действия. Актуалните пакети затварят известни вериги, но истинската причина е извикването на unserialize с чужди данни.

**Има ли PHP2Shell нещо общо с десериализацията?**  
Двете основни уязвимости са в обработката на заявките на PHP. Съобщението за CVE-2026-115560 пък засяга необичайна конфигурация на сериализацията на сесиите. Мярката там е session.upload_progress.enabled=0.

## Източници

- [OWASP Cheat Sheet Series (Deserialization)](https://cheatsheetseries.owasp.org/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [php.net: Съобщения за сигурност](https://www.php.net/)
