OWASP & AppSec

PHP2Shell (CVE-2026-109618) : exploiter et patcher le moteur PHP

PHP2Shell d'octobre 2026 : argument injection php-cgi, LFI via session.upload_progress, use-after-free CVE-2026-109618. Mécanisme, exploitation, patch et lab.

Naïm Aouaichia
6 min de lecture
  • PHP
  • RCE
  • CVE-2026-109618
  • Use-after-free
  • LFI
  • OWASP

En octobre 2026, une faille baptisée PHP2Shell a rappelé une vérité que l'on préfère oublier : la sécurité d'une application ne vaut jamais plus que celle du moteur qui l'exécute. PHP fait tourner une large part du web, et un défaut dans son cœur touche en même temps des milliers de serveurs qui n'ont pourtant aucun bug dans leur propre code. Cet article décrit la famille PHP2Shell, du détournement d'un paramètre jusqu'à la corruption mémoire, et montre comment chaque technique s'exploite puis se referme.

Une faille d'interpréteur, pas une faille d'application

La plupart des vulnérabilités web vivent dans le code applicatif : un contrôle d'accès oublié, une requête mal paramétrée, une entrée non validée. PHP2Shell est d'une autre nature. La faiblesse est dans l'interpréteur PHP lui-même, celui qui exécute le code de l'application. Conséquence directe : une application au code irréprochable reste vulnérable si elle tourne sur une version ou un mode d'exécution atteint.

La famille se lit sur une pente de difficulté croissante. On commence par une option de ligne de commande détournée, on passe par une fonctionnalité anodine abusée, et on finit par la mémoire du processus. Trois profondeurs, un seul moteur.

1. Injection d'arguments php-cgi (CVE-2012-1823, puis CVE-2024-4577)

Quand PHP est servi en mode CGI, le serveur web transmet la query-string à l'exécutable php-cgi. La règle CGI (RFC 3875) est fatale : une query-string qui ne contient aucun signe égal est traitée comme une requête indexée et découpée en arguments de ligne de commande. Le réseau parle alors directement à l'analyseur d'options de l'interpréteur.

On prouve l'injection en demandant la divulgation du source, puis on passe à l'exécution :

# 1. Dump du source : la query atteint les arguments de php-cgi
curl 'http://cible/index.php?-s'
 
# 2. Exécution : on force l'inclusion du corps de requête comme code PHP
curl 'http://cible/index.php?-d+allow_url_include%3don+-d+auto_prepend_file%3dphp://input' \
     --data '<?php system("id; cat /opt/flag.txt"); ?>'

Le %3d encode le signe égal interne aux directives : s'il apparaissait nu dans la query-string, celle-ci ne serait plus traitée comme indexée et l'exploit échouerait en silence.

CVE-2012-1823 a été remontée en 2012 par l'équipe Eindbazen. Douze ans plus tard, en juin 2024, Orange Tsai de DEVCORE publie CVE-2024-4577 : la même classe d'injection, cette fois sur php-cgi sous Windows, par un contournement du correctif via une conversion de jeu de caractères Best-Fit. Exploitation de masse en quelques jours, rançongiciel TellYouThePass en tête. La parade racine n'a pas changé : on ne sert jamais php-cgi en CGI exposé, on passe par php-fpm, et on tient l'interpréteur à jour.

2. Inclusion de fichier local via session.upload_progress (CWE-98)

L'étape intermédiaire ne touche pas la mémoire : elle chaîne deux faiblesses faibles pour atteindre l'exécution, sans jamais téléverser de webshell. Une visionneuse permissive inclut un fichier par son nom sans validation (inclusion de fichier local), et le suivi de téléversement de PHP écrit dans le fichier de session une valeur que l'on contrôle.

Le geste tient en deux temps. On empoisonne d'abord le fichier de session en nommant un champ PHP_SESSION_UPLOAD_PROGRESS, dont la valeur, ici le nom du fichier téléversé, est écrite telle quelle côté serveur. On inclut ensuite ce fichier de session par la LFI, avec une traversée de chemin, et on exécute :

