PHP2Shell : ce qu’il faut savoir dès maintenant et comment se préparer à la suite En savoir plus

CrowdStrike protège ses clients contre les cybermenaces transmises par PHP2Shell

Deux vulnérabilités critiques de corruption de mémoire, permettant l’exécution de code à distance (RCE) et répertoriées sous les identifiants CVE-2026-109618 et CVE-2026-109619, ont été révélées dans le cœur de PHP.

21 octobre 2026  •  Amit Serper  •  From The Front Lines

Deux vulnérabilités critiques permettant l’exécution de code à distance (RCE) par corruption de mémoire, collectivement dénommées PHP2Shell, ont été révélées dans le cœur de PHP, le moteur sous-jacent qui fait fonctionner une grande partie du Web. La plateforme CrowdStrike Falcon® offre d’emblée une visibilité sur les activités post-exploitation liées aux deux vulnérabilités, ainsi qu’une couverture de détection de ces activités.

Répertoriées sous les identifiants CVE-2026-109618 et CVE-2026-109619, ces deux vulnérabilités présentent un score CVSS 4.0 de 9,2 (critique) et sont classées sous la catégorie CWE-824 : Accès à un pointeur non initialisé. Elles permettent toutes deux à un cyberattaquant distant non authentifié d’exécuter des commandes système arbitraires sur un serveur web vulnérable via le protocole HTTP.

Ces deux problèmes ont une cause première unique. Lorsque PHP rencontre une situation critique (épuisement de la limite de mémoire memory_limit configurée, dépassement du temps d’exécution maximal ou perte de la connexion client), il ne propage pas d’erreur via la pile d’appels. Il déclenche une erreur fatale et utilise longjmp() pour abandonner brutalement le contexte d’exécution en cours, interrompant ainsi la requête. Toute fonction en langage C, surprise en pleine construction d’une structure de données, est purement et simplement abandonnée : aucun destructeur n’est exécuté, aucune opération de suivi n’est annulée, et rien n’indique que la structure est incomplète. PHP enchaîne ensuite sur une procédure de nettoyage, par ailleurs classique, qui suppose que toutes les structures se trouvent dans un état cohérent.

Les deux CVE décrivent les situations où cette hypothèse s’avère erronée :

  • CVE-2026-109618 (RCE par épuisement de mémoire) concerne des situations où la mémoire allouée à une requête demeure non initialisée. Une structure de données est marquée comme contenant une entrée avant même que la mémoire destinée à cette entrée ne soit allouée ; lorsque cette allocation entraîne le dépassement de la limite de mémoire, l’entrée conserve des données résiduelles présentes dans le tas. La routine de nettoyage propre à la requête défaillante lit alors ces octets et effectue des décisions conditionnelles en fonction de leur contenu.
  • CVE-2026-109619 (exécution de code à distance [RCE] via des erreurs fatales) concerne les contextes qui laissent l’état global dans un état invalide. Une variable globale au niveau du module pointe toujours vers le tas propre à la requête, déjà libéré à l’arrêt, et indique toujours que l’opération est active. Une requête ultérieure traitée par le même processus worker lit cette mémoire libérée ou effectue des décisions conditionnelles en fonction de son contenu.

Dans les deux cas, le cyberattaquant contrôle le déclencheur et, surtout, les données que PHP finit par mal interpréter. CrowdStrike a vérifié de bout en bout qu’une seule requête POST anonyme peut amener un worker Apache mod_php à lancer un processus shell et à exécuter une commande définie par un cyberattaquant, sans authentification, sans session et sans code applicatif vulnérable.

Ce qui rend ces vulnérabilités particulièrement critiques, c’est leur position au sein de la pile d’exécution. PHP analyse les données des requêtes entrantes avant même que les extensions ne soient initialisées et qu’une seule ligne de code applicatif ne soit compilée ; l’exploitation est donc indépendante du contenu hébergé par le serveur. Une application minutieusement entretenue est exposée exactement au même degré qu’une application négligée. L’attaque est par ailleurs remarquablement discrète : le processus de traitement ne plante pas, le serveur renvoie une réponse HTTP 200 standard et les logs de l’application n’enregistrent rien d’anormal.

