Der Bericht php2shell von Calif Research analysiert zwei PHP-Schwachstellen für Remote Code Execution, CVE-2026-109618 und CVE-2026-109619. PHP hat sie im Security-Release vom 21. Oktober 2026 behoben. Die Untersuchung entstand bei Calif mit Beiträgen von OpenAI und Wiz. Als Autor des Berichts wird Ryan Austin von Calif genannt.

Der älteste von den Forschern gefundene Angriffsweg reicht bis PHP 5.4.0 zurück. Welche Variante funktioniert, hängt von PHP-Version und SAPI ab. Laut Bericht sind die betroffenen Wege jedoch in Standardkonfigurationen ausnutzbar. Bestätigt wurde Pre-Auth-RCE gegen WordPress 7.1.2 und Drupal 11.4.3. Getestet wurden außerdem Joomla 6.1.4 und MediaWiki 1.46.2. Auch Roundcube 1.7.4 und Magento 2.4.9 tauchen in den Tests auf. Die Liste umfasst ferner phpMyAdmin 5.2.3 und Nextcloud 35.0.1. Genannt werden zudem Laravel 13.11 und Symfony 8.1. Für WHMCS nennt der Bericht keine Version. Einige Angriffswege richten sich gegen beliebige PHP-Skripte.

Die Analyse ordnet die Funde in eine längere Geschichte von PHP-Speicherfehlern ein. Stefan Esser meldete 2004 die erste entfernte PHP-Schwachstelle rund um das Speicherlimit. Wegen ihrer Schwere veröffentlichte er keinen Exploit. CORE und Immunity entwickelten später nicht öffentliche Exploits. Im IRC kursierten daneben gefälschte Exploit-Meldungen. Calif erklärt, eine frühere Untersuchung von PHP-Use-after-free-Problemen habe keine ausnutzbaren Standardpfade in den verbliebenen unserialisierbaren Klassen gefunden.

Im Jahr 2020 meldete jr@coredu.edu den PHP-Bug #78876. Lange Variablen konnten dabei einen Out-of-Memory-Fehler auslösen und temporäre Dateien zurücklassen. In der Zeitleiste des Berichts wird die Meldung jr@coredu.mp zugeschrieben. Ein Audit von OSTIF und Quarkslab fand 2024 einen verwandten Zustand erneut, konnte aber keine Ausnutzung nachweisen. Ein PHP-Patch im Umfeld von #78876 entschärfte dasselbe Muster außerdem in HHVM. Im Git-Verlauf trägt der Patch das Tag `security-audit-2024`, obwohl er aus dem Jahr 2020 stammt.

OpenAI holte Calif am 30. Juli 2026 in die Initiative Patch the Planet. Stefan Esser stellte den Kontakt zu Volker Dusch aus dem Ecosystem Security Team der PHP Foundation her. Calif meldete den ersten Fehler am 11. August an PHP, übermittelte am 14. August einen RCE-Proof-of-Concept und erhielt am 15. August die Bestätigung, dass er funktionierte. Der zweite Fehler folgte am 21. August. Ryan Austin hatte am 5. August den Hash eines Fundes veröffentlicht. Die korrigierten Releases erschienen am 21. Oktober.

Der erste Angriffsweg beginnt mit kontrolliertem Speicherdruck. PHP vergleicht `Content-Length` vor der POST-Verarbeitung mit `post_max_size`. Die tatsächlich eingelesenen Bytes werden aber nicht durch dieselbe Prüfung begrenzt. Chunked-HTTP/1.1-Anfragen und Multipart-Anfragen ohne Längenangabe über HTTP/2 können deshalb mehr Daten übertragen, als die Vorabprüfung erwarten lässt. Ein Eingangsfilter zur Dekompression erzeugt eine vergleichbare Differenz zwischen komprimierten Daten und dem dekodierten Strom.

Ein zweiter Auslöser verwendet Multipart-Uploads mit `filename=""`. Diese leeren Dateiteile zählen nicht gegen `max_file_uploads`, trotzdem erzeugt PHP für jeden Teil sechs `$_FILES`-Felder: `name`, `full_path`, `type`, `tmp_name`, `error` und `size`. Tief verschachtelte Feldnamen zwingen PHP, für jedes Feld eigene Hash-Tabellen anzulegen. Hunderte kurze Datensätze können das standardmäßige `memory_limit` von 128 MiB überschreiten. Der gesamte Request bleibt dabei unter dem Standardwert von 8 MiB für `post_max_size`. Eine spätere Speicheranforderung löst dann einen kontrollierten fatalen Fehler aus.

