PHP2Shell: So habe ich meine Server bis zum Patch geschützt

Am 20. Oktober 2026 wurden zwei schwere Lücken im PHP-Kern veröffentlicht. Der gängige Name ist PHP2Shell, die Kennungen lauten CVE-2026-109618 und CVE-2026-109619. Der CVSS-Wert liegt bei 9,2. Sie erlauben Codeausführung auf dem Server ohne Anmeldung, und Angriffe wurden bereits beobachtet.

Schema zu PHP2Shell: fünf Kreise, verbunden mit einem Kasten mit Terminal-Symbol

In diesem Artikel erkläre ich, was das ist und welche PHP-Versionen betroffen sind. Ich erzähle, wie ich die Server, die ich betreue, abgesichert habe, solange es keinen Patch gab. Außerdem beschreibe ich, wie ich zum endgültigen Update gekommen bin. Hier findest du auch das Skript, das ich benutzt habe, und die Befehle zum Starten.

Was ist PHP2Shell

Das Problem liegt darin, wie PHP Anfragen vom Typ multipart/form-data liest. Das ist das Format, mit dem der Browser Dateien sendet. PHP verarbeitet es, bevor irgendein Skript startet.

Darum braucht es kein Upload-Formular. Eine einzige Anfrage an irgendeine PHP-Datei der Seite kann genügen.

Die beiden Lücken in Kürze:

  • CVE-2026-109618. Wenn der Speicher ausgeht, während PHP die Anfrage liest, werden Daten beschädigt. Der Schaden zeigt sich beim Aufräumen am Ende der Anfrage und kann zur Codeausführung führen.
  • CVE-2026-109619. Ein Teil des Zustands einer Anfrage bleibt im Speicher. Eine spätere Anfrage im selben Prozess verwendet ihn wieder.

Diese technischen Details stammen aus der Analyse des niederländischen NCSC und aus anderen öffentlichen Quellen. Ich habe nicht versucht, die Lücken auf meinen Servern auszunutzen.

Welche PHP-Versionen sind betroffen

Die korrigierten Versionen erschienen am 21. Oktober 2026: PHP 8.5.12, 8.4.27, 8.3.36 und 8.2.35. Jede ältere Version ist betroffen.

Für die Zweige 8.1 und älter gibt es keine offizielle korrigierte Version, weil sie nicht mehr unterstützt werden. PHP hat nur minimale Backports des Quellcodes für 7.4, 8.0 und 8.1 veröffentlicht. Für noch ältere Zweige ist nichts bekannt.

Das Risiko betrifft jede Art, PHP auszuführen. Am stärksten ausgesetzt ist mod_php. Auch PHP-FPM, php-cgi, FrankenPHP und LiteSpeed zählen. Dazu kommen alle Anwendungen darüber: WordPress, Joomla, Drupal, Nextcloud, phpMyAdmin, Roundcube und das ISPConfig-Panel.

Bei Debian gibt es ein Detail, das man kennen sollte. Debian übernimmt die Korrekturen als Backport und ändert die PHP-Versionsnummer nicht. Ein Server kann eine niedrige Nummer zeigen und trotzdem schon korrigiert sein. Deshalb vergleicht das Skript keine Versionen. Es prüft, ob das Changelog des installierten Pakets die beiden CVEs nennt.

Die Schutzmaßnahme, bis der Patch da ist

Als die Lücken öffentlich wurden, waren die Patches für die Distributionen noch nicht verfügbar. Ich wollte die Server nicht ungeschützt lassen. Also habe ich das Risiko mit einem Skript verringert.

Die Schutzmaßnahme ist kein Patch. Wer session.upload_progress abschaltet, schließt nur einen der bekannten Angriffswege zur CVE-2026-109619. Gegen die CVE-2026-109618 hilft das nicht. Die Maßnahme sollte die Angriffsfläche bis zum Update verkleinern, nicht das Update ersetzen.

Was das Skript macht

Das Skript ist für Debian 12 und 13 gedacht. Es funktioniert mit Apache (mod_php, FastCGI, suPHP, PHP-FPM) und mit Nginx (PHP-FPM). Es erkennt Server, die mit ISPConfig verwaltet werden, ob Apache oder Nginx.

