PHP2Shell : les deux failles critiques de PHP du 21 octobre 2026, expliquées
Deux failles du moteur de PHP (CVE-2026-109618 et CVE-2026-109619, notées 9,2/10) permettent de prendre le contrôle d'un serveur sans compte. Le mécanisme technique, les versions corrigées et ce qu'il faut faire tout de suite.
Le 21 octobre 2026, l’équipe de PHP a publié en urgence quatre nouvelles versions : 8.5.12, 8.4.27, 8.3.36 et 8.2.35. Elles corrigent deux failles du moteur lui-même, surnommées PHP2Shell, notées 9,2 sur 10 (CVSS v4). Elles permettent à un attaquant sans compte d’exécuter du code sur le serveur avec les droits du processus web, parfois avant même que le code du site ne s’exécute.
La faille n’est ni dans WordPress, ni dans une extension : elle est dans PHP. Tout site qui fait tourner PHP est concerné, quel que soit le logiciel qu’il utilise (WordPress, Drupal, Joomla, PrestaShop, code maison).
| CVE-2026-109618 | CVE-2026-109619 | |
|---|---|---|
| Nature | Mémoire corrompue quand PHP manque de mémoire | Mémoire réutilisée après libération, d’une requête à l’autre |
| Déclencheur | Une requête de formulaire (multipart) piégée | Une erreur fatale ou un délai dépassé qui interrompt le nettoyage |
| Prise de contrôle confirmée | Apache avec mod_php, réglages par défaut | mod_php, et PHP-FPM par le suivi des envois de fichiers |
CVE-2026-109618 : la mémoire qui manque au mauvais moment
PHP borne la mémoire de chaque requête (memory_limit, 128 Mo par défaut). Quand une allocation dépasse cette limite, PHP déclenche une erreur fatale et quitte la fonction en cours avec longjmp() : un saut qui abandonne le travail là où il en est, sans le terminer.
Le problème vient de l’ordre des opérations. À certains endroits, PHP déclare une donnée prête avant d’avoir fini de l’allouer. L’analyse de Datadog en donne un exemple : en ajoutant une entrée à une table de hachage, la structure qui contient $_POST, PHP augmente d’abord le compteur d’éléments, puis alloue la clé. Si cette allocation échoue, la table compte une case dont le contenu est ce qui traînait en mémoire.
À la fin de la requête, PHP détruit $_POST et parcourt toutes les cases comptées. Il lit donc ces octets comme des valeurs PHP. Si l’attaquant a pu y placer des octets choisis, le nettoyage exécute son code.
Le manque de mémoire n’est pas le but : c’est le moyen de laisser derrière lui une structure à moitié construite.
Reste à manquer de mémoire sans dépasser les limites de taille de PHP. L’avis officiel décrit comment une requête multipart (un formulaire avec envoi de fichiers) y parvient :
- sans longueur déclarée utilisable, la requête passe le contrôle initial de
post_max_size; - chaque partie peut porter un nombre illimité d’en-têtes, qui coûtent bien plus de mémoire dans PHP que sur le réseau ;
- les champs de fichier vides ne comptent pas dans
max_file_uploads, mais créent quand même des tableaux de métadonnées ; - les noms imbriqués (
a[b][c]…) multiplient ce travail.
Pourquoi mod_php est le plus exposé. Avec Apache et mod_php, PHP lit le corps de la requête dès son démarrage, et exécute quand même le nettoyage de fin si ce démarrage échoue. L’attaque aboutit donc avant le code de l’application, avec les limites par défaut. PHP-FPM, lui, arrête le processus quand le démarrage échoue : aucune attaque complète n’a été démontrée, mais la faille reste atteignable si l’application elle-même provoque un manque de mémoire plus tard.
CVE-2026-109619 : un pointeur oublié d’une requête à l’autre
Un processus PHP (un « worker ») sert de nombreuses requêtes à la suite. Certaines variables globales du processus pointent vers de la mémoire propre à la requête en cours. À la fin de la requête, cette mémoire est libérée ; si la variable n’est pas remise à zéro, la requête suivante lit une zone déjà rendue et peut-être réutilisée. C’est un use-after-free.
Cela arrive quand une erreur fatale ou un délai dépassé interrompt la remise à zéro. Deux chemins sont confirmés :
| Chemin | Réglage concerné | Où l’attaque est confirmée |
|---|---|---|
| Suivi de progression des envois de fichiers | session.upload_progress.enabled, actif par défaut | mod_php et PHP-FPM |
| Traces des erreurs fatales | fatal_error_backtraces (PHP 8.5) | mod_php |
Les requêtes de l’attaquant doivent tomber sur le même worker. Un serveur qui en a beaucoup demande plus d’essais : ce n’est pas une protection.
Ce que change le correctif
- Plus de
longjmp()sur manque de mémoire. Une limite « douce » signale l’erreur au prochain point sûr de l’exécution ; une limite « dure », plus haute, arrête le processus immédiatement, sans nettoyage hasardeux. - L’état qui survit à une requête (traces d’erreur, session, état d’erreur, cache OPcache) est remis à zéro à la fin de chaque requête.
Qui est concerné, et avec quelle version
| Branche | Version corrigée |
|---|---|
| PHP 8.5 | 8.5.12 |
| PHP 8.4 | 8.4.27 |
| PHP 8.3 | 8.3.36 |
| PHP 8.2 | 8.2.35 |
| PHP 8.1, 8.0, 7.4 | pas de nouvelle version : correctifs minimaux publiés en code source, pour les distributions |
Tous les modes d’exécution qui servent des pages sont dans le champ : Apache avec mod_php, PHP-FPM, php-cgi, FrankenPHP, LiteSpeed. Red Hat estime le risque un peu moins élevé, parce que l’attaque demande de provoquer un manque de mémoire très précis. Un exploit fonctionnel a été réalisé pendant les recherches, l’information a fuité un jour avant la publication, et des chercheurs signalent des tentatives d’attaque. Attention aussi aux faux codes d’exploitation publiés sur GitHub : ne les exécutez pas.
Que faire maintenant
-
Mettre à jour vers la version corrigée de votre branche, puis redémarrer tous les processus PHP (Apache, PHP-FPM) et reconstruire les images Docker. C’est la seule protection complète.
-
Vérifier la version réellement utilisée par le site, pas celle de la ligne de commande, qui peut être différente. Sur un hébergement mutualisé, la version se choisit dans le panneau de l’hébergeur : vérifiez qu’elle porte bien le numéro corrigé.
-
Si la mise à jour doit attendre, poser ces réglages dans
php.iniou la configuration du pool, puis redémarrer les processus (ini_set()et.user.iniarrivent trop tard) :session.upload_progress.enabled = 0;fatal_error_backtraces = 0sur PHP 8.5 ;memory_limit = -1, imposé par l’administrateur, avec une limite de taille des requêtes au niveau du serveur web ou du proxy : sans elle, le serveur n’est plus protégé contre l’épuisement de la mémoire ;- bloquer les requêtes multipart au niveau du proxy si le site n’accepte aucun envoi de fichier.
Ces mesures sont partielles : elles ferment les chemins connus, pas la cause.
-
Surveiller : erreurs de mémoire épuisée en tout début de requête (« Allowed memory size exhausted », « Unknown on line 0 »), formulaires aux noms de champs démesurés (plusieurs mégaoctets), plantages de processus PHP, et surtout un processus web qui lance un shell. Une attaque réussie peut répondre normalement (code 200) sans rien faire planter.
Et les sites sans PHP ?
Un site statique, ou une application écrite en Node.js ou en Python, n’est pas touché par ces deux failles : elles sont dans le moteur de PHP. Chaque environnement a les siennes. Ce qui protège vraiment, c’est une version encore suivie par ses développeurs, et des mises à jour appliquées dans les jours qui suivent leur publication.
Sources : avis de sécurité officiel de PHP (GHSA-w8hw-w9fx-j79w), Rapid7, Datadog Security Labs, JFrog et Red Hat, consultés le 22 octobre 2026.