php2shell (CVE-2026-109618/109619) : corriger PHP et se protéger en attendant
Deux failles PHP notées 9.2, exploitables sans authentification avant tout code applicatif : versions corrigées, parades temporaires, vérification et lab.
L'essentiel
- Deux CVE notées 9.2, corrigées en 8.2.35, 8.3.36, 8.4.27 et 8.5.12 ; PHP 8.1 et antérieurs n'ont plus de correctif officiel.
- Exploitables sans authentification, avant tout code applicatif ; mod_php est la configuration la plus exposée.
- Parade immédiate :
session.upload_progress.enabled = Offdansphp.iniou le pool (pas.user.ini), puis remplacement de tous les workers. - Vérifiez chaque binaire (FPM, mod_php, CGI, panneau) : le
phpdu terminal ne dit rien de PHP-FPM. - Le lab github.com/ikow/php2shell-poc sert à éprouver sa parade sur une machine isolée, jamais sur le système d'un tiers.
Le 20 octobre 2026, l'équipe sécurité de PHP a annoncé une publication d'urgence pour deux vulnérabilités notées 9.2 en CVSS v4.0, programmée le lendemain à 14:00 UTC, sans aucun détail technique. Les détails ont fuité avant l'heure, un outil baptisé « PHP2Shell » a circulé, et les versions corrigées sont sorties le 21 octobre : 8.2.35, 8.3.36, 8.4.27 et 8.5.12.
La meilleure lecture technique du moment est l'analyse de Qi Deng, « php2shell: Dissecting PHP's Critical Session Upload Progress Vulnerabilities », accompagnée d'un laboratoire reproductible publié sur GitHub : github.com/ikow/php2shell-poc. Cet article en reprend l'essentiel côté défense : ce qui est touché, ce qu'il faut mettre à jour, ce qu'on peut couper en attendant, et comment vérifier que la parade est réellement active.
Ce que dit l'avis officiel
L'avis publié sur php.net décrit deux défauts du moteur :
- CVE-2026-109618 — exécution de code par corruption sur manque de mémoire. Une erreur d'allocation peut interrompre PHP alors que des données internes de la requête ne sont que partiellement initialisées ; le nettoyage de fin de requête les traite ensuite comme valides.
- CVE-2026-109619 — exécution de code par état global périmé. Un état interne propre à une requête peut survivre à la libération de sa mémoire ; une requête suivante, traitée par le même worker, peut l'utiliser avant sa réinitialisation.
Le périmètre est large : toute installation PHP qui sert des requêtes web, qu'il s'agisse de PHP-FPM, d'Apache avec mod_php, de php-cgi, de FrankenPHP ou de LiteSpeed. L'avis juge le risque le plus élevé pour mod_php, avec une exploitation démontrée dans laquelle une requête non authentifiée déclenche l'attaque avant que le moindre code applicatif ne s'exécute.
La surface d'attaque : session.upload_progress
La recherche publique converge vers une fonction que peu d'administrateurs connaissent : session.upload_progress. Introduite en PHP 5.4 et activée par défaut, elle enregistre dans la session l'avancement des envois de fichiers en multipart/form-data, pour alimenter une barre de progression.
Le danger tient à l'endroit où elle s'exécute : dans la couche SAPI, pendant l'analyse du corps de la requête, avant que l'application ne prenne la main. Concrètement :
- aucune authentification n'est nécessaire ;
- aucun code de l'application n'intervient ;
- n'importe quel point d'entrée PHP qui accepte un POST suffit, même s'il n'a jamais prévu de recevoir un fichier ;
- la fonction écrit dans les fichiers de session des données en partie choisies par l'émetteur de la requête.
Ce que la recherche de Qi Deng a mis au jour
En auditant le code source, le chercheur relève plusieurs défauts dans le traitement de la progression d'envoi :
- Fixation de session : l'identifiant de session peut être lu dans le corps de la requête sans tenir compte de
session.use_only_cookies(correctif proposé dans la PR #22113). - Nom de fichier non filtré : le nom du fichier envoyé est stocké tel quel dans les données de session.
- Use-after-free dans la désérialisation : un destructeur d'objet appelé pendant le nettoyage peut réentrer dans
unserialize()et relire de la mémoire déjà libérée, ce qu'AddressSanitizer confirme. - Sérialiseur de session non réinitialisé d'une requête à l'autre sous FPM.
- Destructeurs exécutés même quand le décodage de la session échoue.
Il en tire trois chaînes menant à l'exécution de code. Leurs prérequis sont ce qui compte pour évaluer votre exposition :
| Chaîne | Ce qu'il faut côté application |
|---|---|
| Injection dans le fichier de session + inclusion de fichier local | Une faille d'inclusion de fichier local (LFI), cas classique des extensions WordPress mal écrites |
| Use-after-free de la désérialisation | Un appel à unserialize() sur des données atteignables (Laravel et Magento sont cités) |
| Désaccord de sérialiseur entre répertoires | Deux répertoires aux session.serialize_handler différents (via .user.ini, fréquent quand plusieurs applications cohabitent) et une classe exploitable déjà chargée, typiquement par opcache.preload |
Les trois chaînes ont été vérifiées en laboratoire sous PHP 8.2.34. Une nuance s'impose : ces descriptions sont celles du chercheur, écrites avant la publication des détails officiels. Elles recoupent en partie l'avis : le sérialiseur qui survit d'une requête à l'autre rappelle « l'état global périmé » de la CVE-2026-109619. En revanche, l'avis décrit aussi un chemin par manque de mémoire que la recherche n'aborde pas. Tout le monde s'accorde sur deux points : mettre à jour, et désactiver session.upload_progress en attendant.
Suis-je concerné ?
Selon la recherche, toutes les versions depuis PHP 5.4.0 sont touchées, jusqu'à 8.5.11. Les versions corrigées sont 8.2.35, 8.3.36, 8.4.27 et 8.5.12. Les branches 8.1 et antérieures ne reçoivent plus de correctif du projet PHP.
Le piège classique consiste à vérifier le mauvais PHP. La commande php du terminal est le binaire CLI : elle ne dit rien de PHP-FPM, d'un mod_php chargé dans Apache ni des versions qu'un panneau d'hébergement embarque. Il faut interroger chaque binaire qui sert réellement du web :
# Quels PHP servent réellement des requêtes ?
ps -eo args | grep -E 'php-fpm|php-cgi|lsphp|frankenphp' | grep -v grep | sort -u
apache2ctl -M 2>/dev/null | grep -i php # un mod_php chargé ?
# Version et réglages effectifs, binaire par binaire
php-fpm8.3 -v
php-fpm8.3 -i | grep -E 'session.upload_progress.enabled|session.use_only_cookies'
# Paquets corrigés disponibles (Debian/Ubuntu)
apt list --upgradable 2>/dev/null | grep -i php
Deux angles morts : -i ne montre pas les surcharges posées par pool FPM (php_admin_value) ou par virtual host, qu'il faut relire dans leurs fichiers ; et sur Ubuntu, le numéro amont ne bouge pas, puisque le correctif arrive comme une nouvelle révision du paquet. Un PHP « 8.3.6 » peut donc être corrigé ou non : seul l'avis de sécurité de la distribution permet de trancher. Debian 13, de son côté, a reçu directement la 8.4.27.
Mettre à jour : la seule correction
- Paquets de distribution : installez la révision corrigée dès sa publication, puis redémarrez PHP-FPM, et Apache si mod_php est chargé. Tant que les anciens workers tournent, l'ancien code reste en mémoire.
- Panneaux d'hébergement (Plesk, cPanel…) : ils livrent leurs propres builds, une par branche installée. Mettez-les toutes à jour, y compris celles qu'aucun site n'utilise plus.
- Conteneurs : reconstruisez les images à partir d'une base corrigée ; redémarrer un conteneur ne change pas le PHP qu'il contient.
- PHP 8.1 et antérieurs : l'équipe sécurité a publié des rétroportages minimaux en source pour 7.4, 8.0 et 8.1. Sinon, il faut passer par un fournisseur LTS ou migrer. Si vous dépendez du dépôt bénévole remirepo, l'avis prévient qu'il n'aurait pas de mise à jour le jour de la publication et conseille les paquets de la distribution ou la compilation.
- PHP 8.2 arrive en fin de vie le 31 décembre 2026 : c'est le moment de planifier la suite.
En attendant le paquet : les parades temporaires
L'avis donne une parade par CVE. Celle de la CVE-2026-109619 est la plus simple et la plus utile :
; /etc/php/8.3/fpm/conf.d/99-php2shell.ini
; (même fichier dans cgi/conf.d et apache2/conf.d si ces SAPI sont utilisées)
session.upload_progress.enabled = Off
session.use_only_cookies = On
- Le réglage doit s'appliquer avant l'analyse de la requête : dans
php.ini, un pool FPM ou la configuration du serveur. Niini_set()ni un.user.inine conviennent, parce qu'ils interviennent trop tard. - Tous les workers doivent être remplacés : rechargement ou redémarrage de chaque service PHP-FPM, redémarrage d'Apache pour mod_php.
- Sous PHP 8.5, ajoutez
fatal_error_backtraces = 0, qui ferme un autre chemin confirmé. - La recherche recommande aussi un
session.serialize_handleridentique dans tous les répertoires d'un même serveur.
La parade de la CVE-2026-109618 est plus délicate : memory_limit = -1 imposé par php_admin_value (donc non surchargeable par l'application), un post_max_size bas (8M sans envoi de fichiers, 32M sinon, upload_max_filesize en dessous), des limites de taille et de nombre de parties côté serveur web, et une limite mémoire par cgroup ou conteneur comme filet. L'avis précise de ne pas recourir à RLIMIT_AS ni RLIMIT_DATA, qui peuvent justement provoquer l'échec d'allocation vulnérable.
Vérifiez ensuite, au lieu de supposer :
# Une application utilise-t-elle la progression d'envoi ?
grep -rn --include='*.php' -E 'upload_progress|PHP_SESSION_UPLOAD_PROGRESS' /var/www
# Configuration valide, rechargement, valeur effective
sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm
php-fpm8.3 -i | grep session.upload_progress.enabled # attendu : Off => Off
Retour d'expérience : ce qu'on oublie en durcissant un parc
J'ai posé cette parade le jour même sur une quinzaine de binaires PHP répartis sur plusieurs serveurs. Le réglage lui-même tient en une ligne. Le vrai travail a été de savoir où le poser :
- Le mod_php oublié. Un Apache chargeait encore mod_php alors que les sites passaient par PHP-FPM. Il n'apparaît pas sous le nom « php » dans la liste des processus, et c'est pourtant la configuration que l'avis juge la plus exposée.
- Les versions fantômes des panneaux. Un panneau d'hébergement installe plusieurs branches en parallèle, dont des branches en fin de vie que plus aucun site n'utilise. Chacune a son propre répertoire de configuration, et chacune doit recevoir la parade.
- Les machines oubliées. Une VM en PHP 8.1, sans correctif possible, redémarrait automatiquement avec son hôte et tournait depuis plusieurs jours. Elle a été arrêtée plutôt que durcie : la meilleure parade reste de ne pas exposer ce qu'on n'utilise pas.
- Le test avant/après. Pour chaque serveur, une liste d'URL représentatives, avec le code HTTP et la taille de la réponse relevés avant et après. C'est ce test qui a montré qu'un durcissement voisin,
cgi.fix_pathinfo=0, cassait les routes/index.php/wp-json/de WordPress sous Plesk : il a été retiré. La désactivation desession.upload_progress, elle, n'a changé aucune réponse. - La note de sortie. Chaque fichier de parade est consigné, avec la décision à prendre après la mise à jour : le retirer ou le garder. Pour
session.upload_progress, aucune application ne s'en sert : le garder ne coûte rien.
Repérer une tentative
Quelques signaux à surveiller, sans illusion : leur absence ne prouve rien.
- Des POST
multipart/form-datavers des URL qui ne reçoivent jamais de fichier. - Le champ nommé par
session.upload_progress.name(PHP_SESSION_UPLOAD_PROGRESSpar défaut) dans des requêtes vers des applications qui ne l'utilisent pas. Si votre WAF inspecte les corps de requête, c'est une règle simple à poser, partielle comme les autres parades. - Des workers PHP-FPM qui meurent sur signal :
grep -E 'exited on signal (6|11)' /var/log/php8.3-fpm.log. - Des fichiers au contenu inattendu dans le répertoire
session.save_path. - Des processus lancés par l'utilisateur du serveur web (un shell,
curl,wget), que l'observabilité eBPF avec Falco détecte au moment où ils démarrent.
Le lab : tester sa parade, pas la production des autres
Le dépôt ikow/php2shell-poc fournit un environnement Docker (PHP 8.2.34 volontairement vulnérable, en FPM derrière nginx), trois scripts de vérification (un par chaîne, sans charge offensive selon son auteur) et une analyse technique détaillée dans docs/ANALYSIS.md.
Pour un administrateur, son intérêt est de rejouer le scénario sur une machine isolée, d'appliquer la parade, puis de constater ce qu'elle couvre et ce qu'elle ne couvre pas. Le chercheur comme l'avis officiel le disent : couper la progression d'envoi ne ferme pas tous les chemins. Le lab permet de le constater concrètement, et c'est la meilleure preuve que seule la mise à jour corrige tout.
La leçon
Une fonction activée par défaut depuis 2012, dont presque personne ne se sert, a fourni toute la surface d'attaque. C'est l'argument le plus concret qui soit pour désactiver ce qu'on n'utilise pas, avant qu'une CVE ne le rappelle. Le second enseignement est moins visible : le jour d'une faille critique, appliquer le correctif prend dix minutes, alors que savoir quels PHP tournent, et où, peut prendre la journée. Cet inventaire se prépare à froid, comme le reste de la checklist de sécurité d'un serveur Linux.
Le scénario n'est pas nouveau : une couche de framework atteignable avant toute authentification, comme pour React2Shell côté Next.js. Plus le code exécuté avant votre application est volumineux, plus il compte dans votre surface d'attaque.
Sources
- Avis de sécurité php.net du 21 octobre 2026 (GHSA-w8hw-w9fx-j79w, GHSA-6wmf-4rv5-p3rp)
- Qi Deng, « php2shell: Dissecting PHP's Critical Session Upload Progress Vulnerabilities »
- Lab et scripts de vérification : github.com/ikow/php2shell-poc
Questions fréquentes
Désactiver session.upload_progress suffit-il ?
Mon application utilise-t-elle la progression d'envoi de PHP ?
grep -rn -E 'upload_progress|PHP_SESSION_UPLOAD_PROGRESS' sur vos sources tranche la question : sans résultat, la désactivation n'a aucun effet visible.Ma distribution affiche toujours PHP 8.3.6 : suis-je corrigé ?
Je suis encore en PHP 8.1 ou plus ancien : que faire ?
Peut-on lancer les scripts du lab contre sa production ?
Cet article vous a plu ?
Cet article vous a-t-il été utile ?
Merci pour votre retour !
Commentaires
Expert en cybersécurité et administration Linux. J'aide les entreprises à sécuriser et optimiser leurs infrastructures critiques.