PHP CVE-2026-109618 und CVE-2026-109619 (PHP2Shell): Der Notausgang bei Speichermangel beschädigt den Interpreter
Überschreitet eine PHP-Anfrage ihr memory_limit, räumt der Interpreter nicht geordnet auf, sondern springt per longjmp() aus der gerade laufenden C-Funktion heraus, ganz gleich, wie weit diese mit ihrer Arbeit war. Jahrzehntelang galt dieser Sprung als bequemer Notausgang. CVE-2026-109618 und CVE-2026-109619 zeigen, was er zurücklässt: halb initialisierte Strukturen, die das anschließende Aufräumen für gültig hält, und globalen Zustand, der im Worker liegen bleibt und auf den Speicher eines längst beendeten Requests zeigt. Unter Apache mit mod_php genügt dafür ein präparierter Multipart-Request, noch bevor dein eigener Code eine Zeile ausgeführt hat. Der Fix in PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35 schafft den Sprung bei Speichermangel deshalb ganz ab. Wie die Veröffentlichung selbst aus dem Takt geriet, zeigt nebenbei, wie wenig du den Zeitpunkt eines Patches in der Hand hast.
TL;DR — 90 Sekunden
- Was: Zwei Speicherfehler im PHP-Kern. Bei
CVE-2026-109618hinterlässt ein Abbruch wegen Speichermangels halb initialisierte Request-Daten, beiCVE-2026-109619überlebt globaler Zustand das Ende eines Requests und wird vom nächsten Request im selben Worker benutzt. - Schwere: Beide Lücken haben CVSS 4.0 9.2 (
AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N). Die Lücken stehen nicht im CISA-KEV-Katalog, mehrere Sicherheitsanbieter melden aber Angriffsversuche. - Wen es trifft: Jede PHP-Installation, die Web-Requests bedient, also mod_php, PHP-FPM, FrankenPHP und LSAPI. Am einfachsten ist der Angriff gegen mod_php, wo er ohne jeden Anwendungscode auskommt.
- Was zu tun ist: Auf PHP 8.5.12, 8.4.27, 8.3.36 oder 8.2.35 aktualisieren, alle Worker neu starten und Container-Images neu bauen. FrankenPHP liefert die Fixes ab 1.13.2 aus.
- Was vorher geschützt hätte: Grenzen für Request-Größe und Multipart-Struktur vor PHP, abgeschaltetes Session-Upload-Progress und ein Worker, der nach einer Übernahme weder Shell noch Internetzugang findet. Die eigentliche Ursache beseitigt nur der Patch.
Vom Notausgang zur Speicherkorruption
PHP verwaltet den Speicher eines Requests selbst. Jede Allokation über emalloc() läuft durch den Zend Memory Manager, der mitzählt und bei Überschreitung von memory_limit den bekannten Fatal Error „Allowed memory size of … bytes exhausted“ auslöst. Danach ruft die Engine zend_bailout() auf, und diese Funktion springt per longjmp() zurück zum letzten Aufsetzpunkt, den die Engine vorher mit setjmp() markiert hat. Ein solcher Sprung stellt laut C-Standard nur den Stack dieses Aufsetzpunkts wieder her. Alles, was die übersprungenen Funktionen noch erledigen wollten, vom Initialisieren eines Feldes bis zum Freigeben eines Puffers, findet einfach nicht statt.
Solange das mitten in der Ausführung eines Skripts passiert, geht das meist gut, weil PHP den gesamten Request-Speicher am Ende ohnehin verwirft. Gefährlich wird es dort, wo Strukturen schon erreichbar sind, bevor sie fertig aufgebaut sind. Genau das beschreibt das Advisory GHSA-w8hw-w9fx-j79w: In bestimmten Situationen ist Request-Speicher bereits eingehängt, bevor er vollständig initialisiert wurde. Schlägt in diesem Moment eine Allokation fehl, bleibt er in einem ungültigen Zustand zurück, und der Request-Shutdown liest diesen Zustand später, als wäre er korrekt, oder verzweigt sogar abhängig davon. Daraus entsteht laut Advisory eine Use-after-free-Situation, die sich bis zur Codeausführung treiben lässt.
Warum ein Angreifer den Moment des Speichermangels bestimmen kann, zeigt der Multipart-Parser, der Formulardaten und Uploads zerlegt, bevor dein Skript startet. Das Advisory nennt drei Hebel. Multipart-Requests ohne verwertbare Content-Length kommen an der ersten Prüfung von post_max_size vorbei. Ein einzelner Part darf beliebig viele Header tragen, deren interne Darstellung ein Vielfaches ihrer Rohgröße belegt. Und leere Datei-Felder zählen nicht gegen max_file_uploads, erzeugen aber trotzdem Metadaten-Arrays, deren Aufwand sich mit verschachtelten Feldnamen vervielfacht. Wer diese Hebel kombiniert, steuert die Speicherbelegung so fein, dass das Limit an einer gewünschten Stelle reißt. Der CVSS-Vektor wertet das als hohe Angriffskomplexität, weil Timing und Heap-Belegung zusammenpassen müssen. Ich lese das als Aufwand, nicht als Hürde, die einen entschlossenen Angreifer aufhält.
Die zweite Lücke beginnt dort, wo ein Worker-Prozess mehrere Requests nacheinander bedient. PHP hält globalen Zustand für Fehler, Sessions, Objekte und Erweiterungen, den es zwischen zwei Requests zurücksetzt. Laut Advisory GHSA-6wmf-4rv5-p3rp kann ein Fatal Error an der falschen Stelle diesen Reset verhindern, ausgelöst etwa durch Speichermangel, einen Timeout, ein fehlgeschlagenes require oder einen Parse-Fehler. Für einen Angriff braucht es dann einen Producer, der requestbezogene Daten global ablegt, und einen Consumer, der sie im nächsten Request vor der regulären Initialisierung liest. Das Advisory nennt zwei solche Consumer: Warnungen, die PHP beim Anwenden bestimmter INI-Einstellungen noch vor der Initialisierung ausgibt, und das Session-Upload-Progress, das vor dem eigentlichen Session-Start auf Session-Zustand zugreift. Den stärksten Fall liefert unter PHP 8.5 fatal_error_backtraces als Producer, weil die Fehlerausgabe dabei Backtrace-Daten speichert, deren Reset im Shutdown fehlte. Darauf beruht der bestätigte Exploit gegen mod_php, und über das Upload-Progress hat das Advisory Codeausführung zusätzlich gegen mod_php und PHP-FPM validiert.
Beide Fehler haben eine gemeinsame Wurzel, die ich für die eigentliche Lehre halte. Der Code ging davon aus, dass nach einem Fatal Error nichts mehr passiert, was von sauberem Zustand abhängt, weil der Request ohnehin endet. Der Prozess endet aber nicht. Er räumt noch auf, und er bedient danach den nächsten Request.
Änderungen im Fix-Commit
Der Fix-Commit 3584a98 trägt den Titel „Avoid state corruption via bailout“ und ist für einen Sicherheitsfix ungewöhnlich groß: Er berührt rund 390 Dateien, vom Zend-Kern über main/rfc1867.c und den SAPI-Code bis zu Erweiterungen wie json, pcre, session, mbstring und opcache. Inhaltlich macht er laut Commit-Message zwei Dinge. Er ersetzt den Bailout bei Speichermangel durch eine geordnete Abwicklung über UnwindExit, und er setzt gezielt jene globalen Variablen zurück, die beim Start eines Requests gelesen werden können.
Die neue Logik beschreibt Alexandre Daubois aus dem PHP-Security-Team so: memory_limit wird zu einer weichen Grenze. Überschreitet eine Anfrage sie, merkt sich PHP einen ausstehenden Fehler, die laufende C-Funktion beendet ihre Arbeit regulär, und erst am nächsten Prüfpunkt der VM, etwa bei einem Sprung oder Funktionsaufruf, löst PHP die Abwicklung aus. Der Multipart-Parser räumt dabei auf und hört auf, danach meldet die SAPI den Fatal Error wie gewohnt. Damit ein Angreifer die weiche Grenze nicht beliebig ausdehnt, gibt es zusätzlich eine harte Grenze, die neue Einstellung memory_hard_limit_percent. Laut Daubois liegt sie bei einem memory_limit unter 1G standardmäßig beim Doppelten, beim Standardwert von 128M also bei 256M. Wird auch sie überschritten, beendet sich der Prozess sofort mit _exit(137).
Am Umfang des Commits lässt sich ablesen, wie tief die alte Annahme saß. Jede C-Funktion, die Speicher anfordert, durfte sich bisher darauf verlassen, dass eine fehlgeschlagene Allokation nie zu ihr zurückkehrt. Nach dem Fix kehrt sie zurück, und jede dieser Funktionen muss mit dieser Situation umgehen können. Dazu kommen zahlreiche neue Tests, die gezielt Speichermangel an bestimmten Stellen erzeugen. Der Fehler war also kein einzelner vergessener Check, sondern eine Annahme, auf die sich der Interpreter an unzähligen Stellen verlassen hat.
Vom präparierten Formular zur Shell im Worker
Die Voraussetzungen sind bescheiden, und das macht die Lücken so ernst. PHP setzt memory_limit ab Werk auf einen endlichen Wert, und nur dann gibt es überhaupt einen Speichermangel, den ein Angreifer herbeiführen kann. Das Session-Upload-Progress ist laut PHP-Doku standardmäßig eingeschaltet, und fatal_error_backtraces steht in PHP 8.5 ebenfalls ab Werk auf 1. Eine Standardinstallation ist also in beiden Fällen betroffen, ohne dass jemand etwas falsch konfiguriert hätte.
Konzeptionell läuft ein Angriff auf CVE-2026-109618 so ab: Der Angreifer schickt einen Multipart-Request, dessen Aufbau die Speicherbelegung bis kurz vor das Limit treibt, und lässt die Allokation dann an einer Stelle scheitern, an der eine Struktur halb fertig ist. Das Aufräumen arbeitet mit diesem Zustand weiter, und mit gezielter Belegung des Heaps lässt sich daraus Codeausführung mit den Rechten des Web-Workers machen. Unter mod_php läuft der Shutdown laut Advisory auch nach einem gescheiterten Start, deshalb braucht der Angriff dort keinerlei Anwendungscode. Unter PHP-FPM beendet sich der Child-Prozess nach einem gescheiterten Start. Die PHP Foundation hat für FPM trotzdem Codeausführung über anwendungsseitige Code-Pfade bestätigt, eine anwendungsunabhängige Demonstration gibt es dort nicht. FrankenPHP ruft den Shutdown laut Advisory unbedingt auf und gilt deshalb als vermutlich anfällig.
Was eine erfolgreiche Übernahme kostet, hängt an dem, was der Worker sehen darf. In einer typischen TYPO3- oder Symfony-Installation sind das die Zugangsdaten zur Datenbank, der Encryption Key, API-Tokens aus der Konfiguration und Schreibrechte im Webroot. Von dort ist es nicht weit zu einer Webshell, zu manipulierten Seiten und zu Zugriffen auf alles, was das Netz hinter dem Webserver erreichbar macht.
Gefährdet sind damit alle PHP-Prozesse, die Requests aus dem Netz verarbeiten. Kaum exponiert sind reine CLI-Prozesse wie Cronjobs oder Queue-Worker, die keine Multipart-Daten von außen parsen, wobei ich das aus dem beschriebenen Angriffspfad ableite und nicht aus dem Advisory. Bei den Versionen kursierten viele falsche Angaben. Die Advisories führen nur die Zweige 8.2 bis 8.5 als betroffen und gepatcht. Laut Daubois reicht der Mechanismus bis zum Allocator von PHP 5.3 zurück, ein Exploit für so alte Versionen ist aber nicht belegt.
| CVE | Betroffen | Fix | Voraussetzung | CVSS |
|---|---|---|---|---|
| CVE-2026-109618 (GHSA-w8hw-w9fx-j79w) | PHP 8.2 vor 8.2.35, 8.3 vor 8.3.36, 8.4 vor 8.4.27, 8.5 vor 8.5.12 | 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 | Endliches memory_limit, Web-SAPI; unter mod_php ohne Anwendungscode | 9.2 (kritisch) |
| CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp) | wie oben | wie oben | Worker bedient mehrere Requests; Producer wie fatal_error_backtraces, Consumer wie Upload-Progress | 9.2 (kritisch) |
Zur Ausnutzung ist die Lage unübersichtlich. Bis zum Release fand das Security-Team nach eigener Aussage keinen funktionierenden öffentlichen Proof of Concept, schreibt aber, dass ein versehentlich veröffentlichtes Video die Entwicklung verschiedener Exploits beschleunigt habe. Cloudflare meldete frühe Angriffsversuche und setzte vorübergehend WAF-Regeln ein, Patchstack und Aikido berichten laut SOCRadar von Ausnutzung, ohne Details zu veröffentlichen, und Chainguard-Chef Dan Lorenc sagte The Register, die Honeypots seines Unternehmens hätten am 21. Oktober Angriffe aus China und Russland auf die Lücken registriert. Belegte erfolgreiche Kompromittierungen nennt keine der Quellen. Im CISA-KEV-Katalog stehen die Lücken mit Stand 21. Oktober nicht, und die EPSS-Schnittstelle von FIRST lieferte am 22. Oktober für beide CVEs noch keinen Wert. Die Behörden haben trotzdem früh reagiert. CISA rief zu sofortigem Patchen auf, CERT-EU riet schon am 20. Oktober, sich darauf vorzubereiten, und das britische NCSC warnte vorab vor dem anstehenden Release.
Leak, Video und ein gebrochenes Embargo
Die Vorgeschichte reicht weiter zurück, als die Aufregung dieser Woche vermuten lässt. Laut PHP Foundation stieß ein Kernentwickler schon 2024 bei einem Audit durch Quarkslab, organisiert über OSTIF und finanziert von der Sovereign Tech Agency, auf erste Anzeichen des OOM-Problems. Damals schien eine Ausnutzung nur theoretisch, und die Arbeit an einem Fix geriet über Monate immer wieder ins Stocken. Die Foundation nennt das selbst ein strukturelles Versagen. Ernst wurde es am 11. August 2026, als Calif im Rahmen eines Audits eine RCE mit derselben Ursache meldete und OpenAI am selben Tag unabhängig dieselbe Ursache fand. Wiz und OpenAI lieferten im August und September weitere Angriffswege nach.
Am 20. Oktober gegen 15:00 UTC tauchten die beiden CVE-Nummern dann in einem inzwischen gelöschten Beitrag auf Hacker News und in sozialen Medien auf, mit falschem Termin und einem falschen CVSS-Wert von 10.0. Technische Details enthielt der Leak nicht. Um 19:30 UTC veröffentlichte das PHP-Team seine Vorankündigung früher als geplant und zog das Release vom 22. auf den 21. Oktober vor, jeweils um 14:00 UTC. Gegen 20:00 UTC veröffentlichte Calif versehentlich ein Video seines Proof of Concept, das zwar schnell gelöscht wurde, dessen Kopien aber weiter kursierten. Am 21. Oktober um 07:41 UTC, mehr als sechs Stunden vor dem koordinierten Termin, veröffentlichte Chainguard gebaute Images mit dem Fix.
Die PHP Foundation wirft Chainguard vor, das Embargo einseitig gebrochen zu haben. Chainguard hatte den Patch wie andere Distributoren vorab erhalten, und mit den Images konnten Angreifer den Fix in Binärform studieren, während alle anderen noch warteten. Lorenc bestreitet nicht den Ablauf, wohl aber den Vorwurf. Sein Team habe die Fehler nach einem Hinweis selbst mit eigenem Testaufbau und einem KI-Modell nachvollzogen und anschließend beim Testen der Patches geholfen. Als am Dienstag erste Proof of Concepts in sozialen Medien auftauchten und andere Anbieter Angriffe meldeten, habe er spät in der Nacht entschieden auszuliefern und die Koordinationsgruppe sofort informiert. Chainguards Richtlinie laute „Coordinated vulnerability disclosure, unless there's evidence of active exploits in the wild“. Welche Anbieter Angriffe gemeldet hatten, sagte er nicht, Belege legte er keine vor.
Was ich daraus ableite, ist weniger ein Urteil über Chainguard als eine Feststellung über deinen Betrieb. Ein Embargo schützt genau so weit, wie alle Beteiligten es halten, und dieser Fall hatte vier Lecks: die CVE-Nummern, das Video, möglicherweise die Hinweise an Betreiber kritischer Infrastruktur und schließlich die vorzeitigen Images. Ob die sechs Stunden Angreifern tatsächlich geholfen haben, lässt sich von außen nicht beurteilen. Für Nutzer von Wolfi- und Chainguard-Images bedeuteten sie jedenfalls einen Vorsprung, den aber nur hatte, wessen Pipeline neue Image-Digests ohne Zutun eines Menschen übernimmt. Der Zeitpunkt eines Patches richtet sich nach Leaks, Distributoren und Forschern, nicht nach deinem Wartungsfenster.
PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35 und ihre Nebenwirkungen
Die Advisories nennen das Update auf eine Version mit dem Patch ausdrücklich als einzige vollständige Gegenmaßnahme. Die Fix-Versionen sind PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35, und sie beheben beide Lücken zugleich. Nach dem Update müssen alle Worker neu starten, sonst läuft der alte Code im Speicher weiter. Bei PHP-FPM heißt das einen Neustart des Masters, bei mod_php einen Neustart von Apache, bei Containern ein neues Image und ein Rollout.
FrankenPHP hat am 21. Oktober Version 1.13.2 veröffentlicht. Laut Release Notes sind die Docker-Images mit PHP 8.2.35, 8.3.36, 8.4.27 und 8.5.12 gebaut, die statischen Binaries mit PHP 8.5.12. Betreibst du FrankenPHP als statisches Binary, tauschst du es aus. Baust du ein eigenes Image auf einem FrankenPHP-Basisimage, musst du neu bauen und Tag oder Digest anheben. In Wolfi führen die Paketdefinitionen im Repository wolfi-dev/osphp-8.5 in Version 8.5.12 und php-8.4 in Version 8.4.27, das habe ich am 22. Oktober direkt in den YAML-Dateien nachgesehen. Debian hat laut Security-Tracker für Bookworm php8.2 8.2.35-1~deb12u1 und für Trixie php8.4 8.4.27-1~deb13u1 (DSA-6574-1) ausgeliefert, Forky war am 22. Oktober noch verwundbar. Amazon Linux 2023 hat am 22. Oktober die Pakete php8.2 bis php8.5 mit ALAS2023-2026-3301 bis -3304 gepatcht, für php8.1 ist dort kein Fix geplant. Unter Amazon Linux 2 ist nur das Extra php8.2 gepatcht (ALAS2PHP8.2-2026-015), das php aus dem Core-Repository bekommt keinen Fix mehr. Der Alpine-Tracker führte die CVE am 22. Oktober noch nicht. Distributionen patchen oft per Backport, ohne die Upstream-Versionsnummer zu übernehmen, die Versionsnummer allein sagt dann wenig.
Das Update ändert das Verhalten bei Speichermangel, und das solltest du vor dem Rollout kennen. memory_limit ist künftig eine weiche Grenze, eine Anfrage darf sie bis zur nächsten Prüfstelle der VM leicht überschreiten, bevor sie abbricht. Reißt sie die harte Grenze aus memory_hard_limit_percent, beendet sich der Prozess mit Exit-Code 137. In Kubernetes kennst du diesen Code bisher als Zeichen für einen Kill durch den OOM-Killer oder ein SIGKILL. Nach dem Update kann er also auch bedeuten, dass PHP sich selbst beendet hat, was ich aus dem Exit-Code ableite und für Alarme und Dashboards im Blick behalten würde. Bei gewöhnlichem Speichermangel bricht ein gepatchtes PHP den Request weiterhin mit dem bekannten Fatal Error ab, hinterlässt dabei aber keinen beschädigten Zustand. Den ganzen Prozess beendet erst die harte Grenze.
Für ältere Zweige gibt es keine offiziellen Binärreleases. Laut Threat Landscape und SOCRadar gibt es für 7.4, 8.0 und 8.1 nur minimale Source-Backports, und einige Distributionen und Anbieter von Langzeitsupport, die den Patch vorab erhalten hatten, liefern eigene Backports aus. Für 7.3 und älter gibt es laut SOCRadar gar keinen Fix. Inoffizielle Patch-Sammlungen aus der Community halte ich für keinen tragfähigen Weg, weil niemand ihre Vollständigkeit belegt. Betreibst du solche Versionen noch, nimm diesen Fall als Anlass für ein Upgrade.
Härtung über den Patch hinaus
Bevor es um einzelne Maßnahmen geht, lohnt eine Einordnung. Der Fehler liegt im Interpreter selbst und wird ausgelöst, bevor deine Anwendung überhaupt läuft. Alles, was auf Ebene der Anwendung prüft, kommt also zu spät. Wirken können drei Klassen von Maßnahmen: solche, die dem Angreifer die Kontrolle über die Speicherbelegung nehmen, solche, die Zustand zwischen Requests begrenzen, und solche, die eine gelungene Übernahme im Worker festhalten. Die ersten beiden Klassen senken die Wahrscheinlichkeit, die dritte den Schaden.
Request-Grenzen vor PHP
Der Angriff auf CVE-2026-109618 lebt davon, dass ein Request mehr Speicher erzeugt, als seine Größe vermuten lässt. Ein Reverse Proxy, der die Body-Größe begrenzt, nimmt dem Angreifer einen Teil dieses Spielraums. In Caddy und damit in FrankenPHP leistet das die Direktive request_body mit max_size, Lesezugriffe über die Grenze hinaus enden laut Doku mit Status 413. In Traefik übernimmt die Middleware Buffering mit maxRequestBodyBytes diese Aufgabe. Laut Daubois greifen solche Grenzen vor allem bei Bodies ohne deklarierte Länge, also genau dem Fall, der die erste Prüfung von post_max_size umgeht.
Gegen die übrigen Hebel hilft eine reine Größengrenze nur begrenzt, denn viele Header pro Part und viele leere Datei-Felder passen auch in einen kleinen Body. Das Advisory empfiehlt deshalb zusätzlich, Anzahl der Parts, Header pro Part und Uploads am Frontend zu begrenzen und max_input_nesting_level zu senken. Solche Strukturgrenzen kann ein gewöhnlicher Reverse Proxy kaum ausdrücken, dafür brauchst du eine WAF mit Multipart-Inspektion. Wo eine Anwendung gar keine Formulardaten dieser Art erwartet, etwa bei reinen JSON-APIs, ist es am einfachsten, multipart/form-data am Proxy für diese Pfade abzulehnen. Ein TYPO3-Backend braucht Uploads, eine Headless-API oft nicht.
Speichergrenzen für die Übergangszeit
Die im Advisory zuerst genannte Übergangsmaßnahme klingt widersinnig: memory_limit=-1. Ohne endliches Limit fehlt dem Angreifer die vorhersagbare Grenze, auf die er seinen Request zuschneidet. Ganz verschwindet der Sprung laut Advisory trotzdem nicht, denn scheitert etwa mmap(), springt PHP weiterhin. In PHP-FPM setzt du den Wert mit php_admin_value im Pool, weil Anwendungen ihn dann nicht per ini_set() zurückdrehen können, und prüfst, dass weder ein Pool noch eine Verzeichniskonfiguration noch eine Anwendung wieder ein endliches Limit setzt. Danach müssen alle Worker neu starten. Die Kehrseite ist offensichtlich, denn ein einzelner Request kann jetzt beliebig viel Speicher belegen. Das Advisory nennt als Ersatz eine cgroup-Grenze über memory.max, warnt aber, dass sie keinen sauberen Allokationsfehler garantiert und jeder Worker eine eigene Gruppe braucht, damit nicht Master und alle Worker gemeinsam sterben.
Genau dieser Punkt wird in Kubernetes zur Stolperfalle. Das Speicherlimit eines Containers ist eine cgroup für den ganzen Container. Bei FPM im Container trifft ein Überlauf also den Master samt allen Kindern, bei FrankenPHP ohnehin den einzigen Prozess. Die Übergangslösung tauscht damit eine Speicherkorruption gegen ein Verfügbarkeitsrisiko, das ich nur für die Stunden bis zum Patch hinnehmen würde. Ausdrücklich nicht verwenden solltest du laut Advisory RLIMIT_AS oder RLIMIT_DATA, weil dann mmap() fehlschlägt und genau den verwundbaren Pfad auslöst.
Weniger Zustand zwischen den Requests
CVE-2026-109619 braucht einen Consumer, der veralteten Zustand vor der Initialisierung liest, und der wichtigste lässt sich abschalten. Mit session.upload_progress.enabled=0 entfällt das Session-Upload-Progress. Das Advisory empfiehlt das ausdrücklich auch nach dem Patch, wenn du die Funktion nicht brauchst, und die PHP-Doku rät schon lange dazu, weil die Funktion Datei- und Feldnamen des Clients ungeprüft in den Session-Speicher schreibt. Die Einstellung ist laut Doku INI_PERDIR, gehört also in die php.ini oder die Pool-Konfiguration, und erst ein Neustart der Worker macht sie wirksam.
Unter PHP 8.5 entfernt fatal_error_backtraces=0 den Producer des bestätigten RCE-Falls. Nach dem Patch kannst du den Wert wieder einschalten, wenn dir die Backtraces bei der Fehlersuche helfen. Die radikalste Variante, pm.max_requests=1 in PHP-FPM, startet für jeden Request einen frischen Worker und lässt veraltetem Zustand keinen Nachfolger. Das kostet allerdings so viel Leistung, dass es nur für kleine Instanzen in Frage kommt. Für FrankenPHP in der Standardkonfiguration hat das Advisory keinen frühen Consumer nachgewiesen, immun sei es deshalb aber nicht. Den Worker-Modus, in dem eine Anwendung bewusst über viele Requests im Speicher bleibt, analysieren die Advisories nicht gesondert. Unabhängig davon reduziert jeder Fatal Error und jeder Timeout, den du im Log findest und behebst, die Gelegenheiten für einen Producer.
Abschied von mod_php
Der gefährlichste Pfad betrifft mod_php, weil dort der Shutdown auch nach einem gescheiterten Start läuft und weil Apache .htaccess-Direktiven mit php_value vor dem Start des Requests anwendet. PHP-FPM und FrankenPHP sind nicht immun, gegen FPM hat das Advisory über das Upload-Progress sogar Codeausführung validiert. Für CVE-2026-109618 ist dort aber kein anwendungsunabhängiger Exploit belegt. Betreibst du noch mod_php, hast du damit einen guten Grund für den Umstieg, zumal FPM den PHP-Prozess vom Webserver trennt und eigene Benutzer pro Pool erlaubt. Ein Ersatz für den Patch ist das nicht.
Der Worker als Gefangener
Gelingt die Übernahme trotzdem, entscheidet die Umgebung des Workers über den Schaden. Im Kubernetes-Pod sind das die bekannten Bausteine: runAsNonRoot, allowPrivilegeEscalation: false, entzogene Capabilities, seccompProfile: RuntimeDefault und der Pod Security Standard „restricted“. Mit readOnlyRootFilesystem: true und beschreibbaren emptyDir-Volumes nur für temporäre Dateien, Sessions und Caches kann eine Webshell weder Code noch Programmdateien dauerhaft ablegen. Ein minimales Base-Image ohne Shell und Paketmanager nimmt dem Angreifer die Werkzeuge, mit denen er weiterarbeiten will. Keine dieser Maßnahmen verhindert die Codeausführung selbst, weil sie im legitimen PHP-Prozess passiert, sie machen aber aus einer Übernahme eine Sackgasse.
Am meisten bringt dabei eine Egress-Sperre. Eine NetworkPolicy mit policyTypes: [Egress], die dem PHP-Pod nur DNS, Datenbank, Valkey oder Redis und die wirklich benötigten APIs erlaubt, verhindert das Nachladen weiterer Werkzeuge, eine Reverse Shell und den Abfluss gestohlener Daten an einen eigenen Server. Der Betriebsaufwand liegt in der Pflege der Freigaben, denn jeder neue externe Dienst braucht eine Regel. Zugangsdaten, die der Worker legitim braucht, schützt sie nicht. Datenbankpasswort und Encryption Key sind nach einer Übernahme als kompromittiert zu behandeln. Wie sich Egress-Regeln, Pod Security und Secrets für TYPO3 in der Praxis zusammensetzen, beschreibt die Serie TYPO3 auf Kubernetes unter IT-Grundschutz.
Wirkungslose Maßnahmen
Einige vertraute PHP-Härtungen greifen gegen diesen Mechanismus nicht. disable_functions und open_basedir setzt PHP innerhalb der jeweiligen PHP-Funktionen durch. Ein Angreifer, der über eine Speicherkorruption nativen Code im Prozess ausführt, ruft diese Funktionen gar nicht erst auf und umgeht so ihre Prüfungen. Das leite ich aus ihrer Funktionsweise ab. Laut Advisory schützen auch error_reporting, der Operator @, display_errors und log_errors nicht, weil der INI-Consumer unabhängig davon arbeitet, ob eine Warnung angezeigt oder geloggt wird. Daubois weist zudem darauf hin, dass das Abschalten des Upload-Progress gegen CVE-2026-109619 hilft, gegen CVE-2026-109618 aber nicht.
Regeln einer WAF, die Anwendungslogik schützen, Updates von TYPO3-Extensions und sauberer Anwendungscode ändern ebenfalls nichts, weil der Angriff vor dem ersten Byte Anwendungscode stattfindet. Ein CVE-Scanner schließlich kann in beide Richtungen danebenliegen. Er meldet gepatchte Distributionspakete mit alter Versionsnummer als verwundbar und übersieht ein eigenes Binary, dessen Version er nicht kennt. Verlass dich deshalb auf die Paketquelle und den Changelog, nicht allein auf die Zeichenkette hinter php -v.
Erkennung und Prüfung
Ob du betroffen bist, zeigt zuerst die Version. Prüfe mit php -v, php-fpm -v oder frankenphp version in jedem Container-Image und auf jedem Host, nicht nur auf dem, den du gerade vor Augen hast. Bei Distributionspaketen gilt der Changelog: Taucht dort CVE-2026-109618 auf, ist der Backport drin, auch wenn die Versionsnummer älter aussieht. Danach lohnt ein Blick auf die Konfiguration, die für die Übergangszeit zählt, also memory_limit, session.upload_progress.enabled und unter 8.5 fatal_error_backtraces, jeweils in der tatsächlich wirksamen Konfiguration des Web-SAPI und nicht der CLI.
Ob jemand angegriffen hat, ist schwerer zu sagen, denn das PHP-Team hat keine Indikatoren veröffentlicht, und das Advisory warnt ausdrücklich, dass saubere Logs keine Sicherheit beweisen. Plausibel sind aus meiner Sicht drei Spuren, die ich aus dem Mechanismus ableite. Häufungen von „Allowed memory size … exhausted“ bei Multipart-POSTs aus derselben Quelle passen zu Versuchen, den Speichermangel gezielt herbeizuführen. Abstürze von Workern, im FPM-Log als „exited on signal 11“ und im Apache-Log als Segmentation Fault, passen zu fehlgeschlagenen Versuchen mit beschädigtem Speicher. Und nach einem erfolgreichen Angriff fallen Kindprozesse des Web-Workers auf, die eine Shell, curl oder wget starten, was auch SOCRadar als Erkennungsregel empfiehlt. Ergänzend gehören neue PHP-Dateien im Webroot und in beschreibbaren Verzeichnissen sowie neue Cronjobs zur Prüfung.
Bei einem Verdacht gilt die Reihenfolge Isolieren, Patchen, Erneuern. CERT-EU empfiehlt, internetseitige Anwendungen vom öffentlichen Netz zu trennen, wenn ein sofortiges Update nicht möglich ist. Danach setzt du den Worker nicht von Hand zurück, sondern baust ihn aus einem sauberen, gepatchten Image neu auf, rotierst Datenbankzugänge, Encryption Keys und API-Tokens aus der Konfiguration und prüfst die Logs ab dem 20. Oktober.
Betreiberempfehlung
Akut handeln solltest du, wenn PHP vor 8.2.35, 8.3.36, 8.4.27 oder 8.5.12 Requests aus dem Internet bedient, ganz gleich ob über mod_php, FPM oder FrankenPHP. Am dringendsten ist mod_php, danach jede Instanz mit Uploads und Formularen. Spiel das Update heute ein, starte alle Worker neu und baue deine Images neu. Wenn das nicht sofort geht, schalte das Upload-Progress ab, unter 8.5 auch fatal_error_backtraces, begrenze Request-Größe und Multipart am Proxy und schotte die Instanz notfalls vom Netz ab.
Beobachten genügt nur dort, wo PHP ausschließlich als CLI für interne Jobs läuft und keine Daten von außen parst. Auch dann gehört das Update ins nächste Wartungsfenster, weil sich solche Einsatzorte erfahrungsgemäß schneller ändern als die Dokumentation darüber.
Für den Rest gilt, was ich bei jeder Lücke sage, und dieser Fall liefert das Argument gleich mit. Patches und Updates gehören automatisiert. Wenn Renovate oder Dependabot neue Basis-Images für PHP oder FrankenPHP und neue Digests deiner Wolfi-Images selbstständig als Merge Request vorschlagen und die Pipeline sie gegen eine Staging-Umgebung testet, war das Release vom 21. Oktober eine Sache von Stunden. Mit Chainguard-Images wäre der Fix so sogar vor dem offiziellen Termin in Produktion gewesen. Eine Karenzzeit für Routine-Updates schützt vor kompromittierten Releases, Sicherheitsupdates wie dieses nimmst du davon aus. Und die geänderte Semantik von memory_limit fällt dann im Test auf, wo ein Lasttest mit Exit-Code 137 harmlos ist, nicht erst in Produktion. Aktualisierst du von Hand, hast du die Vorwarnung vom Dienstagabend vielleicht gelesen, das Update aber im ungünstigsten Fall erst nach dem Urlaub eingespielt, zusammen mit allem anderen, was bis dahin liegen geblieben ist.
Häufige Fragen zu PHP2Shell
Bin ich mit FrankenPHP betroffen?+
Ja, FrankenPHP bettet den betroffenen PHP-Kern ein und gilt laut Advisory als vermutlich anfällig. Aktualisiere auf FrankenPHP 1.13.2, das mit PHP 8.5.12 und den anderen gepatchten Versionen gebaut ist, und baue deine Images neu.
Reicht memory_limit=-1 als Schutz?+
Was ist mit PHP 7.4, 8.0 und 8.1?+
Für diese Zweige gibt es keine offiziellen Releases. Der Mechanismus reicht laut Alexandre Daubois aus dem PHP-Security-Team bis PHP 5.3 zurück, deshalb solltest du von einer Betroffenheit ausgehen und auf einen Backport deiner Distribution oder ein Upgrade setzen.
War es falsch, dass Chainguard früher ausgeliefert hat?+
Das hängt davon ab, wie du die Hinweise auf Angriffe aus der Nacht zum Mittwoch bewertest, und die hat Chainguard nicht belegt. Für deinen Betrieb ist die wichtigere Frage, ob deine Pipeline einen Fix übernimmt, sobald er irgendwo erscheint.
Fazit
PHP2Shell ist kein exotischer Parserfehler, sondern das Ergebnis einer Abkürzung in der Fehlerbehandlung, die über Jahre in fast jede Ecke des Interpreters gewandert ist. Ein nicht-lokaler Sprung aus einer C-Funktion hinterlässt alles so, wie es in diesem Augenblick war, und ein langlebiger Worker trägt diesen Zustand in den nächsten Request. Die Lehre reicht über PHP hinaus. Wo ein Laufzeitsystem Fehler mit einem Sprung behandelt und Prozesse wiederverwendet, lohnt die Frage, was nach dem Fehler noch läuft und wer den Zustand danach liest. Die zweite Lehre betrifft den Betrieb: Der Termin eines Patches gehört dir nicht, und zwischen Leak und Fix lagen diesmal weniger als 24 Stunden. Vorbereitet bist du darauf nur, wenn du Updates ohne Handarbeit ausrollen kannst.
Quellen
- GitHub Advisory GHSA-w8hw-w9fx-j79w: RCE via OOM corruption (CVE-2026-109618)
- GitHub Advisory GHSA-6wmf-4rv5-p3rp: RCE via stale global state (CVE-2026-109619)
- php-src Fix-Commit 3584a98: Avoid state corruption via bailout
- PHP Foundation: Today's PHP Security Release, and How We're Strengthening PHP Security
- Alexandre Daubois: What the PHP2Shell rumors got wrong, from inside the PHP security team
- oss-security: CVE-2026-109618
- FrankenPHP Releases (v1.13.2)
- Socket: PHP Foundation Says Chainguard Broke Security Embargo
- The Register: PHP blames Chainguard
- Threat Landscape: PHP Core RCEs Leak Early
- Infosecurity Magazine: Critical PHP Vulnerabilities Leaked
- SOCRadar: PHP2Shell
- PHP-Handbuch: Laufzeitkonfiguration der Fehlerbehandlung (fatal_error_backtraces)
- PHP-Handbuch: Session-Konfiguration (session.upload_progress.enabled)
- Caddy Docs: request_body
- CISA Known Exploited Vulnerabilities Catalog
Ich prüfe deine PHP- und TYPO3-Plattform auf verwundbare Laufzeiten, setze Request-Grenzen und Container-Härtung um und baue die Pipeline, die einen Fix wie diesen in Stunden ausrollt.
PHP- und FrankenPHP-Audits, Egress-Regeln und Pod Security für TYPO3 auf Kubernetes, Update-Automatisierung mit Renovate für Basis-Images und Wolfi-Digests.
Plattform-Betrieb statt Beratung auf Papier: Ich prüfe, patche und härte deine Infrastruktur laufend.
