PHP zwei RCE-Lücken am 21. Oktober: was wir am eigenen Stack gemessen haben

Um 12:59 UTC lag die Advisory des PHP-Projekts im Netz, nach unabhängigen Berichten eine Stunde vor dem angekündigten Termin. Kurz danach meldeten sich CERT-EU und die NSA. Behoben wurden PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35, zwei Schwachstellen mit CVSS v4.0 9.2. Gemessen haben wir an diesem Tag statt übersetzt, und der interessanteste Befund steht nicht in der Advisory, sondern in unserer Container-Startzeit.
Was die beiden Lücken tun
CVE-2026-109618 sitzt im Aufräumen nach einem Speicherengpass: Bricht eine Request-Verarbeitung wegen erschöpften Speichers ab, läuft die Bereinigung über einen Zustand, der nicht mehr stimmt, und daraus wird Codeausführung. CVE-2026-109619 lässt Request-Zustand über das Ende seiner eigenen Gültigkeit hinaus überleben, der nächste Request arbeitet mit falschen globalen Daten. Beide brauchen keine Anmeldung und beginnen dort, wo jeder Browser anfängt: an einem Request.
Die Disclosure-Gruppe, das PHP-Projekt mit OpenAI Codex Security, der Firma Calif und Wiz Research, schreibt auf der gemeinsamen Seite php2shell.com, dass der Code seit PHP 5.4 mitläuft. Das sind mehr als zehn Jahre Angriffsfläche. Nach unseren Gegenquellen liegt PHP bei 71,8 Prozent aller Websites, deren Server-Sprache bekannt ist. Bei dieser Breite schaffen das nur wenige Betreiber in einer Nacht.
Was wir abstellten, bevor das neue Bild lief
Beide Fehler wachsen mit dem, was ein Request mitbringen darf. Der erste wird über den Weg erreichbar, auf dem PHP Speicher reserviert und wieder freigibt. Wir fanden am betroffenen Host 512 MB Speicher je Skript, 512 MB Request-Körper und 1 GB je Upload, dazu die Fortschrittsablage für Uploads angeschaltet. Diese Ablage erzeugt für jedes Teilstück Session-Zustand, und Session-Zustand ist genau das, was der erste Fehler nach einem Abbruch falsch aufräumt.
Gestellt haben wir auf 32 MB Request-Körper und 24 MB Upload, die Fortschrittsablage ist aus, und das Speicherlimit je Skript steht bei uns auf unbegrenzt (Nachtrag 22.10.). Denn ein Limit klingt nach Schutz, und genau dieses Limit ist der Auslöser: Überschreitet ein Request es, verlässt PHP die laufende Funktion per Sprung statt mit einer Fehlermeldung, und beim Aufräumen bleibt ein halbfertiger Zustand, aus dem der erste Fehler wird. Die Advisory nennt unbegrenzt deshalb als Teilmaßnahme und warnt, dass der Prozess dann eher an die Systemgrenze läuft. Ersetzt ist damit nichts. Dazu eine zweite Lehre: Eine Grössengrenze allein in der PHP-Konfiguration ist keine Grenze, weil ein Mehrfach-Request ohne verwertbare Längenangabe die Prüfung passiert. Begrenzen muss das Frontend.
Dazu kam nichts: kein Gerät und keine Leistung gegen virtuelles Nachrüsten, die eine Lücke in der Sprache selbst hätte zudecken können. Ein grosser WAF-Betreiber hatte seine Regeln neun Stunden vor der Disclosure live, wir aber stehen hinter keinem solchen Betreiber. Geschlossen wurden an diesem Tag Panel und interne API-Pfade, Adress-Gates in unserer eigenen Kante, vorher schon vorhanden und nun bis auf die letzten Pfade gezogen.
Der Engpass war am Patch-Tag das Image
Wir betreiben das CMS der Gruppe in einem eigenen Bild. Als die Versionen da waren, gab es im offiziellen Docker-Katalog für 8.4.27 und 8.5.12 zunächst nur RC1-Markierungen; die fertigen Apache-Bilder kamen laut Registry-Zeitstempel um 16:48 UTC für 8.5.12-apache und um 17:31 UTC für 8.4.27-apache. Das Alpine-Paket stand da noch auf 8.5.11. Warten auf das offizielle Bild hätte bedeutet, dass unsere Produktivsysteme über Mittag mit der Lücke gestanden hätten.
Gebaut haben wir deshalb um 17:07 UTC selbst, auf Basis der Webdevops-Ebene mit dem offiziellen FPM-Alpine-Zweig darüber, weil das offizielle PHP-Bild die Sprache aus dem Quellcode erzeugt. Die 43 Minuten zwischen unserem Build und dem offiziellen Apache-Bild sind kein Held, sie sind der Unterschied zwischen einem gemessenen Zustand und einem Hoffnungswert.
Wie gross unsere PHP-Fläche wirklich war
Alle 207 Hostnamen unserer Inventur haben wir öffentlich aufgelöst, 198 antworten von aussen, und diese prüften wir per HTTP. Sechs Endpoints liefern Inhalte, zwei zeigen darin ein PHP. Beruhigend klingt das erst, bis wir denselben Host von der anderen Seite ansehen: Unsere englische Produktsite läuft ebenfalls auf PHP und liefert nach aussen keinen Marker, der das verrät. Weder Header noch Cookie-Pfad noch Versionszeile geben einen Hinweis, wegrationalisieren lässt sich die Instanz damit nicht.
Am Abend zählten wir acht PHP-Instanzen im Betrieb: eine auf 8.5.12 und drei auf 8.4.27 auf dem Host der Gruppe, zwei auf 8.5.12 auf unserer Hetzner-Maschine, ein Feed-Leser auf 8.5.11 hinter einem Adress-Gate und ein Domänencontroller mit mod_php 8.3.6, den wir nicht messen können. Zwei davon waren Synchronisations-Jobs, einer ein Dienst für Werkzeug-Schnittstellen, keine war eine Seite. Genau deshalb wären sie in einer reinen Website-Zählung untergegangen.
Der Neustart steht in keiner Versionszeile
Der schnelle Weg führt in den Container und dort in die Befehlszeile von PHP. Das beweist die installierte Datei, kaum etwas über den laufenden Arbeiter. Die Advisory nennt den Schritt ausdrücklich: Apache, PHP-FPM und Langzeit-Worker müssen nach dem Update neu gestartet werden.
Nachgewiesen haben wir den Neustart über die Container-Startzeit: Unser Bild lief um 17:07 UTC, die betroffenen Container starteten um 18:25:11, 20:10:16 und 20:28:10 UTC, alle drei Werte liegen nach dem Build. Dazu ein Grund, der in fast jeder Überschrift fehlt: CVE-2026-109619 kann Serverspeicher aus anderen Requests lesen, inklusive Passwörter, Token, API-Schlüssel und Zertifikate, ohne dass ein Angreifer Code ausführen muss. Ein ungepatchter Host leakt, ein gepatchter ohne Neustart leakt ebenfalls.
Deshalb steht bei uns nach dem Tausch eine Rotate-Liste, und deshalb prüfen wir Startzeiten statt der Versionszeile zu glauben.
Was der Fund durch ein Modell für uns ändert
Der praktische Unterschied zu einer normalen Lücke ist das Zeitfenster: abgestimmte Veröffentlichung mit dem Update, ohne öffentlichen Angriffsaufbau. Das Wissen ist trotzdem für alle lesbar. Die Frage, ob ein freies Modell das auch gekonnt hätte, führt am Punkt vorbei, und die Finder schreiben das selbst: Modelle fangen nicht von selbst bei 20 Jahre alten Ausnutzungsprimitiven an, ein Mensch muss die Richtung setzen. Ihr Forscher hat sie bewusst auf die alten PHP-Stellen von 2004, 2020 und 2024 angesetzt; die Modelle haben die Arbeit beschleunigt; den Fund hat ihr Forscher gemacht.
Unsere Rechenstraße aus fünf Grafikkarten ist eine Inferenzstrecke und kein Schwachstellen-Scanner, keines dieser Modelle lief bei uns gegen den Bestand (Korrektur 22.10.). Was bleibt, ist eine Annahme über die Zeit: Bei einer Sprache, die so viele Betriebe gleichzeitig läuft, braucht ein Angriffsaufbau aus unserer Sicht Stunden und nicht Wochen. Das verschiebt unsere Reihenfolge, das Werkzeug bleibt das gleiche.
Was offen bleibt
Ehrliche Deckung ist eine Bruchzahl. Zwei öffentliche PHP-Endpunkte laufen auf der behobenen Linie und sind zusätzlich durch Adress-Gates beim Panel und bei den API-Pfaden geschlossen; die Request- und Uploadgrenzen stehen auf 32 MB und 24 MB, das Speicherlimit je Skript ist aufgehoben, die Fortschrittsablage aus, weil der eine der beiden Fehler über aufwendige Request-Körper schneller erreichbar ist. Ein Feed-Leser läuft noch auf 8.5.11, nur aus dem Verwaltungsnetz erreichbar, und tauscht beim nächsten planmässigen Bild. Der Domänencontroller mit mod_php bleibt ungeprüft, weil wir keinen Zugriffsweg haben, die Windows-Flotte ist in dieser Zählung nicht enthalten. Wer eine Deckung von eins zu eins erwartet, kriegt von uns eine Liste mit Lücken.
Ein System haben wir an diesem Tag ganz vom Netz genommen: unsere Web-Analytik auf Matomo-Basis, mit mod_php 8.4.25 und öffentlichem Tracker der unbequemste Kandidat. Seither läuft die Auswertung ohne PHP-Schicht, der Beitrag zur Umstellung beschreibt Umbau und Migration der Historie. Das ist pragmatischer als jede nächtliche Panik.
Weiterführende Quellen
- PHP Change Log 8.5.x: php.net/ChangeLog-8.php
- Advisory zu CVE-2026-109618: GHSA-w8hw-w9fx-j79w
- Advisory zu CVE-2026-109619: GHSA-6wmf-4rv5-p3rp
- Gemeinsame Seite der Finder: php2shell.com
- Research-Writeup bei Calif, dort auch die Zeitachse 2004 bis 2026: calif.io/php2shell
- CERT-EU 2026-018: cert.europa.eu
- Nutzung von PHP nach W3Techs: w3techs.com
Müssen wir diese Nacht alle PHP-Systeme vom Netz nehmen?+
Für unsere Umgebung nein, weil wir am selben Tag auf die behobenen Linien gegangen sind und die öffentlich erreichbaren Zugangsflächen zusätzlich über Adress-Gates geschlossen sind. Die Empfehlung, Systeme offline zu nehmen, richtet sich an Betreiber, die nicht patchen können. Das PHP-Projekt selbst hält fest, dass es keine vollständige Mitigation gibt und das Update der einzige sichere Weg ist. Wer patchen kann, patcht. Wer nicht kann, nimmt heraus, trennt das Netz ab oder zieht eine Wartungsseite davor.
Reicht das Update auf 8.5.12 oder 8.4.27?+
Nein, und das ist der Punkt, an dem die meisten Messungen zu früh aufhören. Apache, PHP-FPM und alle Langzeit-Worker müssen nach dem Update neu gestartet werden, sonst läuft der angreifbare Prozess mit dem alten Zustand weiter. Die zweite Lücke kann zudem Serverspeicher aus anderen Requests auslesen, darunter Passwörter, Authentifizierungstoken, API-Schlüssel und Zertifikate, ohne dass überhaupt Code ausgeführt wird. Ein Host, der gepatcht, aber nicht neu gestartet ist, hat in diesem Fenster weiter Secrets geliefert. Deshalb gehört für uns eine Schlüsselrotation zur Abschlussliste.
Betrifft das nur unsere Websites?+
Nein. Betroffen ist jede PHP-Instanz, die Web-Requests bedient, also auch interne Werkzeuge und Synchronisations-Jobs mit HTTP-Endpunkt. In unserer Zählung am Abend liefen drei Container mit 8.4.x, die niemand als Website auf dem Schirm hatte, weil sie Nebenprodukte eines Images sind. Genau diese Instanzen sind unangenehm, weil sie selten hinter einem öffentlichen Eingang hängen, aber selten gepatcht werden.
senn-tech