Lors de nos tests, nous avons développé un code d’exploit qui fonctionnait de manière constante sur les architectures x86-64 et ARM, restait fiable malgré les redémarrages des workers et, sur les hôtes basés sur ARM – de plus en plus répandus dans les parcs cloud – contournait la randomisation de l’agencement de l’espace d’adressage (ASLR) en utilisant uniquement des requêtes HTTP.

Détails techniques

La cause première commune : longjmp() sans déroulage de la pile

Les deux vulnérabilités se situent au sein de Zend, le moteur qui constitue le cœur de PHP. Zend est l’implémentation en langage C qui compile le code source PHP en bytecode, l’exécute dans une machine virtuelle et gère tous les éléments nécessaires au langage lors de son exécution : variables, tableaux, objets, comptage de références et allocation mémoire. Conçu à l’origine par Zeev Suraski et Andi Gutmans (le nom est d’ailleurs une contraction de leurs prénoms) et introduit avec PHP 4, il ne s’agit ni d’un composant facultatif ni d’un ajout tiers ; il est intégré à chaque version de PHP, raison pour laquelle son code source se trouve dans le répertoire Zend/ du répertoire php-src. Concrètement, cela signifie qu’une faille dans Zend constitue une faille dans le moteur PHP lui-même, affectant ainsi toutes les applications, frameworks et extensions qui s’exécutent sur cet interpréteur, quelle que soit la manière dont ce code a été écrit.

Deux composants essentiels du moteur Zend sont au cœur des problèmes décrits ici : la HashTable, qui sous-tend chaque tableau PHP, y compris la superglobale $_POST, et le Zend Memory Manager (Zend MM), le propre allocateur par requête de PHP qui s’intercale entre PHP et le système d’exploitation. Le chemin d’erreur fatale de PHP aboutit à zend_bailout(), qui effectue un LONGJMP pour sortir de la pile d’exécution C actuelle :

// Zend/zend.c
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno)
{
...
CG(unclean_shutdown) = 1;
...
LONGJMP(*EG(bailout), FAILURE);
}

Aucun code situé entre l’erreur fatale et le bloc zend_catch englobant ne s’exécute. Tout appelant qui a modifié l’état et comptait atteindre son propre code de nettoyage laisse cet état modifié en place. Cela ne présente aucun risque que si chaque appelant de ce type est conçu en tenant compte d’une interruption brutale, ce qui n’est pas le cas pour certains d’entre eux.

CVE-2026-109618 : Mémoire de requête non initialisée, utilisée au cours de la même requête

L’instance de référence est _zend_hash_str_add_or_update_i() dans Zend/zend_hash.c, la fonction auxiliaire utilisée lors de chaque insertion d’un élément indexé par une chaîne dans un tableau PHP. Lors du processus d’insertion, elle incrémente les deux compteurs d’éléments, récupère un pointeur vers le nouvel emplacement (bucket), et ne procède qu’ensuite à l’allocation d’une copie de la clé :

// Zend/zend_hash.c
add_to_hash:
   idx = ht->nNumUsed++;
   ht->nNumOfElements++;
   p = ht->arData + idx;
   p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT);

Si zend_string_init() dépasse memory_limit, Zend MM déclenche une erreur fatale qui aboutit à un appel à zend_bailout(). L’exécution ne reprend jamais, empêchant ainsi l’achèvement du bucket. Par conséquent, la table indique une entrée de plus qu’elle n’en contient réellement ; la valeur, le hash et le pointeur de clé de cette entrée correspondent alors aux octets qui se trouvaient précédemment à cet emplacement mémoire, car le tableau du bucket est alloué via emalloc() sans initialisation, seule la zone dédiée au hash étant explicitement réinitialisée.

Lors de la phase de nettoyage, php_request_shutdown() détruit les superglobales, et zend_array_destroy() parcourt les buckets nNumUsed. L’entrée obsolète est transmise à i_zval_ptr_dtor(), qui traite les octets résiduels comme une valeur gérée par comptage de références : elle déréférence le pointeur qu’ils contiennent, décrémente le compteur de références à cette adresse et, si celui-ci atteint zéro, effectue un appel via une table de pointeurs de fonction, indexée par une balise de type également extraite des octets résiduels.

