defense php2shell defense playbook

Guide de Défense en Production : Règles Nginx sans Coupure, Patchs Virtuels WAF et Confinement PHP-FPM

php2shell.run Research Lab •

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

  1. Isolation des Droits Fichiers : Le compte www-data ne 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.
  2. Surveillance des Événements Runtime : Maintenez actives les sondes Falco et Auditd pour détecter toute anomalie comportementale dès la première seconde.
  3. 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.