Der erste Fehler steckt in `_zend_hash_str_add_or_update_i`. PHP erhöht `nNumUsed` und `nNumOfElements`, bevor der Schlüssel des Buckets reserviert und gesetzt ist. Scheitert diese Reservierung mit einem fatalen Fehler, sieht die Speicherbereinigung den Bucket bereits als gültig an, obwohl Schlüssel, Hash und Wert noch fehlen. Die Registrierung von Request-Variablen erreicht diesen Code sowohl über URL-kodierte POST-Daten als auch über RFC1867-Multipart-Daten, jeweils vor dem Start des Zielskripts. Beim Shutdown von mod_php zerstört PHP die fehlerhafte Hash-Tabelle und behandelt den veralteten Bucket als gültige Struktur.

PHP-FPM behandelt denselben Fehler beim Request-Start anders. Es beendet die FastCGI-Anfrage, fährt das PHP-Modul herunter und beendet den Worker mit `FPM_EXIT_SOFTWARE`. Der Worker behält den fehlerhaften Request-Zustand daher nicht. Der erste Angriffsweg betrifft damit Apache mit mod_php. Für diese Variante nennt der Bericht Ausnutzung gegen beliebige Skripte ab PHP 7.3.

Danach verallgemeinerten die Forscher das Muster: Ein Zustand wird sichtbar, bevor eine fehleranfällige Operation abgeschlossen ist, und ein fataler Fehler überspringt anschließend die Rückabwicklung. Eine LLM-gestützte Analyse überführte diese Idee in ein Suchmuster für `SKILL.md` und fand einen zweiten Weg in der Session-Upload-Progress-Funktion. Multipart-Daten können Session aktivieren, bevor die normalen Module aktiviert sind. Scheitert die Verarbeitung nach `FILE_START`, wird der Bereinigungs-Callback übersprungen. `PG(modules_activated)` bleibt falsch gesetzt, und die Session-Globals zeigen weiter in den inzwischen freigegebenen Request-Heap. Ein späterer Multipart-Request mit Session-ID und `PHP_SESSION_UPLOAD_PROGRESS`, aber ohne Dateiteil, erreicht die Bereinigung ohne Zurücksetzen dieses Zustands. So entsteht für mod_php ein Use-after-free über den nächsten Request. PHP-FPM beendet den Worker beim Fehler während des Starts und erhält diesen Zustand nicht.

Ein weiterer Angriffsweg greift PHP-FPM nach erfolgreichem Request-Start an. Der generische Unserializer legt Referenz- und Destruktorinformationen in den Globals der Basic-Erweiterung ab. Ein fataler Fehler während der Dekodierung auf Anwendungsebene kann `BG(unserialize).data` auf freigegebenen Request-Speicher zeigen lassen, während der Verschachtelungszähler ungleich null bleibt. Der nächste Request startet die Session-Upload-Verarbeitung vor der normalen Initialisierung von Basic und kann diesen Speicher wiederverwenden. Serialisierte `R:`- und `r:`-Referenzen erreichen dann ausgewählte Einträge der alten Referenzliste. Ein negativer Wert für `used_slots` in einem Destruktorblock kann Speicher vor dem vorgesehenen Array auswählen. Doppelte Schlüssel führen über `var_push_dtor_value()` dazu, dass ein echter zval in den ausgewählten Slot kopiert wird. Der Bericht beschreibt diese Variante als anwendungsabhängige RCE in mod_php und PHP-FPM. Betroffen sein können etwa `session_start()`, `session_decode()`, `unserialize()` und WordPress’ `maybe_unserialize()`.

Calif betont, dass weder ein spezielles Fuzzing-Harness noch ein ungewöhnlich leistungsfähiges Modell nötig war. Ausschlaggebend waren Kenntnisse über ältere PHP-Schwachstellen und gezielte Fragen an LLMs. Allgemeine Prompts finden laut den Forschern alte, wenig bekannte Exploit-Primitiven nicht zuverlässig wieder.

Calif empfiehlt die sofortige Installation des PHP-Security-Releases vom Oktober 2026. Als vorläufige Maßnahmen können PHP-FPM-Betreiber `session.upload_progress.enabled` deaktivieren und `memory_limit=-1` setzen. Für mod_php wird ebenfalls `memory_limit=-1` genannt. Diese Einstellungen ersetzen kein Update. Ein unbeschränktes Speicherlimit kann das Risiko einer Erschöpfung der Prozessressourcen erhöhen.