Il suffit d’un corps POST encodé au format URL pour déclencher cette condition, car PHP remplit $_POST dans php_hash_environment(), que php_request_startup() appelle avant zend_activate_modules() et avant la compilation de tout script. Un champ dont le nom est imbriqué, comme ZZZZ…[x]=1 entraîne l’insertion du nom externe comme nouvelle clé de tableau par php_register_variable_ex(), de sorte que le cyberattaquant contrôle directement la longueur de la clé à allouer. En dimensionnant ce nom de façon à dépasser memory_limit, le bailout se produit précisément lors de l’allocation de la clé.

Le contrôle des octets résiduels tient au déterminisme de l’allocateur. Zend MM fournit les petites allocations à partir de listes libres distinctes pour chaque taille, selon un ordre strict LIFO (« dernier entré, premier sorti »), et ne remet jamais à zéro les blocs récupérés. Un champ situé plus tôt dans la même requête, dont la valeur est copiée, décodée, puis immédiatement libérée par PHP, peut donc être dimensionné de telle sorte que son contenu, une fois libéré, occupe précisément le bloc qui sera ultérieurement alloué au tableau corrompu. L’entrée inactive devient alors une valeur entièrement définie par un cyberattaquant. Nous l’avons exploitée pour créer un tableau factice dont le pointeur de destructeur pointe vers la fonction system() de libc (la bibliothèque standard C), la chaîne de commande étant elle-même située dans ce même bloc.

Le seul artefact côté serveur associé à une tentative réussie est une erreur fatale générique dans le log du serveur web :

PHP Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 3670048 bytes) in Unknown on line 0

L’indication in Unknown on line 0 est révélatrice : elle confirme qu’aucun fichier PHP n’était en cours d’exécution.

CVE-2026-109619 : État global invalide, utilisé par une requête ultérieure

La deuxième CVE repose sur le même mécanisme, mais les dégâts touchent un état propre au processus, ce qui décale la fenêtre d’exploitation à une requête ultérieure. Ici, l’opération interrompue met son état en cache dans une variable globale et ne peut l’effacer que si son propre appel de nettoyage est exécuté. PHP met en cache les contextes actifs de sérialisation et de désérialisation dans des variables globales de module, puis réutilise le pointeur mis en cache dès que le niveau d’imbrication est supérieur à zéro. Seul l’appel de destruction correspondant décrémente ce niveau et efface le pointeur, et seule la routine d’initialisation des requêtes d’une extension le réinitialise. La gestion des sessions fonctionne de manière similaire : elle conserve l’identifiant de session, l’état du gestionnaire, les variables de session et l’état de progression des chargements dans des variables globales, que des routines d’arrêt dédiées effacent.

Lorsqu’une erreur fatale interrompt l’une de ces opérations, l’étape de nettoyage correspondante est ignorée. La variable globale conserve son pointeur, et le compteur d’imbrication continue d’indiquer que l’opération est en cours. La procédure d’arrêt de la requête libère alors l’intégralité du tas associé à cette requête, mais la variable globale subsiste, car elle appartient au processus et non à la requête. PHP se retrouve ainsi avec un pointeur, au niveau du module, qui référence une zone mémoire déjà libérée.

Deux facteurs rendent cela accessible et contrôlable :

  • Réutilisation du worker : dans les déploiements à workers persistants, comme Apache mod_php, le même processus traite la requête suivante. La variable globale obsolète est retrouvée intacte, l’opération est considérée comme étant en cours, et le pointeur libéré est lu, utilisé pour écrire à son adresse ou utilisé dans un branchement. Le contenu occupant cet emplacement mémoire à ce stade est sous le contrôle du cyberattaquant, car la requête intermédiaire est également la sienne : il en choisit la taille et le contenu, et le mécanisme d’allocation de Zend MM est suffisamment déterministe pour permettre un placement prévisible des données. PHP-FPM met fin au worker dont le démarrage de la requête échoue, ce qui empêche sa réutilisation en cas d’interruption pendant la phase de démarrage ; en revanche, une interruption survenant pendant l’exécution d’un script laisse le processus FPM actif et tout autant exposé.
  • Ordre de démarrage : php_request_startup() analyse les variables de la requête avant d’exécuter les fonctions de rappel d’initialisation de requête des extensions. Le traitement du suivi de progression des chargements multipart exécute donc le code de gestion des sessions et de sérialisation avant que les extensions responsables de ces variables globales aient eu la moindre possibilité de les réinitialiser, ce qui permet d’exploiter un pointeur obsolète dès la toute première phase d’une requête.