Die Optionen:

  • Ohne Optionen prüft es nur. Für jedes PHP-Paket kontrolliert es, ob das Changelog die beiden CVEs nennt. Es meldet Prozesse, die noch eine alte Binärdatei nutzen, und liest die aktiven Einstellungen und die offenen Web-Ports aus.
  • --update aktualisiert zuerst den Paketindex.
  • --logs sucht verdächtige Spuren: neue PHP-Dateien in Upload- oder Cache-Ordnern, typische Webshell-Muster, Verweise auf php2shell in den Logs. Das ist eine Heuristik und beweist keine Kompromittierung.
  • --harden wendet die unten beschriebene Absicherung an.
  • --dry-run zeigt zusammen mit --harden, was passieren würde, ohne etwas zu ändern.
  • --skip-disable-functions lässt zusammen mit --harden disable_functions unangetastet.
  • --rollback nimmt zurück, was --harden angewendet hat.

Was sich mit –harden ändert

Für jede PHP-Version in /etc/php legt das Skript die Datei mods-available/cve-hardening.ini an. Es aktiviert sie mit phpenmod für die SAPIs fpm, apache2 und cgi. So gilt der Schutz für alle Seiten auf einmal.

Die originalen php.ini bleiben unberührt. Die CLI wird nicht angefasst, also laufen composer, artisan und wp-cli weiter.

Die Datei setzt drei Dinge:

  • session.upload_progress.enabled = Off
  • expose_php = Off
  • disable_functions mit exec, passthru, shell_exec, system, proc_open, popen und pcntl_exec, zusätzlich zu den bereits vorhandenen.

Danach testet das Skript die Konfiguration und lädt PHP-FPM und Apache nur neu, wenn der Test besteht. Vor dem Schreiben legt es ein Backup in /var/backups/php-cve-hardening/ an.

Die Grenzen, die du kennen solltest

  • disable_functions kann manche Anwendungen kaputt machen, zum Beispiel WordPress mit bestimmten Plugins oder Magento. Dann nimm --rollback oder starte erneut mit --skip-disable-functions.
  • Die Direktiven php_admin_value in den PHP-FPM-Pools haben Vorrang vor der globalen Datei. Das Skript meldet sie, ändert sie aber nicht.
  • Bei ISPConfig werden die Dateien in pool.d vom Panel neu erzeugt. Overrides korrigierst du im Panel unter Sites, Website, Options, Custom php.ini settings, nicht von Hand.
  • Die php.ini einzelner Seiten (FastCGI- oder suPHP-Modus von ISPConfig) und PHP-Versionen in /opt werden nicht verwaltet. Sie müssen separat geprüft werden.

So startest du das Skript

Das Skript liegt hier: https://cisilino.com/mscripts/php20261.sh

Bevor du etwas aus dem Internet ausführst, lies es. Du kannst es herunterladen und öffnen:

wget -qO php20261.sh https://cisilino.com/mscripts/php20261.sh
less php20261.sh

Wenn du es direkt aus der Ferne starten willst, brauchst du Root-Rechte. Das sind die Befehle, in der Reihenfolge, die ich empfehle.

1. Nur prüfen, mit Aktualisierung des Paketindex:

wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --update

2. Absicherung simulieren, ohne Änderungen:

wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --harden --dry-run

3. Anwenden:

wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --harden

4. Entfernen, falls etwas kaputtgeht:

wget -qO- https://cisilino.com/mscripts/php20261.sh | sudo bash -s -- --rollback

Nach --harden und nach --rollback musst du nichts von Hand neu starten. Das Skript lädt PHP-FPM und Apache selbst neu, nachdem es die Konfiguration getestet hat. Ein Neustart ist dagegen nach dem Update des PHP-Pakets nötig, weil die laufenden Prozesse die alte Binärdatei im Speicher halten:

sudo systemctl restart 'php*-fpm' apache2

Die Anführungszeichen um php*-fpm sind wichtig. Ohne sie expandiert die Shell das Sternchen, nicht systemctl. Wenn der Server nur Nginx nutzt, lass apache2 weg.

Das Ergebnis des Skripts ist auch ein Exit-Code: 0, wenn alles in Ordnung ist, 1 bei Warnungen, 2 wenn das System verwundbar ist oder etwas getan werden muss.

Auf Servern mit ISPConfig: ispconfig-multipart-guard

ISPConfig-Logo

