Aktualna sytuacja: PHP2Shell. Dwie krytyczne luki w PHP (CVSS 9,2) umożliwiają wykonanie złośliwego kodu bez logowania. Poprawki od 21.10.2026.Co należy teraz zrobić →

Niebezpieczna deserializacja w PHP: unserialize i bezpieczeństwo

unserialize() z obcymi danymi może w najgorszym razie doprowadzić do wykonania kodu. Jak zastąpić je JSON-em, ograniczyć tam, gdzie musi zostać, i znaleźć ryzykowne miejsca w kodzie.

Stan 5 min czytaniaPoradniki

Krótko: W bezpieczeństwie PHP unserialize obowiązuje prosta zasada: danych pochodzących z zewnątrz nigdy nie przetwarzać przez unserialize(). Proszę użyć zamiast tego JSON. Tam, gdzie unserialize() musi pozostać ze starego kodu, proszę ograniczyć je przez allowed_classes => false i sprawdzać autentyczność danych podpisem.

Co może pójść źle przy unserialize()

PHP potrafi zamienić tablice i obiekty za pomocą serialize() na ciąg znaków i odtworzyć je za pomocą unserialize(). Jest to wygodne do zapisywania danych w ciasteczkach, pamięciach podręcznych lub polach bazy danych. Problem: unserialize() tworzy przy tym także obiekty dowolnych klas, które mogą być załadowane w aplikacji.

Przy tworzeniu, a najpóźniej przy niszczeniu takiego obiektu PHP automatycznie wywołuje określone metody, takie jak __wakeup(), __unserialize() lub __destruct(). Jeśli klasy w projekcie lub w bibliotekach mają takie metody z akcjami na plikach, bazie danych lub systemie, atakujący może je wykorzystać niezgodnie z przeznaczeniem przez zmanipulowane dane. Specjaliści mówią o PHP Object Injection. W zależności od istniejących klas skutki sięgają od usunięcia pojedynczych plików po wykonanie złośliwego kodu.

Dla osób nietechnicznych w skrócie: jedno nieprzemyślane wywołanie w niewłaściwym miejscu może wpuścić atakującego głęboko do aplikacji. Ta podatność od lat należy dlatego do tematów OWASP Top 10.

Bezpieczna alternatywa: JSON zamiast unserialize

W niemal wszystkich przypadkach, gdy dane przychodzą z zewnątrz lub opuszczają serwer, JSON jest właściwym wyborem. json_decode() tworzy tylko tablice, liczby, ciągi znaków i wartości logiczne, bez obiektów własnych klas.

<?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.');
    }

    // Przejmować tylko znane pola z oczekiwanymi typami
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

Ważna jest druga część: dane JSON również są danymi wejściowymi. Proszę przejmować tylko oczekiwane pola i sprawdzać typy oraz zakresy wartości, zanim zacznie się z nimi pracować.

Gdy unserialize jest nieunikniony: allowed_classes i podpis

W starszych aplikacjach zserializowane dane często tkwią w bazach danych lub pamięciach podręcznych, których nie da się od razu przestawić. Wtedy ryzyko ogranicza się w dwóch krokach. Po pierwsze, za pomocą allowed_classes zapobiega się tworzeniu obiektów. Po drugie, podpisem HMAC upewnia się, że dane faktycznie pochodzą z Państwa aplikacji i nie zostały po drodze zmienione.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nigdy w repozytorium kodu

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;
}

Jeśli jednak potrzebne są określone klasy, proszę wymienić je pojedynczo w allowed_classes, zamiast dopuszczać wszystkie. Klucz należy do konfiguracji serwera lub magazynu sekretów i po incydencie musi zostać odnowiony.

Stopniowe przestawianie istniejących danych na JSON

W rozwijanych latami aplikacjach zserializowane wartości często leżą w bazie danych przez lata. Przestawienie w jednym kroku jest wtedy ryzykowne. Sprawdziło się przejście: aplikacja czyta oba formaty, ale zapisuje już tylko JSON. Przy odczycie starej wartości jest ona jednorazowo rozpakowywana z allowed_classes => false i od razu zapisywana z powrotem jako JSON. Po pewnym czasie większość wpisów jest przestawiona, a resztę załatwia jednorazowy skrypt.

W czasie przejścia proszę protokołować, jak często nadal czytany jest stary format, a także gdy podpisy się nie zgadzają. Takie niepowodzenia mogą wskazywać na próby manipulacji i należą do monitoringu. Dopiero gdy stary format już się nie pojawia, proszę całkowicie usunąć gałąź unserialize() z kodu.

Ukryta deserializacja: sesje, pamięci podręczne i ścieżki plików