Au-delà de l’épuisement de la mémoire, cet état peut également être atteint à la suite de délais d’attente d’exécution ou de connexions client interrompues. Ces deux situations empruntent le même chemin zend_bailout(). L’exploitation se déroule donc en deux étapes : une requête empoisonne le worker, puis une seconde déclenche l’utilisation de la mémoire libérée.

Impact de la vulnérabilité

La gravité des deux problèmes tient à leur emplacement au début du traitement des requêtes. Comme la corruption se produit alors que PHP assemble encore les variables internes de la requête, avant l’initialisation des extensions et avant l’exécution du moindre code applicatif, chaque endpoint PHP accessible sur un serveur affecté constitue une surface d’attaque active, y compris les formulaires statiques, les routes de connexion et les contrôles d’intégrité. Il n’y a aucune authentification à contourner, aucune session à dérober et aucun endpoint vulnérable à découvrir. L’exposition est indépendante du CMS, du framework et des plug-ins utilisés.

L’attaque ne laisse également pratiquement aucune empreinte numérique. Les processus workers restent stables, les réponses renvoient un code HTTP 200 et les logs applicatifs n’enregistrent aucune anomalie, car aucun code applicatif n’a jamais été exécuté. La seule trace côté serveur est une unique erreur fatale liée au dépassement de la limite de mémoire dans le log d’erreurs du serveur web, généralement considérée comme du bruit de fond, privant les équipes de sécurité de données de télémétrie web exploitables pour identifier les endpoints compromis. 

Dans les serveurs web multiprocessus tels qu’Apache en mode prefork, chaque worker est issu d’un seul processus parent. La disposition en mémoire sur laquelle s’appuie un cyberattaquant demeure constante d’un redémarrage de worker à l’autre. Cette stabilité permet à un cyberattaquant d’affiner ses charges actives sans aucun coût ; une tentative échouée est absorbée par la recréation d’un worker, une opération que les mécanismes de surveillance considèrent comme une procédure courante.

Les deux CVE diffèrent par leur portée et leur temporalité, deux aspects qui ont une incidence sur le plan opérationnel. La CVE-2026-109618 est autonome : une requête unique corrompt ses propres données, et les conséquences se manifestent lors de la phase de nettoyage de cette même requête ; le processus est donc rapide, atomique et difficile à reconstituer a posteriori. La CVE-2026-109619 s’étend sur plusieurs requêtes : elle nécessite une étape d’empoisonnement suivie d’une étape de déclenchement, ciblant le même worker. Elle affecte toutefois un périmètre plus large de l’état interne du système et peut être déclenchée par des dépassements de délai, des déconnexions du client ou encore un épuisement de la mémoire. Les déploiements avec des workers persistants constituent la principale vulnérabilité face à cette seconde catégorie.

L’exploitation réussie de l’une ou l’autre de ces vulnérabilités permet d’exécuter des commandes dans le contexte de l’utilisateur du serveur web, point d’appui classique pour déployer des webshells, récupérer des identifiants de base de données et d’API dans les fichiers de configuration, puis rebondir vers les réseaux internes. L’application de correctifs est la seule correction complète. Lorsque l’application immédiate de correctifs n’est pas envisageable, la surveillance comportementale des endpoints constitue la principale ligne de défense, car l’exploitation est invisible au niveau HTTP, mais pas ses conséquences.

Suis-je concerné ?

Les deux vulnérabilités sont présentes dans toutes les versions de PHP des séries 7.x et 8.x. L’ordre spécifique de la HashTable à l’origine de CVE-2026-109618 a été introduit par une optimisation des performances en octobre 2017 et intégré pour la première fois à PHP 7.3.0 (décembre 2018). Les mécanismes de sérialisation et de gestion des sessions à l’origine de CVE-2026-109619 remontent à plus loin encore : respectivement à PHP 7.1 et à PHP 5.4. Autrement dit, cette fenêtre d’exploitation existe dans les versions publiées de PHP depuis plus de sept ans. Aucune branche de PHP encore officiellement prise en charge n’est épargnée.

