Technische Details

Wie PHP2Shell funktioniert

Eine Ursache, zwei CVEs, vier Bugs: PHP räumt nach einem fatalen Fehler — besonders bei Speichermangel — mitten im Request nicht zuverlässig auf.

Zuletzt geprüft 2026-10-22

Ursache

Erreicht PHP das memory_limit, löst es einen fatalen Fehler aus und springt mit longjmp() zurück. Dabei wird Code übersprungen, der Zustand fertig initialisiert oder zurückgesetzt hätte. Zwei Dinge gehen schief:

  • Halb gebaute Daten (CVE-2026-109618). Reservierter, aber noch nicht initialisierter Speicher wird später beim Request-Shutdown gelesen, als wäre er gültig.
  • Veraltete Globals (CVE-2026-109619). Globale Zeiger pro Request (Error-, Session-, Objekt-, Extension-Zustand) werden nicht gelöscht, der Speicher, auf den sie zeigen, aber am Ende des Requests freigegeben. Der nächste Request auf demselben Worker nutzt sie vor der normalen Initialisierung.

Angreifer erreichen diese Pfade mit präparierten Multipart-Requests, die PHP beim Parsen der Eingabe in einen OOM treiben — bevor Ihr Router, Framework oder Ihre Authentifizierung läuft. Deshalb ist ein „Hello World“-Skript genauso exponiert wie WordPress.

Die Exploit-Kette

  1. Schritt 1

    Auslesen

    Mit dem Speicherleck lebenden Worker-Speicher lesen und ASLR aushebeln.

  2. Schritt 2

    Korrumpieren

    Mit einem Use-after-free (Unserializer oder Backtrace) kontrollierte Daten schreiben.

  3. Schritt 3

    Ausführen

    Die Korruption in Codeausführung als Worker-Benutzer verwandeln — vor jedem Login.

Die Ausnutzung gelingt nicht auf Anhieb: Der Angreifer tastet sich wiederholt an das Speicherlayout jedes Ziels heran. Die Forschenden bündelten die Kette in einem Skript namens php2shell (unveröffentlicht) und demonstrierten es an den damals aktuellen Container-Images wordpress, drupal und joomla.

Die vier Bugs

CVE-2026-109618

Use-after-free im Upload-Progress-Unserializer

Das Session-Modul dekodiert Daten für das Upload-Progress-Tracking, bevor die Request-Initialisierung abgeschlossen ist, und führt Buch in BG(unserialize). Ein Allokationsfehler reißt den Request-Heap ab, lässt diese Global aber stehen. Der nächste Request auf demselben Worker behandelt vom Angreifer wiederverwendeten Speicher als lebendigen Unserializer-Zustand.

CVE-2026-109618

Auslesen von Prozessspeicher aus der Ferne

Beim frühen RFC-1867-Multipart-Parsing kann Upload-Progress-Code eine Session-ID in PS(id) veröffentlichen. Nach einem OOM-Abbruch hängt dieser Zeiger ins Leere. Ein späterer Multipart-Request vertraut Zeiger und Länge und URL-kodiert, was dort jetzt liegt, in ein PHPSESSID-Cookie — selbst wenn die Anwendung nie session_start() aufruft.

CVE-2026-109619

Use-after-free im Fatal-Error-Backtrace

EG(last_fatal_error_backtrace) überlebt einen OOM, der den Request-Heap zerstört. Eine frühe Diagnose im nächsten Request folgt dem hängenden Zeiger. Am direktesten auf PHP 8.5 mit aktiviertem fatal_error_backtraces.

CVE-2026-109619

Type Confusion im Shared Memory von OPcache

In zend_accel_script_persist() wird CG(map_ptr_last) hochgezählt, bevor die gemeinsame Grenze veröffentlicht ist. Ein Allokationsfehler dazwischen hinterlässt Map-Pointer-Slots, über die sich Worker uneinig sind, sodass ein Worker einen Slot für einen anderen Objekttyp wiederverwenden kann.

Die Zuordnung der Bugs zu den CVEs folgt dem Beitrag von Wiz Research. Die PHP-Advisories beschreiben die Klassen breiter und nennen weitere Varianten.

Exposition je Server-Setup

