„sh -c /usr/sbin/sendmail -t -i“, Owner www-data. Diese Zeile habe ich letzte Woche in einer Prozessliste gefunden und dann gut vier Minuten mit erhöhtem Puls verbracht, bis mir wieder einfiel, dass PHPs mail() genau das tut, jedes einzelne Mal, ganz bewusst. Eigentlich ist dieser Fehlalarm schon die ganze Kolumne. Seit dem 21. Oktober patchen wir alle CVE-2026-109618 und CVE-2026-109619, das Duo, das inzwischen alle nur noch php2shell nennen, und die Community-Analysen zur Erkennung laufen immer wieder gegen dieselbe Wand: Der Exploit selbst ist ein Speicherfehler, der zuschlägt, bevor dein Anwendungscode überhaupt läuft, also zeigen deine Logs nichts. Erwischen kannst du nur, was danach kommt. Und das fällt nur auf, wenn dein Web-User normalerweise langweilig ist.

Mein Argument betrifft also die Architektur, nicht die Alert-Regeln. Wenn www-data regelmäßig Shells startet, .php-Dateien ins Document Root schreibt und Cronjobs ausführt, dann kann keine Erkennungsregel der Welt einen Angreifer von deinem ganz normalen Dienstag unterscheiden. Regeln können nur so scharf sein, wie deine Baseline ruhig ist. Ein Web-User, der nie etwas ausführt und nie Code schreibt, liefert dir ein Signal, dem du wirklich glauben kannst. Ein Web-User, der beides tut, wann immer einem Plugin danach ist, liefert dir ein Dashboard voller Level-8-Alerts, die bis Donnerstag alle zu ignorieren gelernt haben.

Zur Erinnerung, wie übel das hier ist: CVSS v4.0 9.2, ohne Authentifizierung, und es trifft PHP-FPM, mod_php, php-cgi, FrankenPHP und LiteSpeed gleichermaßen, wobei mod_php der schlimmste Fall ist, weil der Angreifer drin ist, bevor dein Router überhaupt geladen wird. Gefixte Builds sind 8.5.12, 8.4.27, 8.3.36 und 8.2.35. Für 8.1 und älter gibt es keinen offiziellen Fix, und wer auf Debian bullseye mit PHP 7.4 festsitzt, ging ebenfalls leer aus. Als das Advisory rauskam, kannte der Wazuh-Vulnerability-Feed noch keine der beiden CVEs, CISA KEV hatte sie noch nicht gelistet, und der einzige Proof of Concept, der herumging, war unverifiziert und womöglich vermint. Ein paar Tage lang haben sich etliche Teams gegen etwas verteidigt, das ihre Scanner nicht einmal benennen konnten.

Der stärkste Einwand verdient eine ehrliche Antwort, denn er stimmt. Echte PHP-Anwendungen machen diese Dinge mit Absicht. WordPress aktualisiert sich selbst, indem es PHP in seinen eigenen Verzeichnisbaum schreibt. Die Hälfte der CMS, die ich betreut habe, lässt geplante Jobs als Web-User laufen, weil das 2014 der Weg des geringsten Widerstands war. mail() startet eine Shell. Einer kleinen Agentur mit vierzig Kundenseiten zu sagen, sie solle das alles mitten im Notfall umbauen, ist eine Fantasie, und ich werde nicht so tun, als wäre es anders. Auch die Detection-first-Fraktion hat einen Punkt: Unter Beschuss kannst du nicht neu designen, also beobachtest du, was du hast, und nimmst das Rauschen in Kauf.

Ich lande trotzdem auf der anderen Seite, und zwar aus folgendem Grund: Jede dieser Gewohnheiten kannst du zu einem bekannten, überschaubaren Preis ablegen. Verschick Mails per SMTP über eine Bibliothek, und mail() forkt nie wieder. Lass Updates über einen Deploy-User durch deine Pipeline laufen und mach das Document Root für den Prozess, der es ausliefert, schreibgeschützt. Verschieb Cron auf einen eigenen Account. Nichts davon ist exotisch, und sobald es erledigt ist, wird aus einer gesampelten Prozessliste oder einem File-Integrity-Watcher statt eines verrauschten Hinweises ein nahezu sicherer Alarm. Ein Polling-Sample alle 60 Sekunden wird einen curl, der zwei Sekunden lebt, immer verpassen. Auf einem ruhigen Account reicht aber schon ein einziger Treffer, weil dort überhaupt nichts sein dürfte.

Noch etwas, das nichts mit cleverer Erkennung zu tun hat: Ein gepatchtes Paket patcht nicht die php-fpm-Worker, die bereits laufen. Ich habe diesen Monat zwei Server gesehen, die laut apt auf 8.3.36 waren und laut Realität noch auf dem verwundbaren Binary, weil niemand den Pool neu gestartet hatte. Ehrlich gesagt ist das der PHP-typischste Fehler überhaupt: Unser Shared-nothing-Modell verzeiht fast alles, nur nicht den Master-Prozess, der seit August läuft. Starte ihn neu. Und dann prüf nach, ob du es wirklich getan hast.

Für die Nachzügler auf 7.4 bis 8.1 ist der ehrliche Schritt, sie vom öffentlichen Internet zu nehmen, statt Monitoring um sie herum zu bauen und das dann Abdeckung zu nennen. Eine Tür zu beobachten, von der du weißt, dass du sie nicht abschließen kannst, hilft bestenfalls eine Woche lang. Danach ist es nur noch ein Plan, die Kompromittierung ein bisschen früher zu bemerken.

Deshalb würde ich wirklich gern von dir hören, denn ich vermute, die Antworten verlaufen entlang der Grenze zwischen Agenturen und Produktfirmen: Kann der User, der auf deinen Produktivservern heute PHP ausliefert, in das Verzeichnis schreiben, aus dem er ausliefert? Und falls ja, welches eine Feature würdest du verlieren, wenn du ihm das wegnimmst, und ist dieses Feature einen blinden Fleck in php2shell-Größe wert?