La version seule ne détermine pas l’exposition. Le déclencheur de mémoire insuffisante doit être accessible pendant que PHP analyse les données de la requête, ce qui nécessite qu’un corps de requête puisse croître suffisamment pour épuiser la limite de mémoire avant l’exécution d’un script. En pratique, cela signifie que la configuration satisfait à peu près :

 memory_limit < 4 × post_max_size

Les valeurs par défaut fournies avec PHP (post_max_size = 8M, memory_limit = 128M) ne remplissent pas cette condition. Une installation standard n’est donc pas accessible via le chemin encodé dans l’URL que nous avons décrit. Cependant, de nombreuses installations en production sont configurées pour accepter des chargements volumineux, et des combinaisons courantes telles que post_max_size = 64M avec memory_limit = 128M, ou post_max_size = 100M avec memory_limit = 256M, satisfont cette condition. À noter que post_max_size peut être défini par répertoire dans .htaccess ou pour un hôte virtuel, de sorte que, dans un hébergement mutualisé, un seul site peut adopter la configuration vulnérable sans qu’aucune modification ne soit apportée à l’échelle du serveur. CrowdStrike a reproduit le problème en abaissant memory_limit à 16M et en portant post_max_size à 64M, uniquement pour que la charge active de test reste petite ; le même bug peut être déclenché avec des charges actives plus volumineuses lorsque les limites ci-dessus sont plus élevées.

Pour voir quelle version de PHP et quelle configuration sont utilisées sur votre serveur, exécutez php --version. Pour voir les valeurs définies sur votre serveur pour les options répertoriées ci-dessus, exécutez la commande suivante :
php -i | grep -E "^(memory_limit|post_max_size|upload_max_filesize|Loaded Configuration File)".

Comment CrowdStrike Falcon protège ses clients

CrowdStrike adopte une approche à plusieurs niveaux pour détecter l’exploitation et les activités post-exploitation, en s’appuyant sur le machine learning et des détections basées sur le comportement, notamment des indicateurs d’attaque (IOA). La plateforme Falcon offre une visibilité prête à l’emploi et une couverture de détection des activités post-exploitation liées aux vulnérabilités PHP. Les IOA existants détectent et empêchent les activités post-exploitation courantes, notamment le déploiement de webshells, le lancement de processus enfants suspects par des processus PHP ou de serveur web, ainsi que l’exécution de commandes malveillantes.

CrowdStrike poursuit ses recherches sur les techniques d’exploitation associées à cette vulnérabilité.

Figure 1. Chaîne d’exploitation des vulnérabilités PHP

Identifier les versions vulnérables de PHP avec CrowdStrike Falcon® Exposure Management

CrowdStrike Falcon® Exposure Management offre une visibilité complète sur les environnements concernés. Les fonctionnalités Network Scan (Netscan) et de gestion de la surface d’attaque externe (EASM) permettent une détection rapide basée sur les bannières des assets exposés à Internet. En outre, la visibilité basée sur les capteurs permet d’évaluer automatiquement les correctifs en aval fournis par les fournisseurs sur les distributions Linux prises en charge et les systèmes d’exploitation Windows et macOS.

Falcon® Exposure Management détecte ces CVE sur toutes les versions prises en charge du capteur via son gestionnaire de packages existant et des vérifications ciblées des applications. Ces vérifications ne requièrent aucune version minimale du capteur.

CrowdStrike a également ajouté les détections suivantes reposant sur le capteur. Elles détectent les installations de PHP que les contrôles existants pourraient ne pas repérer, comme les installations personnalisées ou celles effectuées en dehors d’un gestionnaire de packages. Elles sont disponibles uniquement sur les versions du capteur répertoriées :

  • Windows (agent Falcon 7.40 ou version ultérieure) : vérification à l’échelle du système des exécutables PHP sur le lecteur par défaut. Cette vérification révèle d’autres installations de PHP que les contrôles existants pourraient ne pas repérer, notamment les installations personnalisées.
  • Linux (agent Falcon 7.31 ou version ultérieure) : vérification des processus en cours pour identifier les installations PHP effectuées en dehors du gestionnaire de packages attendu.

