Production-Defense-Playbook: Nginx-Regeln, WAF-Virtual-Patches und PHP-FPM-Sandboxing
Production-Defense-Playbook: Nginx-Regeln, WAF-Virtual-Patches und PHP-FPM-Sandboxing
<time datetime="2026-10-23">Geprüft am 23. Oktober 2026</time>
Dieses Playbook beschreibt unterbrechungsfrei einsetzbare Schutzmaßnahmen gegen CVE-2026-109618 und CVE-2026-109619 (PHP2Shell). Die Reihenfolge ist bewusst gewählt: Der Patch steht an erster Stelle. WAF, Nginx und Sandboxing überbrücken die Zeit bis zum Rollout und reduzieren die Auswirkungen, falls ein Angriff dennoch gelingt. Jede Schicht ist einzeln deploybar und mit einem Rückfallplan versehen.
1. Mehrschichtige Architektur
Internet
│
▼
┌──────────────────────────────────────────────┐
│ Tier 0: CDN / Edge-WAF (z. B. Cloudflare) │
│ - Rate-Limits, bekannte Signaturen │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 1: Nginx-Perimeter │
│ - Upload-Pfade trennen, multipart 415/403 │
│ - Body-/Buffer-Limits, Timeouts │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 2: ModSecurity / Coraza Virtual Patch │
│ - Multipart-Feldprüfung, Anomalie-Score │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 3: PHP-FPM-Konfiguration │
│ - session.upload_progress Off │
│ - pm.max_requests, Restart-Schwellen │
└──────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ Tier 4: systemd-Sandbox + Seccomp │
│ - ProtectSystem, PrivateTmp, NoNewPrivileges│
│ - execve-Blockade (falls fachlich vertretbar)│
└──────────────────────────────────────────────┘
Die Schichten decken unterschiedliche Fehlermodi ab. Tier 1 kann durch Kodierung umgangen werden, Tier 2 durch unbekannte Feldvarianten, Tier 3 durch Fehlkonfiguration, Tier 4 durch notwendige Ausnahmen. Keine Schicht ist allein ausreichend.
2. Sofort einsetzbare Konfigurationen
2.1 Tier 1: Nginx-Perimeter
# /etc/nginx/conf.d/php2shell-hardening.conf
client_max_body_size 8m;
client_body_buffer_size 128k;
client_body_timeout 15s;
client_header_timeout 10s;
send_timeout 20s;
# Öffentliche Bereiche ohne Upload-Funktion
location ~ ^/(api|health|static|assets)/ {
if ($http_content_type ~* "multipart/form-data") {
return 415;
}
client_max_body_size 256k;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 20s;
}
# Upload-Bereich bewusst isolieren
location ^~ /upload/ {
client_max_body_size 64m;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
sudo nginx -t && sudo systemctl reload nginx
Der wichtigste Effekt ist nicht die 415-Antwort selbst, sondern die Trennung von Upload- und Nicht-Upload-Pfaden. Dadurch wird die Angriffsfläche auf die wenigen Endpunkte reduziert, die tatsächlich Dateien annehmen.
2.2 Tier 2: ModSecurity / Coraza
# /etc/modsecurity.d/php2shell.conf
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 67108864
SecRequestBodyInMemoryLimit 131072
SecRule MULTIPART_FILENAME|ARGS_NAMES|REQUEST_HEADERS \
"@rx (?i)PHP_SESSION_UPLOAD_PROGRESS" \
"id:1096181,phase:2,deny,status:403,log,msg:'PHP2Shell upload progress field'"
SecRule REQUEST_HEADERS:Content-Type "@rx (?i)multipart/form-data" \
"id:1096182,phase:1,pass,nolog,chain"
SecRule REQUEST_URI "@rx ^/(api|health|static|assets)/" \
"t:lowercase,deny,status:415,msg:'multipart to non-upload path'"
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/modsec_audit.log | grep -E '109618[12]'
Behalten Sie die Regel-IDs auch bei Coraza bei. Das erleichtert die Korrelation im SIEM und macht Reports über verschiedene Standorte hinweg vergleichbar.
2.3 Tier 3: PHP-FPM
; /etc/php/8.3/fpm/php.ini
session.upload_progress.enabled = Off
session.upload_progress.cleanup = On
expose_php = Off
; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
pm = dynamic
pm.max_children = 24
pm.max_requests = 300
emergency_restart_threshold = 5
emergency_restart_interval = 1m
request_terminate_timeout = 30s
sudo php-fpm -t
sudo systemctl reload php8.3-fpm
php -r 'echo ini_get("session.upload_progress.enabled"), PHP_EOL;'
2.4 Tier 4: systemd-Sandbox
# /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/php/sessions /var/www/uploads
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
MemoryDenyWriteExecute=yes
sudo systemctl daemon-reload
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p ProtectSystem -p PrivateTmp -p NoNewPrivileges
MemoryDenyWriteExecute=yes kann mit JIT oder bestimmten Erweiterungen kollidieren. Testen Sie die Option im Staging, bevor Sie sie produktiv setzen. Wenn sie nicht möglich ist, dokumentieren Sie die Ausnahme und kompensieren Sie durch Seccomp und Runtime-Erkennung.
3. Rollout-Reihenfolge und Rückfall
Die Reihenfolge folgt dem Verhältnis von Wirkung zu Risiko.
- Tier 3 – sofort, unterbrechungsfrei, größte Wirkung.
- Tier 1 – nach Prüfung der Upload-Pfade und Traffic-Muster.
- Tier 2 – zunächst im Log-Modus, dann auf
denyumschalten. - Tier 4 – nur nach Staging-Validierung.
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-$(date +%F)
# Rückfall
sudo cp /etc/php/8.3/fpm/php.ini.bak-2026-10-23 /etc/php/8.3/fpm/php.ini
sudo systemctl reload php8.3-fpm
sudo nginx -t && sudo systemctl reload nginx
4. Verifikationscheckliste
# 1) Callback deaktiviert
php -r 'echo ini_get("session.upload_progress.enabled"), PHP_EOL;'
# 2) Patchstand
php -v | head -1
# 3) Nginx-Regeln geladen
sudo nginx -T 2>/dev/null | grep -A2 'multipart/form-data'
# 4) WAF-Regeln aktiv
sudo tail -n 50 /var/log/modsec_audit.log
# 5) Sandbox aktiv
systemctl show php8.3-fpm -p ProtectSystem -p PrivateTmp -p NoNewPrivileges
# 6) Regressionsprüfung für Uploads
curl -sS -F "file=@/etc/hostname" https://example.com/upload/ -o /dev/null -w '%{http_code}\n'
Führen Sie die Regressionsprüfung nach jeder Änderung aus. Ein 415 auf einem legitimen Upload-Pfad ist ein Hinweis auf eine zu breite Regel.
5. Betriebliche FAQ
F1: Bricht ProtectSystem=strict bestehende Upload-Dateien?
Nein, solange die Upload-Verzeichnisse in ReadWritePaths stehen. Achten Sie jedoch auf Anwendungen, die temporäre Dateien in /tmp ablegen: PrivateTmp=yes isoliert diesen Pfad, sodass Prozesse außerhalb des Dienstes die Dateien nicht sehen.
F2: Funktioniert das auch in Docker/Kubernetes?
Ja, mit anderer Umsetzung. In Kubernetes nutzen Sie readOnlyRootFilesystem, allowPrivilegeEscalation: false, seccompProfile und NetworkPolicy.
F3: Blockiert die WAF legitime Multipart-Uploads?
Der Feldname PHP_SESSION_UPLOAD_PROGRESS wird von modernen Upload-Formularen nicht verwendet. Prüfen Sie dennoch Legacy-Anwendungen und aktivieren Sie die Regel zuerst im Log-Modus.
F4: Wie lange müssen die Maßnahmen bestehen bleiben? Bis alle PHP-Laufzeiten gepatcht sind. Danach können Sie die WAF-Regeln lockern, sollten aber die Deaktivierung des Callbacks beibehalten, wenn die Funktion nicht benötigt wird.
F5: Was ist mit Shared Hosting, bei dem Kunden eigene php.ini-Werte setzen?
Erzwingen Sie die Einstellung über php_admin_flag in der Panel-Konfiguration und verhindern Sie Overrides durch Kunden. Andernfalls kann ein einzelner vHost den Schutz aushebeln.
6. Shared Hosting und Panel-Integration
In Shared-Hosting-Umgebungen mit cPanel, Plesk oder CloudLinux reicht die globale php.ini nicht aus, weil Kunden eigene Werte setzen können. Erzwingen Sie die Deaktivierung über die Panel-Konfiguration und prüfen Sie, ob php_admin_flag in den vHost- oder Pool-Templates enthalten ist. CloudLinux alt-php und EasyApache 4 haben unterschiedliche Pfade; dokumentieren Sie beide Varianten und testen Sie sie nach jedem Panel-Update erneut.
Wichtig ist außerdem die Kommunikation an Kunden. Viele fürchten, dass die Deaktivierung ihre Upload-Formulare bricht. Stellen Sie ein kurzes Merkblatt bereit, das den Unterschied zwischen Fortschrittsanzeige und Dateiverarbeitung erklärt. Bieten Sie an, die Einstellung nach dem Patch wieder zu aktivieren, wenn ein konkreter Bedarf besteht – aber nur nach einer Risikobewertung. So vermeiden Sie Support-Tickets und verhindern, dass Kunden die Schutzmaßnahme eigenmächtig umgehen.
Für Provider mit vielen Mandanten ist ein zentrales Inventar entscheidend. Erfassen Sie pro vHost die PHP-Version, den Patchstand, den Status von session.upload_progress und die verwendete SAPI. Ohne diese Daten ist jede Aussage über die Gesamtrisikolage Spekulation. Ein einfaches CSV oder eine Datenbanktabelle genügt als Anfang; wichtiger als das Werkzeug ist die Vollständigkeit.
7. WAF-Tuning und Fehlalarme
Aktivieren Sie neue Regeln zunächst im Log-Modus und werten Sie mindestens 24 Stunden aus. Achten Sie auf legitime Mandanten mit älteren Upload-Formularen, die den Feldnamen noch verwenden. Diese Fälle brauchen eine eng gefasste Ausnahme pro Pfad, nicht eine globale Deaktivierung der Regel. Dokumentieren Sie jede Ausnahme mit Ablaufdatum und Verantwortlichem.
Ein häufiger Fehler ist die Kombination aus zu strenger WAF-Regel und fehlender Rückmeldung an die Anwendungsentwicklung. Wenn ein Upload-Endpunkt plötzlich 403 liefert, sucht das Entwicklungsteam oft an der falschen Stelle. Sorgen Sie für klare Fehlermeldungen und eine gemeinsame Dokumentation der Regel-IDs. Das reduziert die MTTR und verhindert, dass Schutzmaßnahmen aus Frust abgeschaltet werden.
Ein weiterer Aspekt ist die Dokumentation: Ohne klare Regel-IDs und Zuständigkeiten werden Schutzmaßnahmen im Betrieb schleichend abgeschaltet. Halten Sie daher fest, wer welche Schicht verantwortet und wann sie überprüft wird.
8. Fazit
PHP2Shell lässt sich nicht mit einem einzelnen Produkt abwehren. Engine-Konfiguration, Perimeter-Regeln, WAF und Sandboxing adressieren unterschiedliche Schritte der Angriffskette. Der wichtigste Schritt bleibt das Upgrade auf 8.5.12, 8.4.27, 8.3.36 oder 8.2.35. Alles andere ist Überbrückung – aber eine, die den Unterschied zwischen einem Vorfall und einem Datenverlust ausmachen kann.