Wer ISPConfig nutzt, hat ein zusätzliches Werkzeug. Das Projekt hat das Skript ispconfig-multipart-guard veröffentlicht, beschrieben in der offiziellen ISPConfig-Meldung.

Das Prinzip ist einfach. Wenn das Problem in Multipart-Anfragen liegt, stoppt der Webserver sie, bevor sie PHP erreichen:

  • Es blockiert Anfragen mit Content-Type: multipart/... für alle Seiten, für ausgewählte Seiten oder für das Control Panel;
  • bei Nginx antwortet es auf PHP-Anfragen der blockierten Seiten mit 415, statische Dateien bleiben unverändert. Bei Apache antwortet es mit 403, auch bei statischen Dateien;
  • standardmäßig blockiert es nur das ISPConfig-Panel. Die Seiten blockierst du mit Befehlen wie block all oder block esempio.it;
  • es legt einen Cronjob an, der alle 5 Minuten seine Hooks wiederherstellt, falls ISPConfig die Konfiguration neu schreibt;
  • es hat eine Liste erlaubter IPs, die weiterhin Dateien hochladen dürfen.

Es läuft auf Debian und Ubuntu, mit ISPConfig 3.2 oder 3.3 (auch ohne ISPConfig), mit Nginx oder mit Apache 2.4 und neuer.

Der Preis: Auf blockierten Seiten funktionieren Uploads nicht. Das gilt auch für CMS-Medien, Formularanhänge, Anhänge im Webmail und Importe in phpMyAdmin. Normale Seiten und urlencoded-Formulare laufen weiter. Auch das ist eine vorläufige Maßnahme und keine Korrektur.

Dieses Skript und meines schließen sich nicht gegenseitig aus. Meines greift in die PHP-Konfiguration ein, das von ISPConfig in den Webserver. Ich habe sie nicht in der Praxis verglichen und kann dir nicht sagen, wie sie sich zusammen in jeder Konfiguration verhalten.

Das endgültige Update, in zwei Schritten

Die Schutzmaßnahmen sollten nur kurz gelten. Ich habe die Server aktualisiert, sobald die Updates verfügbar waren, in zwei Schritten:

  1. Direkt kompilieren. Solange die Distribution auf dem jeweiligen Server ihr Paket noch nicht veröffentlicht hatte, habe ich PHP aus dem Quellcode in den korrigierten Versionen kompiliert.
  2. Offizielles Paket. Als das offizielle Paket der Distribution für diesen Server erschien, habe ich die Kompilierung durch das Paket ersetzt.

Der zweite Schritt ist wichtig. Ein von Hand kompiliertes PHP aktualisiert sich nicht zusammen mit dem Rest des Systems. Das Paket der Distribution schon, und das Skript kann das über das Changelog prüfen.

Was du jetzt tun solltest

  1. Aktualisiere PHP auf 8.5.12, 8.4.27, 8.3.36 oder 8.2.35. Unter Debian kannst du auch das Paket der Distribution nehmen, wenn das Changelog die beiden CVEs nennt.
  2. Starte PHP-FPM und Apache neu und lass das Skript dann ohne Optionen noch einmal laufen. Es darf keine Probleme mehr melden.
  3. Wenn du PHP 8.1 oder älter nutzt, plane den Wechsel auf einen unterstützten Zweig. Für diese Zweige gibt es keine offizielle korrigierte Version.
  4. Lass session.upload_progress deaktiviert, bis zur nächsten Korrekturversion, falls deine Anwendung es nicht braucht.
  5. Prüfe Logs und neue Dateien, mit --logs oder von Hand, und suche nach PHP-Dateien, die in Upload-Ordnern aufgetaucht sind.
  6. Vergiss PHP in Containern, Images und Appliances von Dritten nicht. Sie müssen separat aktualisiert werden.
  7. Wenn deine Seite WordPress nutzt: Das Plugin Cisilino Site Defense Network löst PHP2Shell nicht. Es schützt aber den Login und führt ein Aktivitätsprotokoll, das hilft zu verstehen, was auf der Seite passiert.

Quellen

ISPConfig, PHP, Debian und die anderen genannten Namen sind Marken der jeweiligen Inhaber. Sie dienen nur zur Identifizierung der Produkte.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

WordPress Cookie Hinweis von Real Cookie Banner