Les clients qui utilisent des versions antérieures du capteur restent couverts par le gestionnaire de packages et les contrôles ciblés des applications. Ils ne bénéficieront des contrôles supplémentaires à l’échelle du système et des processus en cours d’exécution qu’après la mise à niveau.

Figure 2. Identification des versions vulnérables de PHP dans Falcon® Exposure Management

Identification des cybermenaces avec Next-Gen SIEM

Falcon® Next-Gen SIEM : Tableau de bord de réponse aux cybermenaces émergentes

La couverture de détection de CrowdStrike Falcon® Next-Gen SIEM pour cette cybermenace (nouvelle section « Vulnérabilités d’exécution de code à distance PHP (CVE-2026-109618/109619) » sur le tableau de bord CrowdStrike Falcon - Réponse aux cybermenaces émergentes) a été déployée en production dans le cadre de la publication des packages de contenu NG-SIEM.

Le tableau de bord Réponse aux cybermenaces émergentes est un outil spécialisé conçu pour offrir aux clients de CrowdStrike une visibilité immédiate sur les campagnes actives ou les vulnérabilités récemment découvertes susceptibles d’affecter leur environnement. En s’appuyant sur la riche télémétrie des modules de la plateforme Falcon et sur les données tierces ingérées dans Falcon® Next-Gen SIEM, ce tableau de bord fournit des requêtes de détection et de Threat Hunting prêtes à l’emploi, qui permettent aux équipes de sécurité d’évaluer rapidement leur exposition aux cybermenaces et d’identifier les assets potentiellement compromis.

  • Voir Next-Gen SIEM > Gestion des logs > Tableaux de bord

Figure 3. Identification du tableau de bord de réponse aux cybermenaces émergentes dans Falcon® Next-Gen SIEM

Modèles de règles de corrélation pour CrowdStrike Falcon® Next-Gen SIEM

Nous avons ajouté un nouveau modèle de règles de corrélation (CRT) Falcon® Next-Gen SIEM pour détecter les CVE-2026-109618 et CVE-2026-109619 et y répondre. Ces règles offrent une couverture contre les tentatives d’exploitation liées à ces vulnérabilités. 

Générique - Web - Épuisement de la mémoire PHP lors du démarrage d’une requête (CVE-2026-109618 et CVE-2026-109619)

Cette détection identifie les erreurs fatales d’épuisement de mémoire PHP qui surviennent avant le début de l’exécution d’un script PHP. Plus précisément, elle cible un schéma révélateur qui apparaît dans les logs d’erreur des serveurs web sous la forme “Allowed memory size of N bytes exhausted” associé à “in Unknown on line 0.” Le marqueur "Unknown on line 0” permet de distinguer les défaillances survenant pendant la phase de démarrage des nombreuses erreurs mémoire qui se produisent dans le script. Il fournit aux équipes de sécurité un signal plus fiable indiquant qu’un élément force l’interpréteur PHP à épuiser sa mémoire avant l’exécution de tout code applicatif. Des schémas de ce type peuvent indiquer une tentative d’exploitation du moteur PHP. Les clients peuvent consulter le modèle ici ([US-1]  [US-2]  [US-3]  [EU-1]  [US-GOV-1]  [US-GOV-2]).

Conclusions et recommandations

CrowdStrike surveille activement l’évolution de la situation et communiquera des mises à jour à mesure que de nouvelles informations seront disponibles. En parallèle, nous continuons à développer et à déployer de nouvelles règles comportementales dans la plateforme Falcon afin de détecter et d’empêcher les activités malveillantes liées aux vulnérabilités PHP divulguées CVE-2026-109618 et CVE-2026-109619.

Nous recommandons aux clients d’identifier tous les systèmes exécutant des versions vulnérables de PHP et d’appliquer les dernières mises à jour de sécurité conformément aux recommandations officielles de PHP ou du fournisseur.