curl -s "http://cible/viewer.php?doc=../../../../var/lib/php/sessions/sess_SID&0=cat%20/opt/flag.txt"

La cause racine n'est pas session.upload_progress, c'est l'inclusion pilotée par le client. On ne laisse jamais l'utilisateur désigner le fichier que le serveur exécute : on résout la cible de l'inclusion depuis une liste blanche côté serveur. Le client choisit une clé, pas un chemin. Interdire les ../ ou désactiver allow_url_include ne suffit pas : le fichier de session est un chemin local parfaitement légitime.

3. Use-after-free cross-requête, le vrai PHP2Shell (CVE-2026-109618)

Le climax est une corruption mémoire du cœur PHP (CVE-2026-109618 et CVE-2026-109619), un use-after-free sur PHP 8.4.26, corrigé en 8.4.27. Un pointeur d'une requête est réutilisé par une requête ultérieure sur le même worker : la zone a été libérée, mais elle est relue après avoir été réallouée avec des octets choisis par l'attaquant.

L'exploitation suit trois temps. Un infoleak d'abord : une requête malformée laisse fuiter une adresse de tas lisible via le cookie de session, ce qui vainc l'ASLR. Un façonnage du tas ensuite, pour que la zone libérée soit reprise par une structure forgée. Un détournement de flot enfin : on reconstruit une fausse structure du moteur dont un pointeur de fonction, appelé au nettoyage de session, vise system. La sortie de la commande est écrite dans la racine web inscriptible, puis récupérée par une requête GET, car la sortie réseau directe est coupée.

La leçon de défense est sèche : contre un use-after-free, les mitigations ralentissent mais ne corrigent pas. Une seule fuite d'adresse les annule. Le vrai correctif est mémoire, livré par la mise à jour de l'interpréteur.

Se protéger, en synthèse

  • Servir PHP par php-fpm, jamais php-cgi exposé en CGI.
  • Ne jamais laisser le client désigner un fichier inclus : liste blanche côté serveur, le client choisit une clé.
  • Tenir l'interpréteur PHP à jour, 8.4.27 ou supérieur pour la famille d'octobre 2026.
  • Désactiver session.upload_progress si la fonctionnalité n'est pas utilisée, garder le nettoyage actif.
  • Se souvenir qu'un pare-feu applicatif filtre des motifs connus, il ne supprime pas la primitive sous-jacente.

Pour chaque vulnérabilité, la même discipline revient : on supprime la cause, on ne tamise pas le symptôme. C'est exactement le raisonnement qu'un auditeur applicatif doit tenir face à un composant partagé.

S'entraîner sur le lab PHP → shell

Lire une exploitation ne remplace pas de l'avoir faite. Le parcours gratuit PHP → shell de Zeroday Cyber Academy déploie trois cibles réelles à la demande, une par technique de cette page : l'injection d'arguments php-cgi en entrée, la LFI via session.upload_progress en intermédiaire, puis l'use-after-free d'octobre 2026 en capstone, solveur de référence fourni. Chaque cible porte une fiche qui détaille le pourquoi du comment, l'exploitation pas à pas et la remédiation avant/après. On apprend à exploiter pour apprendre à défendre : l'accès se demande par email sur la page du lab.

