phpguard : Protégez les PHP non corrigés contre les attaques multipart php2shell
Nécessite le plan Pro (ou supérieur) de l'abonnement GetPageSpeed NGINX Extras.
Installation
Vous pouvez installer ce module sur toute distribution basée sur RHEL, y compris, mais sans s'y limiter :
- RedHat Enterprise Linux 7, 8, 9 et 10
- CentOS 7, 8, 9
- AlmaLinux 8, 9
- Rocky Linux 8, 9
- Amazon Linux 2 et Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-phpguard
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-phpguard
Activez le module en ajoutant ce qui suit au début de /etc/nginx/nginx.conf :
load_module modules/ngx_http_phpguard_module.so;
Ce document décrit nginx-module-phpguard v1.0.0 publié le 12 novembre 2026.
Protégez les PHP non corrigés contre php2shell (CVE-2026-109618, CVE-2026-109619 et CVE-2026-115560) au niveau de NGINX, sans toucher à PHP.
php2shell est atteint via le propre analyseur multipart/form-data de PHP. Cet analyseur s'exécute avant tout code applicatif et avant toute authentification, donc chaque point d'entrée PHP est exposé. De simples règles NGINX ne peuvent bloquer que toutes les requêtes multipart, ce qui casse les téléversements, et elles ratent les encodages que PHP accepte.
phpguard lit chaque corps multipart dans NGINX avant que PHP ne voie un seul octet,
et le vérifie avec un analyseur en flux qui reproduit celui de PHP. Il a été
vérifié sur PHP 5.6, 7.4, 8.1, 8.4 et 8.5, derrière PHP-FPM et derrière Apache
mod_php via proxy_pass.
Ce qu'il bloque
- Le champ de progression de téléversement de session, le déclencheur confirmé de php2shell, dans toutes les orthographes que PHP accepte : entre guillemets ou non, quelle que soit la casse, en-têtes repliés, lignes terminées par LF uniquement, types de contenu séparés par des virgules, corps chunked et HTTP/2.
- Les parties dissimulées. Seul le multipart canonique est accepté. Un préambule, un épilogue, des octets NUL ou de contrôle, un guillemet ambigu, ou une chaîne de frontière partout sauf à un véritable délimiteur sont refusés. Cela inclut la partie que PHP trouve après 5120 octets lorsqu'il ignore un téléversement qu'il ne conserve pas.
- Les analyses épuisant la mémoire. La mémoire d'analyse de chaque requête en PHP est estimée
avec un modèle de coût mesuré sur chaque version de PHP prise en charge, et la
requête est refusée au-delà de
phpguard_max_memory. Des entrées de fichier vides avec des noms profondément imbriqués, par exemple, peuvent sinon faire allouer à PHP plus de 100 Mo pour quelques kilo-octets d'entrée.
Seules les requêtes multipart sont inspectées. Les formulaires GET, JSON et encodés en URL ne coûtent qu'une seule vérification d'en-tête.
Configuration
Activez-le pour tout le serveur, afin que le corps soit vérifié là où il est lu pour la première fois :
server {
phpguard block;
location ~ \.php$ {
fastcgi_pass unix:/run/php-fpm.sock;
include fastcgi_params;
}
}
Commencez par phpguard detect; pour journaliser ce qui serait refusé sans
rien bloquer.
Directives
| Directive | Valeur par défaut | Contexte | Description |
|---|---|---|---|
phpguard off \| detect \| block |
off |
http, server, location | block (alias on) refuse ; detect se contente de journaliser |
phpguard_status <code> |
403 |
http, server, location | Statut pour les refus de politique (400-599). Les corps malformés reçoivent toujours 400 |
phpguard_progress_name <name> |
PHP_SESSION_UPLOAD_PROGRESS |
http, server, location | Nom de champ à refuser ; répétez pour en spécifier plusieurs. Définissez-le si session.upload_progress.name est modifié |
phpguard_max_memory <size> |
32m |
http, server, location | Mémoire d'analyse PHP estimée par requête. Gardez-la bien en dessous de votre plus petite memory_limit |
phpguard_max_parts <n> |
1000 |
http, server, location | Parties par corps |
phpguard_max_name_length <n> |
512 |
http, server, location | Octets dans un nom de champ |
phpguard_max_nesting <n> |
32 |
http, server, location | Niveaux de [ dans un nom de champ |
La variable $phpguard contient la raison du refus (vide lorsque la requête
est passée), par exemple dans un log_format.
Remarques
- Les barres de progression de téléversement de PHP (
session.upload_progress) cessent de fonctionner, car leur champ est exactement ce qui est refusé. - Les corps multipart sont entièrement mis en tampon avant que PHP ne les reçoive. C'est
le comportement par défaut de NGINX ; avec
fastcgi_request_buffering off, les téléversements cessent d'être transmis en flux à PHP. - Un nom de champ ou de fichier qui se termine par une barre oblique inverse est refusé. PHP analyse de toute façon mal de tels noms.
- phpguard couvre les chemins multipart portés par les requêtes de php2shell. Il ne corrige pas PHP : les erreurs d'épuisement de mémoire que votre application atteint via d'autres entrées, et les futurs bogues PHP hors de l'analyseur multipart, subsistent. Mettez PHP à jour lorsque vous le pouvez.