Le 21 octobre 2026, le projet PHP a publié des versions d'urgence pour deux bugs critiques permettant à un attaquant non authentifié d'exécuter du code sur un serveur web. Ce duo est suivi sous les identifiants CVE-2026-109618 et CVE-2026-109619 et porte le nom de PHP2Shell. Les deux obtiennent un score de 9,2 sur l'échelle CVSS v4.0, les deux touchent toutes les installations PHP servant du HTTP, et les honeypots d'Amazon ont enregistré des tentatives d'exploitation le 17 octobre, quatre jours avant que le correctif n'existe.
PHP tourne sur 69,3 % des sites web dont le langage côté serveur est connu (W3Techs). Si vous exploitez une API PHP, un backend Laravel ou Symfony, un site WordPress, ou une image de conteneur embarquant PHP, cela vous concerne.
Ce guide couvre ce que font les bugs, quelles versions sont affectées, comment vérifier vos serveurs, ce que les mitigations vous apportent réellement, et comment prouver que votre API fonctionne toujours après le correctif.
En bref
| Question | Réponse |
|---|---|
| De quoi s'agit-il ? | Deux bugs de sûreté mémoire dans la gestion des requêtes de PHP, chaînés en exécution de code à distance |
| CVE | CVE-2026-109618 (corruption par épuisement de mémoire), CVE-2026-109619 (état obsolète, use-after-free) |
| Gravité | CVSS v4.0 9,2 chacun (des identifiants divulgués par erreur indiquaient 10,0 ; PHP affirme que c'était faux) |
| Authentification requise ? | Non |
| Affecté | Tous les SAPI qui servent du HTTP : PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, LiteSpeed |
| Versions corrigées | 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| PHP 8.1 et antérieurs | Aucune version officielle ; rétroportages minimaux du code source pour 7.4, 8.0, 8.1 ; 5.4 à 7.3 n'obtiennent rien |
| Exploité ? | Tentatives observées depuis le 17 octobre (Amazon), premières attaques signalées par Cloudflare |
| Correctif | Mettre à niveau, redémarrer chaque worker. Les contournements réduisent le risque mais ne le suppriment pas |
Ce qu'est réellement PHP2Shell
Les deux bugs partagent une même cause racine : PHP peut laisser sa mémoire dans un état corrompu lorsqu'une requête se termine anormalement, et le code de nettoyage fait ensuite confiance à cet état corrompu.
CVE-2026-109618 : corruption par épuisement de mémoire au démarrage de la requête. Lorsque PHP analyse les données de formulaire dans $_POST, le code de table de hachage du moteur Zend incrémente son compteur d'entrées avant d'allouer la nouvelle clé. L'analyse de Datadog le retrace jusqu'à _zend_hash_str_add_or_update_i() dans zend_hash.c. Si cette allocation dépasse memory_limit, PHP lève une erreur fatale et abandonne l'insertion. Le compteur inclut désormais un bucket qui n'a jamais été initialisé. À l'arrêt, PHP détruit $_POST et traite chaque bucket compté comme réel.
Le point qui devrait inquiéter les équipes API : cela se produit pendant que PHP analyse encore le corps de la requête, avant qu'une seule ligne de votre code ne s'exécute. Votre middleware d'authentification, votre limiteur de débit et votre validation des entrées ne voient jamais la requête. Datadog l'a reproduit avec un simple corps application/x-www-form-urlencoded, pas un envoi de fichier.
CVE-2026-109619 : état global obsolète d'une requête à l'autre. PHP conserve un état propre au worker qui pointe vers de la mémoire propre à la requête. Si une erreur fatale interrompt la réinitialisation de cet état, ou si un diagnostic d'arrêt tardif le recrée après le nettoyage, la requête suivante sur le même worker lit de la mémoire libérée. PHP a confirmé deux chemins :
- Traces d'erreurs fatales (PHP 8.5 uniquement) : un diagnostic de configuration dans une requête ultérieure lit un état de trace résiduel. Exécution de commandes confirmée sur mod_php.
- Traitement des envois de fichiers : après une erreur fatale, la progression d'envoi de session peut laisser un état derrière elle pour un envoi ultérieur. La progression d'envoi est activée par défaut et s'exécute même si votre application n'appelle jamais
session_start(). Exécution de commandes confirmée sur mod_php et PHP-FPM.
L'équipe de sécurité de PHP classe mod_php comme le risque le plus élevé, puisque l'exploit démontré se déclenche à partir d'une seule requête non authentifiée avant le chargement du moindre code applicatif.
Qui l'a découvert (et pourquoi l'IA fait les gros titres)
Les bugs remontent à du code introduit dans PHP 5.4.0, ils ont donc séjourné dans la base de code pendant plus d'une décennie. La PHP Foundation a crédité Calif, OpenAI et Wiz.
Des articles de Decrypt et The Next Web indiquent que le chercheur de Calif, Ryan Austin, a découvert les failles en août lors d'un audit du code source assisté par GPT-5.6 Sol d'OpenAI. OpenAI affirme que sa propre équipe a trouvé une version plus complète en septembre dans le cadre de ses travaux « Patch the Planet ». Wiz affirme avoir trouvé les deux bugs de son côté en utilisant Gemini 4 Argon, Wiz Atlas et GPT-5.6 Sol. Le crédit est contesté ; la conclusion ne l'est pas. Les audits assistés par IA trouvent désormais des bugs mémoire vieux d'une décennie dans des infrastructures critiques, et les attaquants disposent des mêmes modèles.
Chronologie : pourquoi le correctif est arrivé un jour plus tôt
| Date (2026) | Ce qui s'est passé |
|---|---|
| Août à septembre | Calif, OpenAI et Wiz signalent les bugs en privé |
| 17 oct. | Le honeypot MadPot d'Amazon enregistre une tentative d'exploitation |
| 20 oct. | Les identifiants CVE fuient sur Hacker News et les réseaux sociaux ; PHP publie un avis anticipé ; CERT-EU émet l'avis 2026-018 |
| 21 oct., 14:00 UTC | PHP publie les correctifs, avancés du 22 oct. après la fuite ; la CISA publie une alerte ; Cloudflare signale des attaques précoces |
| 22 oct. | Aucun des deux CVE ne figure encore dans le catalogue KEV de la CISA |
Amazon conseille d'enquêter à partir du 17 octobre (la FAQ de Tenable retrace toute la chronologie), et non à partir de la date de divulgation. À la publication, l'équipe PHP a déclaré avoir inspecté le code de preuve de concept apparu après la fuite et n'avoir trouvé aucun exploit fonctionnel. watchTowr a depuis affirmé avoir reproduit les deux bugs. Considérez tout dépôt « PoC » que vous trouvez comme suspect : le code d'exploitation non vérifié transporte souvent des malwares.
Êtes-vous affecté ? Vérifiez en cinq minutes
1. Vérifiez le runtime qui sert le trafic, pas votre portable. Le binaire CLI peut différer du module FPM ou Apache en version et en configuration.
# On the server or inside the running container
php -v
php-fpm -v
# Apache mod_php: check the loaded module version
apachectl -M 2>/dev/null | grep -i php
Ou exposez-le une fois depuis une route protégée et retirez-le ensuite :
<?php echo PHP_VERSION, ' ', PHP_SAPI;
2. Comparez avec les versions corrigées.
| Branche | Vulnérable | Corrigé dans |
|---|---|---|
| 8.5 | < 8.5.12 | 8.5.12 |
| 8.4 | < 8.4.27 | 8.4.27 |
| 8.3 | < 8.3.36 | 8.3.36 |
| 8.2 | < 8.2.35 | 8.2.35 |
| 7.4 à 8.1 | Toutes | Rétroportages du code source uniquement, ou LTS d'un fournisseur |
| 5.4 à 7.3 | Toutes | Aucun correctif |
Debian, Ubuntu, Red Hat et d'autres distributions rétroportent souvent les correctifs sans changer la chaîne de version d'origine. Si php -v affiche un numéro plus ancien, consultez l'avis de votre distribution avant de paniquer.
3. Inventoriez le PHP caché. Les conteneurs construits avec FROM php:8.3-fpm il y a des mois, les appliances de fournisseurs, les compilations maison, les panneaux d'administration sur des sous-domaines oubliés. Cisco a déjà ouvert un avis pour vérifier sa propre gamme de produits. Shodan compte plus de 1,9 million d'hôtes PHP exposés sur Internet ; un chercheur estime que 1,3 million ou plus pourraient être vulnérables.
Comment corriger
# Debian / Ubuntu (Ondřej Surý PPA or distro packages)
sudo apt update && sudo apt install --only-upgrade 'php8.*'
# RHEL / Rocky / Alma with Remi
sudo dnf upgrade 'php*'
# Official Docker image: rebuild on a fixed tag
docker pull php:8.4.27-fpm
docker build --pull -t my-api:patched .
Ensuite, faites l'étape que la plupart des équipes oublient : redémarrez ou remplacez chaque worker PHP. Un pool FPM en cours d'exécution conserve l'ancien binaire en mémoire. Redémarrez php-fpm, rechargez Apache et faites tourner tous les conteneurs. CVE-2026-109619 concerne un état qui persiste entre les requêtes au sein d'un worker, donc un worker non redémarré reste vulnérable.
Sur 8.1 ou antérieur, vous avez trois options : un paquet LTS d'un fournisseur, une compilation à partir du rétroportage du code source de PHP, ou une mise à niveau vers 8.2+. La CISA recommande de mettre hors ligne les systèmes PHP non corrigeables, et le NCSC britannique ainsi que le FBI ont dit la même chose.
Mitigations si vous ne pouvez pas corriger aujourd'hui
Celles-ci font gagner du temps. Gina Peter Banyard, de l'équipe principale de PHP, a déclaré à BleepingComputer qu'aucune mitigation ne bloque entièrement les attaques.
Pour CVE-2026-109618 :
- Définissez
memory_limit=-1là où les applications ne peuvent pas le remplacer (php_admin_valuedans le pool FPM ou la configuration Apache). Cela bloque le chemin connu, mais pas toutes les façons dont une allocation peut échouer. - Abaissez
post_max_size(8M sans envois de fichiers, 32M avec). - Limitez la taille du corps de requête au niveau du reverse proxy (
client_max_body_sizedans nginx), et fixez des limites de mémoire de conteneur ou de cgroup comme frontière de disponibilité. - N'utilisez pas les limites de processus
RLIMIT_ASouRLIMIT_DATA. Elles peuvent provoquer précisément les échecs d'allocation dont l'exploit a besoin.
Pour CVE-2026-109619 :
- Définissez
session.upload_progress.enabled=0dans la configuration chargée avant l'analyse de la requête. - Sur PHP 8.5, définissez aussi
fatal_error_backtraces=0. - Masquer les erreurs avec
display_errors=0ou@ne sert à rien. L'état obsolète existe toujours.
En périphérie : Cloudflare a déployé des règles WAF pour les vecteurs connus sur toutes les offres, y compris la gratuite. Son CTO les a qualifiées de temporaires.
Après toute modification de php.ini, redémarrez chaque worker, sinon elle ne prend pas effet.
Signes que vous avez déjà été touché
Les conseils de détection de Datadog se résument à quatre signaux :
- Ligne de log :
PHP Fatal error: Allowed memory size of ... bytes exhaustedavecUnknown on line 0. Cela signifie que PHP est mort pendant le démarrage de la requête, avant qu'un script ne s'exécute. - Crashes Apache :
AH00052: child pid ... exit signal Segmentation fault (11), probablement une tentative d'exploit ratée. - Publications de formulaire étranges : un nom de champ long de plusieurs mégaoctets, ou des octets binaires et nuls dans un champ au nom vide.
- Le signal fort : un worker web (
php-fpm,apache2,httpd) qui lance un shell.
Ne vous fiez pas aux codes de statut. L'exploit affiné de Datadog renvoyait un HTTP 200 et le worker continuait de tourner. Fouillez les logs jusqu'au 17 octobre.
Prouvez que votre API fonctionne toujours après le correctif : laissez votre agent l'exécuter
Corriger PHP en production sous pression, c'est ainsi que vous découvrez qu'un incrément de version mineure a changé quelque chose. Avant et après le déploiement du correctif, exécutez votre suite de tests d'API contre l'environnement de staging, et laissez votre agent de codage le faire avec Apidog CLI.
Configuration (une fois) :
npm install -g apidog-cli
apidog auth login --with-token <your-access-token>
apidog test-suite list --project <projectId>
Une invite pour Claude Code, Codex ou Cursor :
Nous venons de mettre à niveau PHP vers 8.4.27 sur le staging pour PHP2Shell. Utilise Apidog CLI pour exécuter notre suite de tests de non-régression contre l'environnement de staging, générer des rapports HTML et JUnit, et résumer les endpoints en échec. Confirme ensuite qu'un POST avec un corps dépassant notre client_max_body_size nginx est rejeté avec un 413 au niveau du proxy au lieu d'atteindre PHP.Ce que l'agent exécute :
apidog test-suite run <testSuiteId> --project <projectId> -e <stagingEnvId>
apidog run --test-suite <testSuiteId> --project <projectId> -e <stagingEnvId> \
-r cli,html,junit --out-dir ./php2shell-reports --upload-report
-r choisit les reporters, --out-dir définit où les rapports sont déposés, et --upload-report pousse un résumé que vous pouvez ouvrir dans l'application Apidog. Ajoutez --notification-failed-event <targetId> pour alerter votre équipe uniquement quand quelque chose casse. Placez la même ligne apidog run dans la CI pour que chaque image PHP reconstruite soit testée avant d'être livrée. Le guide de configuration de Claude Code et le tutoriel sur le pipeline CI/CD couvrent les deux en détail.
Exécutez la suite une fois sur l'ancienne version pour obtenir une référence, une fois après la mise à niveau, et comparez. Si un test que vous n'attendiez pas à voir échouer échoue, vous le saurez avant vos utilisateurs. Téléchargez Apidog pour créer et gérer les suites que la CLI exécute.
FAQ
PHP2Shell est-il exploité ? Amazon a vu des tentatives dès le 17 octobre et affirme qu'il est désormais largement exploité ; Cloudflare et watchTowr signalent aussi des attaques. La CISA n'avait pas confirmé d'exploitation au 22 octobre.
Mon framework me protège-t-il ? Non. CVE-2026-109618 se déclenche pendant que PHP analyse le corps, avant le chargement de Laravel, Symfony ou WordPress.
Je suis sur de l'hébergement mutualisé. Que faire ? Consultez la page de statut de votre hébergeur. SiteGround et d'autres affirment avoir corrigé. Si le vôtre n'a rien dit, demandez.
session.upload_progress.enabled=0 suffit-il ? Non. Cela couvre un chemin d'un seul CVE. Corrigez.
Dois-je faire tourner mes secrets ? CVE-2026-109619 permet une divulgation de mémoire. Si les logs montrent des signes de compromission depuis le 17 octobre, faites tourner les mots de passe de base de données, les clés d'API et les secrets de session que le processus PHP pouvait lire.
L'essentiel
Mettez à niveau vers 8.5.12, 8.4.27, 8.3.36 ou 8.2.35. Redémarrez chaque worker. Trouvez le PHP que vous avez oublié. Vérifiez les logs jusqu'au 17 octobre. Ensuite, exécutez vos tests d'API pour que le correctif ne devienne pas la prochaine panne. Si votre PHP est en 8.1 ou antérieur, c'est la semaine pour enfin le quitter.
