Aktuelle Lage: PHP2Shell. Zwei kritische PHP-Lücken (CVSS 9.2) erlauben Schadcode ohne Anmeldung. Patches seit 21.10.2026.Was jetzt zu tun ist →

Unsichere Deserialisierung in PHP: unserialize und Sicherheit

unserialize() mit fremden Daten kann im schlimmsten Fall zur Codeausführung führen. So ersetzen Sie es durch JSON, schränken es ein, wo es bleiben muss, und finden riskante Stellen im Code.

Stand 5 Min. LesezeitRatgeber

Kurz gesagt: Bei der PHP-unserialize-Sicherheit gilt eine einfache Regel: Daten, die von außen kommen, nie mit unserialize() verarbeiten. Nehmen Sie stattdessen JSON. Wo unserialize() aus Altcode bleiben muss, begrenzen Sie es mit allowed_classes => false und prüfen die Echtheit der Daten mit einer Signatur.

Was bei unserialize() schiefgehen kann

PHP kann Arrays und Objekte mit serialize() in eine Zeichenkette umwandeln und mit unserialize() wiederherstellen. Das ist bequem, um Daten in Cookies, Caches oder Datenbankfeldern abzulegen. Das Problem: unserialize() erzeugt dabei auch Objekte beliebiger Klassen, die in der Anwendung geladen werden können.

Beim Erzeugen und spätestens beim Zerstören eines solchen Objekts ruft PHP automatisch bestimmte Methoden auf, etwa __wakeup(), __unserialize() oder __destruct(). Enthalten Klassen im Projekt oder in Bibliotheken solche Methoden mit Datei-, Datenbank- oder Systemaktionen, kann ein Angreifer sie über manipulierte Daten zweckentfremden. Fachleute sprechen von PHP Object Injection. Je nach vorhandenen Klassen reicht die Wirkung vom Löschen einzelner Dateien bis zur Ausführung von Schadcode.

Für Nicht-Techniker zusammengefasst: Ein einziger unbedachter Aufruf an der falschen Stelle kann einen Angreifer weit in die Anwendung lassen. Diese Schwachstelle gehört deshalb seit Jahren zu den Themen der OWASP Top 10.

Die sichere Alternative: JSON statt unserialize

Für fast alle Fälle, in denen Daten von außen kommen oder den Server verlassen, ist JSON die richtige Wahl. json_decode() erzeugt nur Arrays, Zahlen, Zeichenketten und Wahrheitswerte, keine Objekte eigener Klassen.

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

    // Nur bekannte Felder mit erwarteten Typen übernehmen
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

Wichtig ist der zweite Teil: Auch JSON-Daten sind Eingaben. Übernehmen Sie nur erwartete Felder und prüfen Sie Typen und Wertebereiche, bevor Sie damit arbeiten.

Wenn unserialize unvermeidbar ist: allowed_classes und Signatur

In älteren Anwendungen stecken serialisierte Daten oft in Datenbanken oder Caches, die sich nicht sofort umstellen lassen. Dann begrenzen Sie das Risiko in zwei Schritten. Erstens verhindern Sie mit allowed_classes, dass Objekte erzeugt werden. Zweitens stellen Sie mit einer HMAC-Signatur sicher, dass die Daten tatsächlich von Ihrer Anwendung stammen und unterwegs nicht verändert wurden.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nie im Code-Repository

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

Werden doch bestimmte Klassen benötigt, nennen Sie sie einzeln in allowed_classes, statt alle zuzulassen. Der Schlüssel gehört in die Serverkonfiguration oder einen Secret-Speicher und muss nach einem Vorfall erneuert werden.

Bestehende Daten schrittweise auf JSON umstellen

Bei gewachsenen Anwendungen liegen serialisierte Werte oft über Jahre in der Datenbank. Eine Umstellung in einem Schritt ist dann riskant. Bewährt hat sich ein Übergang: Die Anwendung liest beide Formate, schreibt aber nur noch JSON. Beim Lesen eines alten Werts wird er einmalig mit allowed_classes => false entpackt und sofort als JSON zurückgeschrieben. Nach einiger Zeit sind die meisten Einträge umgestellt, und ein einmaliges Skript erledigt den Rest.

Protokollieren Sie während des Übergangs, wie oft noch das alte Format gelesen wird, und auch, wenn Signaturen nicht passen. Solche Fehlschläge können auf Manipulationsversuche hinweisen und gehören in die Überwachung. Erst wenn kein altes Format mehr auftaucht, entfernen Sie den unserialize()-Zweig vollständig aus dem Code.

Versteckte Deserialisierung: Sessions, Caches und Dateipfade