Nie każda deserializacja jest widoczna we własnym kodzie. Proszę sprawdzić także te miejsca:

  • Sesje: PHP serializuje dane sesji automatycznie. Dopóki atakujący nie mogą zapisywać plików sesji, jest to niekrytyczne. Oddzielne ścieżki sesji dla każdej strony opisuje poradnik Zabezpieczanie sesji w PHP.
  • Serializery sesji: Komunikat o CVE-2026-115560 dotyczy nietypowej konfiguracji, w której serialize_handler jest przestawiany w czasie działania przez ini_set(), a session upload progress jest aktywny. Środek zaradczy według php.net: session.upload_progress.enabled=0 w php.ini lub puli FPM, potem restart procesów roboczych.
  • Pamięci podręczne i kolejki: Redis, Memcached lub tabele bazy danych z zserializowanymi wartościami są tak bezpieczne, jak dostęp do nich. Nie należą otwarcie do sieci.
  • Funkcje plikowe ze ścieżkami z danych wejściowych: funkcjom plikowym nigdy nie przekazywać ścieżek z żądania. Wrappery strumieni, takie jak phar://, mogłyby w przeciwnym razie wywołać nieoczekiwane przetwarzanie. Lista dozwolonych nazw plików chroni tu bardziej niezawodnie niż jakiekolwiek filtrowanie.
  • Zależności: biblioteki ze znanymi podatnościami rozszerzają możliwości atakującego. Proszę utrzymywać je w aktualnym stanie, jak opisuje poradnik composer audit: kontrola zależności pod kątem luk.

Wyszukiwanie ryzykownych miejsc w kodzie

Pierwszą inwentaryzację umożliwia proste wyszukiwanie. Niczego nie zmienia w systemie i pokazuje wszystkie wywołania poza katalogiem vendor:

grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'

Każde trafienie proszę ocenić trzema pytaniami: skąd pochodzą dane? Czy użytkownik może je zmienić? Czy ustawiono allowed_classes? Trafienia z danymi z ciasteczek, $_GET, $_POST, $_REQUEST lub zewnętrznych interfejsów mają pierwszeństwo. Proszę zastąpić je JSON-em i przetestować przestawienie, zanim zostanie wdrożone.

Kolejna wskazówka leży w przeglądarce. Zserializowane dane PHP mają typową budowę z oznaczeniami typów i podaniem długości. Jeśli znajdą Państwo takie wartości w ciasteczkach, ukrytych polach formularzy lub parametrach własnej strony, aplikacja przekazała zserializowane dane odwiedzającemu i prawdopodobnie oczekuje ich z powrotem. Właśnie te miejsca należy sprawdzić.

Dla zarządu wystarczy proste pytanie do programistów lub agencji: „Czy nasza aplikacja gdzieś przetwarza zserializowane dane pochodzące z zewnątrz, a jeśli tak, jak są chronione?” Jasna odpowiedź ze wskazaniem miejsc to dobry znak. Wymijająca odpowiedź jest powodem, by zlecić kontrolę aplikacji.

Jak konkretnie pomaga PHP SOS?

  • Przeszukujemy Państwa kod pod kątem deserializacji z obcymi danymi i oceniamy każde znalezisko według osiągalności i skutków.
  • Proponujemy konkretne przejścia na JSON lub zabezpieczone wywołania i wspieramy Państwa programistów we wdrożeniu.
  • Sprawdzamy zależności pod kątem znanych podatności i dokumentujemy stan.
  • Na życzenie zlecamy niezależną kontrolę aplikacji w ramach audytu bezpieczeństwa i testu penetracyjnego.

Częste pytania

Czy unserialize() w PHP jest zasadniczo niebezpieczne?

Nie dla danych, które tworzy wyłącznie Państwa aplikacja i których nikt nie może zmienić. Niebezpiecznie robi się, gdy tylko użytkownicy lub osoby trzecie mogą wpływać na dane wejściowe, na przykład przez ciasteczka, pola formularzy lub zewnętrzne interfejsy.

Czy allowed_classes => false wystarcza jako ochrona?

Zapobiega tworzeniu dowolnych obiektów i zamyka tym samym najważniejszą drogę ataku. Dla danych z zewnątrz JSON jest mimo to lepszym wyborem, ponieważ parser jest prostszy i nie odtwarza obiektów.

Czy aktualizacja bibliotek pomaga przeciw atakom deserializacji?

Częściowo. Ataki często wykorzystują klasy z zależności, które przy niszczeniu lub wybudzaniu obiektu wykonują akcje. Aktualne pakiety zamykają znane łańcuchy, ale właściwą przyczyną jest wywołanie unserialize z obcymi danymi.

Czy PHP2Shell ma coś wspólnego z deserializacją?

Obie główne luki leżą w przetwarzaniu żądań w PHP. Komunikat o CVE-2026-115560 dotyczy natomiast nietypowej konfiguracji serializacji sesji. Środkiem zaradczym jest tam session.upload_progress.enabled=0.

Źródła

Kontakt natychmiast

Liczy się każda godzina. Proszę napisać do nas teraz.

Proszę krótko opisać system, wersję PHP i obserwacje. Skontaktujemy się z Państwem, ustalimy sytuację i będziemy pracować zdalnie, aż incydent zostanie zamknięty i udokumentowany.

Natychmiastowa pomoc