Questions fréquentes

  • Qu'est-ce que PHP2Shell exactement ?
    PHP2Shell désigne la famille de techniques qui transforment une faille du moteur PHP, et non de l'application, en exécution de commande sur le serveur. Le nom a été popularisé en octobre 2026 avec CVE-2026-109618 et CVE-2026-109619, un use-after-free dans le cœur de l'interpréteur PHP 8.4.26 (corrigé en 8.4.27). La lignée est plus ancienne : l'injection d'arguments php-cgi de 2012 (CVE-2012-1823) et son retour de 2024 (CVE-2024-4577) relèvent du même principe, une faiblesse de l'interpréteur lui-même. L'enjeu est systémique : PHP fait tourner une large part du web, donc une faille d'interpréteur touche en même temps des milliers de serveurs qui n'ont pourtant aucun bug dans leur code applicatif.
  • CVE-2012-1823 et CVE-2024-4577, est-ce la même faille ?
    C'est la même classe exactement, l'injection d'arguments sur php-cgi (CWE-78), à douze ans d'écart. CVE-2012-1823 vient de la règle CGI (RFC 3875) qui transmet une query-string sans signe égal comme arguments de ligne de commande à l'exécutable, ici l'interpréteur PHP. CVE-2024-4577, trouvée par Orange Tsai de DEVCORE et publiée en juin 2024, est un contournement du correctif de 2012 sous Windows : une conversion de jeu de caractères Best-Fit retransforme un tiret logiciel en vrai tiret et rouvre le passage des options. Elle a été exploitée en masse en quelques jours, notamment par le rançongiciel TellYouThePass. La leçon : un correctif de surface sur une faille d'interpréteur laisse souvent une variante exploitable.
  • Comment lit-on le flag si le reverse-shell est bloqué ?
    Sur un lab durci, la sortie réseau est coupée (egress none) : un reverse-shell sortant échoue. On lit alors le fichier cible en exécutant une commande sur l'instance, pas en HTTP direct. Pour l'injection d'arguments, on envoie du code PHP dans le corps de la requête via auto_prepend_file=php://input et on exécute cat sur le fichier. Pour l'use-after-free, on écrit la sortie de la commande dans un dossier de la racine web inscriptible, puis on la récupère par une simple requête GET. Comprendre cette contrainte de confinement fait partie de l'exercice : l'exécution de code ne vaut que si on sait exfiltrer la preuve.
  • Quelle est la remédiation racine contre l'injection d'arguments php-cgi ?
    Ne jamais exposer php-cgi en mode CGI derrière le serveur web. On sert PHP par php-fpm via un socket FastCGI, qui reçoit des variables structurées et non une ligne de commande : il n'existe plus d'analyseur d'options à atteindre depuis la query-string. S'y ajoutent la mise à jour de l'interpréteur, et le principe qu'un composant obsolète est une dette de sécurité même quand le code applicatif est sain. Filtrer des motifs comme l'option -s dans l'URL ne corrige rien : tant que php-cgi est joignable, la primitive existe. On supprime la primitive, on ne la tamise pas.
  • Pourquoi l'ASLR ne protège-t-il pas contre le use-after-free PHP2Shell ?
    ASLR, NX et pile canari sont des mitigations : elles renchérissent l'exploitation, elles ne corrigent pas un use-after-free (CWE-416). Dès qu'une seule adresse fuite, l'ASLR est vaincu. Dans CVE-2026-109618, une requête malformée provoque une fuite de tas lisible via le cookie de session, qui donne les bases d'adresses nécessaires pour forger une fausse structure du moteur dont un pointeur de fonction vise system. Le seul vrai correctif est le patch mémoire, livré dans PHP 8.4.27 : invalider le pointeur à la libération et durcir l'allocateur. Compter sur les mitigations contre un bug mémoire, c'est fermer la porte en laissant la clé sous le paillasson.
  • Comment s'entraîner concrètement sur PHP2Shell sans rien casser ?
    Sur un environnement conçu pour, pas sur un service en production. Zeroday Cyber Academy propose un parcours gratuit « PHP → shell » avec trois cibles réelles déployées à la demande, une par technique de la famille : l'injection d'arguments php-cgi en entrée, la LFI via session.upload_progress en intermédiaire, puis l'use-after-free d'octobre 2026 en capstone, avec un solveur de référence. Chaque cible s'accompagne d'une fiche qui explique le pourquoi du comment, l'exploitation et la remédiation. L'accès se demande par email sur la page du lab.

Trouver ces failles dans votre code, puis les refermer.

Nous reprenons les catégories OWASP sur des applications qui tournent : on exploite, on mesure ce que ça donne, puis on corrige et on vérifie que le correctif tient.

Écrit par

Naïm Aouaichia

Fondateur et directeur pédagogique de Zeroday Cyber Academy

Ingénieur cybersécurité avec un parcours hybride : développement, DevOps en ESN française, DevSecOps IN Groupe (sécurité des documents d'identité régaliens), audits CAC 40. Fondateur de Hash24Security et Zeroday Cyber Academy.