Nicht jede Deserialisierung steht sichtbar im eigenen Code. Prüfen Sie auch diese Stellen:

  • Sessions: PHP serialisiert Session-Daten automatisch. Solange Angreifer die Session-Dateien nicht schreiben können, ist das unkritisch. Getrennte Session-Pfade je Website beschreibt der Ratgeber Sessions in PHP absichern.
  • Session-Serialisierer: Der Hinweis zu CVE-2026-115560 betrifft eine ungewöhnliche Konfiguration, bei der der serialize_handler zur Laufzeit per ini_set() umgestellt wird und der Session-Upload-Progress aktiv ist. Abhilfe laut php.net: session.upload_progress.enabled=0 in php.ini oder FPM-Pool, danach Neustart der Worker.
  • Caches und Warteschlangen: Redis, Memcached oder Datenbanktabellen mit serialisierten Werten sind nur so sicher wie der Zugang zu ihnen. Sie gehören nicht offen ins Netz.
  • Dateifunktionen mit Pfaden aus Eingaben: Übergeben Sie Dateifunktionen nie Pfade aus der Anfrage. Stream-Wrapper wie phar:// können sonst unerwartete Verarbeitung auslösen. Eine Erlaubnisliste für Dateinamen schützt hier zuverlässiger als jede Filterung.
  • Abhängigkeiten: Bibliotheken mit bekannten Schwachstellen erweitern die Möglichkeiten eines Angreifers. Halten Sie sie aktuell, wie im Ratgeber Composer-Abhängigkeiten auf Lücken prüfen beschrieben.

Riskante Stellen im Code finden

Eine erste Bestandsaufnahme gelingt mit einer einfachen Suche. Sie ändert nichts am System und zeigt alle Aufrufe außerhalb des vendor-Verzeichnisses:

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

Bewerten Sie jeden Treffer mit drei Fragen: Woher kommen die Daten? Kann ein Nutzer sie verändern? Ist allowed_classes gesetzt? Treffer mit Eingaben aus Cookies, $_GET, $_POST, $_REQUEST oder externen Schnittstellen haben Vorrang. Ersetzen Sie sie durch JSON und testen Sie die Umstellung, bevor Sie sie ausrollen.

Ein weiterer Anhaltspunkt liegt im Browser. Serialisierte PHP-Daten haben einen typischen Aufbau mit Typkennungen und Längenangaben. Finden Sie solche Werte in Cookies, versteckten Formularfeldern oder Parametern Ihrer eigenen Website, hat die Anwendung serialisierte Daten an den Besucher gegeben und erwartet sie vermutlich zurück. Genau diese Stellen sind zu prüfen.

Für die Geschäftsführung genügt eine einfache Frage an Entwickler oder Agentur: „Verarbeitet unsere Anwendung irgendwo serialisierte Daten, die von außen kommen, und falls ja, wie sind sie geschützt?“ Eine klare Antwort mit Fundstellen ist ein gutes Zeichen. Eine ausweichende Antwort ist ein Grund, die Anwendung prüfen zu lassen.

Wie hilft PHP SOS konkret?

  • Wir durchsuchen Ihren Code nach Deserialisierung mit fremden Daten und bewerten jeden Fund nach Erreichbarkeit und Auswirkung.
  • Wir schlagen konkrete Umstellungen auf JSON oder abgesicherte Aufrufe vor und begleiten Ihre Entwickler bei der Umsetzung.
  • Wir prüfen Abhängigkeiten auf bekannte Schwachstellen und dokumentieren den Stand.
  • Auf Wunsch lassen wir die Anwendung im Rahmen eines Sicherheitsaudits und Penetrationstests unabhängig prüfen.

Häufige Fragen

Ist unserialize() in PHP grundsätzlich unsicher?

Nicht für Daten, die nur Ihre eigene Anwendung erzeugt und die niemand verändern kann. Gefährlich wird es, sobald Nutzer oder Dritte die Eingabe beeinflussen können, etwa über Cookies, Formularfelder oder externe Schnittstellen.

Reicht allowed_classes => false als Schutz?

Es verhindert, dass beliebige Objekte erzeugt werden, und schließt damit den wichtigsten Angriffsweg. Für Eingaben von außen ist JSON trotzdem die bessere Wahl, weil der Parser einfacher ist und keine Objekte rekonstruiert.

Hilft ein Update von Bibliotheken gegen Deserialisierungsangriffe?

Teilweise. Angriffe nutzen oft Klassen aus Abhängigkeiten, die beim Zerstören oder Aufwecken eines Objekts Aktionen ausführen. Aktuelle Pakete schließen bekannte Ketten, die eigentliche Ursache ist aber der unserialize-Aufruf mit fremden Daten.

Hat PHP2Shell etwas mit Deserialisierung zu tun?

Die beiden Hauptlücken liegen in der Anfrageverarbeitung von PHP. Der Hinweis zu CVE-2026-115560 betrifft dagegen eine ungewöhnliche Konfiguration der Session-Serialisierung. Abhilfe ist dort session.upload_progress.enabled=0.

Quellen

Sofort-Kontakt

Jede Stunde zählt. Schreiben Sie uns jetzt.

Schildern Sie kurz System, PHP-Version und Beobachtung. Wir melden uns, klären die Lage und arbeiten remote, bis der Vorfall abgeschlossen und dokumentiert ist.

Sofort-Hilfe