Guide de Défense en Production : Règles Nginx sans Coupure, Patchs Virtuels WAF et Confinement PHP-FPM
Guide de Défense en Production : Règles Nginx sans Coupure, Patchs Virtuels WAF et Confinement PHP-FPM
<time datetime="2026-10-23">Vérifié le: 23 octobre 2026</time> · Référentiel Opérationnel : Nginx 1.24+, PHP-FPM, Systemd 245+
Dans les infrastructures critiques et les plateformes à fort trafic transactionnel, suspendre les services web pour procéder à des mises à jour logicielles immédiates sans période de qualification préalable représente un risque d'interruption inacceptable. De plus, sur des parcs étendus de serveurs physiques et de machines virtuelles, le déploiement d'une nouvelle version de runtime PHP peut nécessiter un temps de planification significatif.
Ce manuel d'ingénierie fournit aux équipes SRE, DevOps et RSSI une feuille de route défensive échelonnée en trois phases : de l'interception périmétrique instantanée à chaud (sans aucune coupure de trafic) jusqu'au confinement hermétique des processus au niveau du noyau Linux via systemd.
1. Feuille de Route de Durcissement en 3 Phases
┌─────────────────────────────────────────────────────────────┐
│ Phase 1 : Blocage Périmétrique Immédiat (Délai : 10 minutes) │
│ - Rejet des requêtes suspectes sur Nginx / HAProxy │
│ - Déploiement de règles virtuelles WAF Cloudflare / AWS │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 2 : Neutralisation de la Configuration (Délai : 30 min)│
│ - Inscription de session.upload_progress.enabled = Off │
│ - Rechargement gracieux (Graceful Reload) des pools FPM │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Phase 3 : Confinement Kernel par Bac à Sable (Délai : 1 h) │
│ - Système de fichiers en lecture seule (ProtectSystem) │
│ - Neutralisation des privilèges résiduels (NoNewPrivileges) │
└─────────────────────────────────────────────────────────────┘
2. Phase 1 : Filtrage d'Urgence sur les Proxies Inverses (Nginx & HAProxy)
En interceptant le flux HTTP sur la couche du mandataire inverse (Reverse Proxy), vous neutralisez l'attaque avant même que les paquets ne soient transmis au socket Unix ou TCP du démon PHP-FPM.
Configuration pour Nginx (/etc/nginx/conf.d/php2shell_mitigation.conf) :
# Allocation d'un tampon mémoire suffisant pour analyser le corps initial
client_body_buffer_size 128k;
# Mappage de sécurité pour détecter les méthodes POST multipart
map $http_content_type $is_multipart_post {
default 0;
~*multipart/form-data 1;
}
server {
# ... directives SSL et nom de domaine ...
location / {
# Règle WAF Coraza / ModSecurity (si le module dynamique est chargé)
# SecRule REQUEST_HEADERS:Content-Type "@rx (?i)multipart/form-data" \
# "id:2026101,phase:2,t:none,chain,deny,status:403,log,msg:'PHP2Shell bloqué au niveau Nginx'"
# SecRule REQUEST_BODY "@rx (?i)name=[\"']?PHP_SESSION_UPLOAD_PROGRESS"
proxy_pass http://backend_php;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Validation et rechargement sans coupure de session :
sudo nginx -t && sudo systemctl reload nginx
Règles Alternatives pour HAProxy (/etc/haproxy/haproxy.cfg) :
frontend fe_web_https
bind *:443 ssl crt /etc/ssl/certs/cert.pem
mode http
acl is_multipart hdr_sub(content-type) -i multipart/form-data
acl is_post method POST
acl has_progress_payload req.body -m sub -i PHP_SESSION_UPLOAD_PROGRESS
http-request deny status 403 if is_post is_multipart has_progress_payload
default_backend be_php_fpm
3. Phase 2 : Automatisation de la Configuration à l'Échelle avec Ansible
Pour garantir une application exhaustive et homogène sur des dizaines ou centaines d'instances sans risque d'omission humaine, déployez un playbook Ansible automatisé :
Playbook Ansible (php2shell_remediation.yml) :
---
- name: Neutralisation Globale de PHP2Shell
hosts: all
become: true
tasks:
- name: Découvrir tous les fichiers php.ini présents
ansible.builtin.find:
paths:
- /etc/php
- /etc
- /opt/cpanel/ea-php*
patterns: "php.ini"
recurse: true
register: found_ini_files
- name: Forcer la désactivation de session.upload_progress
ansible.builtin.lineinfile:
path: "{{ item.path }}"
regexp: '^;?\s*session\.upload_progress\.enabled\s*='
line: 'session.upload_progress.enabled = Off'
state: present
loop: "{{ found_ini_files.files }}"
notify: Recharger les démons PHP-FPM
handlers:
- name: Recharger les démons PHP-FPM
ansible.builtin.service:
name: "{{ item }}"
state: reloaded
loop:
- php8.3-fpm
- php8.2-fpm
- php-fpm
failed_when: false
4. Phase 3 : Confinement Système de PHP-FPM avec Systemd
Pour appliquer une stratégie de défense en profondeur imperméable, nous configurons le gestionnaire de services systemd afin d'enfermer les processus travailleurs dans un bac à sable (Sandbox) au niveau du noyau.
Même si un attaquant parvenait à détourner le pointeur d'instruction $rip vers une commande shell, le processus ne disposerait d'aucun droit pour altérer le système de fichiers ou accéder aux dossiers des utilisateurs.
Créez le fichier de durcissement /etc/systemd/system/php8.3-fpm.service.d/override.conf :
[Service]
# 1. Rendre l'intégralité du système de fichiers inaccessible en écriture
ProtectSystem=strict
ReadWritePaths=/var/www /var/log /tmp /var/lib/php/sessions
# 2. Masquer complètement les répertoires personnels (/home, /root)
ProtectHome=true
# 3. Interdire formellement toute élévation de privilèges ou binaire setuid
NoNewPrivileges=true
# 4. Verrouiller la modification des paramètres du noyau et le chargement de modules
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
# 5. Isoler les fichiers temporaires dans un namespace privé (/tmp unique)
PrivateTmp=true
# 6. Interdire la création de sockets réseau bruts (Raw Sockets)
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
Activation immédiate de la protection renforcée :
sudo systemctl daemon-reload
sudo systemctl restart php8.3-fpm
5. Règles Complémentaires pour Apache HTTPD (mod_php / mod_proxy_fcgi)
Pour les infrastructures exploitant encore le serveur HTTP Apache :
# Filtrage perimetrique via mod_rewrite
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
RewriteCond %{THE_REQUEST} PHP_SESSION_UPLOAD_PROGRESS [NC]
RewriteRule .* - [F,L]
</IfModule>
# Directive globale de sécurisation du module PHP
<IfModule mod_php.c>
php_flag session.upload_progress.enabled Off
</IfModule>
6. Synthèse des Standards d'Exploitation Sécurisée
- Isolation des Droits Fichiers : Le compte
www-datane doit jamais être propriétaire des scripts PHP applicatifs en production. Les fichiers doivent appartenir à un utilisateur de déploiement dédié et être accessibles en lecture seule pour le serveur web. - Surveillance des Événements Runtime : Maintenez actives les sondes Falco et Auditd pour détecter toute anomalie comportementale dès la première seconde.
- Plan de Fin de Vie : Planifiez la mise hors service de tous les conteneurs ou machines exécutant encore PHP 7.4 ou PHP 8.0 au profit des versions LTS modernes (PHP 8.2 ou 8.3).
7. Règles Spécifiques pour Caddy Server et OpenResty Lua
Pour les infrastructures modernes déployant Caddy Server ou OpenResty (Nginx + LuaJIT) :
Configuration pour Caddy Server (Caddyfile) :
example.fr {
# Détection des requêtes POST contenant le paramètre de téléversement
@php2shell {
method POST
header Content-Type *multipart/form-data*
path *.php /
}
# Bloquer immédiatement les requêtes suspectes à la frontière
handle @php2shell {
# Directive d'évaluation ou proxy vers backend durci
php_fastcgi unix//run/php/php8.3-fpm.sock {
env session.upload_progress.enabled "0"
}
}
}
Script de Filtrage OpenResty / Lua (/etc/openresty/waf_php2shell.lua) :
-- Inspection synchrone du corps de requête dans la phase access_by_lua
local content_type = ngx.req.get_headers()["content-type"]
if content_type and string.find(string.lower(content_type), "multipart/form-data") then
ngx.req.read_body()
local body = ngx.req.get_body_data()
if body and string.find(body, "PHP_SESSION_UPLOAD_PROGRESS") then
ngx.log(ngx.ERR, "PHP2Shell Exploit Attempt Blocked by OpenResty Lua Layer")
ngx.exit(ngx.HTTP_FORBIDDEN)
end
end
Cette approche programmatique au niveau de la couche mandataire offre une flexibilité totale d'adaptation sans nécessiter de recompilation de modules tiers sur votre flotte de serveurs.
Déclaration d'Atténuation sous Docker Compose (docker-compose.yml) :
services:
php:
image: php:8.3-fpm-alpine
environment:
- PHP_INI_SCAN_DIR=:/etc/php.d
volumes:
- ./security-override.ini:/etc/php.d/99-security.ini:ro
restart: unless-stopped
En montant ce fichier en lecture seule, vous préservez l'intégrité de la directive session.upload_progress.enabled = Off à chaque redéploiement de conteneur.
Ingress Controller Nginx sous Kubernetes (ingress.yaml) :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app-ingress
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
if ($content_type ~* "multipart/form-data") {
set $check_multipart 1;
}
nginx.ingress.kubernetes.io/configuration-snippet: |
more_clear_input_headers "PHP_SESSION_UPLOAD_PROGRESS";
Cette annotation garantit l'assainissement des paquets dès la couche d'entrée du cluster Kubernetes avant routage vers les services d'arrière-plan.