Basierend auf den PHP-Advisories. „Anfällig“ heißt laut Quellcodeanalyse verwundbar, ohne veröffentlichten generischen Exploit für diese SAPI.

Setup…109618…109619Hinweise
Apache + mod_phpRCE bestätigtRCE bestätigtBeide CVEs mit Standardlimits bis zur RCE bestätigt. Setup mit dem höchsten Risiko.
PHP-FPM (nginx, Apache, Caddy…)AnfälligRCE bestätigtRCE über den Session-Lebenszyklus auf FPM bestätigt. OOM-Pfad über Codepfade auf Anwendungsebene bestätigt; kein generischer FPM-Exploit veröffentlicht.
FrankenPHP / LiteSpeed LSAPIWahrscheinlichWahrscheinlichLaut Advisory wahrscheinlich anfällig. Langlebige Worker machen requestübergreifenden Zustand eher relevanter als weniger.
CLI (Cron, Queue-Worker, artisan)Nicht behandeltNicht behandeltIn den Advisories nicht behandelt und im Normalbetrieb nicht per HTTP-Request erreichbar. Trotzdem aktualisieren — es ist dasselbe Paket.

CVE-Einträge

CVE-2026-109618GHSA-w8hw-w9fx-j79wCVSS 4.0 · 9.2

RCE durch Out-of-Memory-Korruption (OOM)

PHP bricht bei Speichermangel den aktuellen Kontext mit longjmp() ab. Passiert das, während der Request-Speicher erst teilweise initialisiert ist, liest oder verzweigt der Request-Shutdown später auf ungültigen Daten. Mehrere Multipart-Parsing-Pfade können PHP innerhalb normaler Limits in einen OOM treiben, noch bevor Anwendungscode läuft.

Auswirkung:
Codeausführung ohne Authentifizierung als Benutzer des PHP-Web-Workers.
Demonstriert:
Pre-Auth-RCE auf WordPress, Drupal und Joomla (mod_php, Standardlimits).
Betroffen
<8.2.35, <8.3.36, <8.4.27, <8.5.12 (und EOL-Branches zurück bis 5.4)
CWE
CWE-416, CWE-755, CWE-824, CWE-1325, CWE-1419
CVE-2026-109619GHSA-6wmf-4rv5-p3rpCVSS 4.0 · 9.2

RCE durch veralteten globalen Zustand zwischen Requests

Ein fataler Fehler (auch OOM) kann das Zurücksetzen pro Request überspringen, sodass globaler Error-, Session-, Objekt- oder Extension-Zustand auf Speicher zeigt, der am Ende des Requests freigegeben wurde. Der nächste Request auf demselben Worker kann diesen freigegebenen Speicher vor der normalen Initialisierung verwenden.

Auswirkung:
Use-after-free, das zum Auslesen von Worker-Speicher oder zur Codeausführung führt.
Demonstriert:
Pre-Auth-RCE auf WordPress und Drupal; anwendungsunabhängiges Auslesen von Speicher aus der Ferne.
Betroffen
<8.2.35, <8.3.36, <8.4.27, <8.5.12 (und EOL-Branches zurück bis 5.4)

Vektor (beide): CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. Über das Netzwerk erreichbar, keine Rechte oder Benutzerinteraktion; „AC:H / AT:P“ spiegelt wider, dass der Exploit auf das Ziel abgestimmt werden muss. Frühe geleakte Berichte nannten CVSS 10.0 — diese Zahl ist falsch.

Was der Fix ändert

Der Patch nutzt bei Out-of-Memory kein longjmp() mehr. Stattdessen setzt das Erreichen von memory_limit einen weichen OOM, der am nächsten sicheren Punkt in der VM ausgelöst wird, wo der Zustand konsistent ist. Eine zweite, höhere Schwelle löst einen harten OOM aus, der den Prozess per _exit() beendet — keine Shutdown-Handler, kein halb aufgeräumter Zustand.

Da der Fix das Fehlermuster statt einzelner Einstiegspunkte behebt, deckt er laut PHP-Team auch die bekannten Varianten ab.

Nebenwirkung: Codepfade, die darauf angewiesen waren, dass Shutdown-Funktionen nach einem OOM laufen, verhalten sich anders, sobald das harte Limit erreicht wird.

Verwandte Seiten