Mon composer.lock connaît le hash de commit exact d'un petit utilitaire de chaînes, à peine plus gros que left-pad, que j'ai ajouté il y a trois ans. Il n'a en revanche aucune idée de la build de PHP qui va l'exécuter. Le 21 octobre, ce trou a cessé d'être une simple bizarrerie philosophique : le projet PHP a publié 8.5.12, 8.4.27, 8.3.36 et 8.2.35 pour corriger CVE-2026-109618 et CVE-2026-109619, toutes deux notées 9.2 en CVSS v4.0, toutes deux exploitables sans authentification. Pour nous qui écrivons les applications, la leçon me paraît sans appel. L'interpréteur est une dépendance, et on devrait le versionner, le livrer et revenir en arrière dessus avec la même rigueur que celle qu'on consacre à notre dossier vendor.

Regarde qui était exposé. L'avis cite PHP-FPM, Apache avec mod_php, php-cgi, FrankenPHP et LiteSpeed, et désigne mod_php comme le pire cas, parce qu'une requête malveillante atteint le code vulnérable avant même que ton contrôleur frontal soit chargé. Ton middleware Laravel ne s'exécute jamais. Ton pare-feu Symfony non plus. Le status.php de 40 lignes qu'un fournisseur de monitoring t'a demandé de poser à la racine web en 2019 fait un point d'entrée aussi valable que ton tunnel de paiement. Je ne vais pas détailler les entrailles ici, les avis publiés sur GitHub le font mieux que moi, mais pour faire court, un des bugs se loge dans la façon dont le moteur abandonne quand la mémoire s'épuise en pleine requête, et l'autre dans des restes qu'un worker recyclé aurait dû effacer entre deux visiteurs.

Ce deuxième bug pique un peu, parce qu'il touche à ce qu'on répète à chaque nouveau venu sur PHP : chaque requête repart de zéro. Cette promesse a toujours concerné nos variables côté userland, et à ce niveau elle tient toujours. En dessous, un processus enfant FPM peut servir dix mille requêtes avant que pm.max_requests ne le mette à la retraite, et le moteur est la seule chose qui sépare le visiteur 4812 de ce qu'a laissé derrière lui le visiteur 4811. On règle ce paramètre pour contenir les fuites mémoire d'une quelconque bibliothèque d'images. Rares sont ceux d'entre nous qui y ont jamais vu un réglage de sécurité, et franchement, ce n'en est toujours pas un : seul le binaire corrigé en est un.

Passons à l'argument inverse, et il est solide. La plupart d'entre nous font tourner PHP depuis Debian, Ubuntu, RHEL ou le panneau de contrôle d'un hébergeur justement pour que quelqu'un d'autre s'occupe de ça. Les équipes sécurité des distributions rétroportent les correctifs dans des paquets dont le numéro de version semble figé dans le temps, et c'est la seule raison pour laquelle les machines en PHP 8.1 ou plus ancien ont encore une porte de sortie, maintenant que l'amont a cessé de les corriger. Une équipe plateforme qui gère le runtime sur tout le parc peut patcher deux cents services en un après-midi, là où deux cents équipes applicatives qui mettent chacune à jour leur Dockerfile prendraient deux semaines et un fil Slack que personne ne lit. Centraliser le runtime est un vrai gain de sécurité, et je ne veux pas balayer ça d'un revers de main.

Là où je reste malgré tout de l'autre côté, c'est sur le redémarrage. Mettre à jour un paquet sur le disque ne change rien pour un master FPM qui tourne depuis août : l'ancien code reste mappé en mémoire jusqu'à ce que quelqu'un recharge le pool ou relance Apache. Dans le modèle centralisé, cette étape a tendance à tomber dans l'interstice entre « les ops ont installé la mise à jour » et « les devs sont responsables du déploiement », et le tableau de bord affiche fièrement la nouvelle version du paquet pendant que les processus en cours d'exécution racontent autre chose. Quand le runtime est livré dans ton image, construite depuis ton dépôt, un correctif n'est qu'un déploiement de plus : nouveau conteneur, l'ancien vidé de son trafic, chaque worker neuf par construction. Tu gagnes aussi un diff à relire et un bouton pour revenir en arrière si 8.4.27 bute sur une extension chargée dont tu avais oublié l'existence.

Le calendrier rend l'argument difficile à esquiver. L'équipe de threat intelligence d'Amazon indique que son honeypot MadPot a capté une tentative d'exploitation de PHP2Shell le 17 octobre, quatre jours avant la publication, et qu'elle a observé davantage de trafic de preuve de concept autour de la divulgation, en provenance d'infrastructures dispersées. Amazon n'a pas su dire si cette tentative précoce aurait fonctionné contre un vrai serveur. Reste que si ton circuit de mise à jour du runtime passe par un ticket, un comité de validation des changements et une fenêtre de maintenance, quatre jours, c'est à peu près le temps qu'il faut pour caler la réunion. Les chercheurs crédités pour avoir fait passer ces failles en correctif urgent, Calif, OpenAI et Wiz, ne seront pas les derniers à dénicher ce genre de chose avec des outils qui ne cessent d'accélérer.

En attendant de pouvoir déployer, les palliatifs proposés par le projet valent la peine d'être appliqués, tant que personne ne les prend pour le correctif. Désactive session.upload_progress.enabled, et sur 8.5, coupe fatal_error_backtraces, puis redémarre vraiment les workers pour que le changement d'ini soit pris en compte. Limite la taille des corps de requête au niveau du proxy. Et résiste à l'idée astucieuse de plafonner la mémoire des processus avec RLIMIT_AS ou RLIMIT_DATA : l'avis prévient explicitement que ça peut précipiter le moteur tout droit dans le chemin d'échec que le correctif supprime. Cloudflare et AWS ont tous deux déployé des règles WAF, ce qui fait gagner du temps pour le trafic qui passe par eux, et strictement rien pour une IP d'origine que n'importe qui peut atteindre directement.

Alors voilà ce que j'aimerais savoir, surtout si tu gères plus d'une poignée de services. La version de PHP vit-elle dans ton dépôt, figée dans un Dockerfile ou une contrainte platform de composer qu'une pull request peut modifier, ou appartient-elle à une équipe plateforme ou à un hébergeur qui la met à jour sans toi ? Et dans le second cas, quand la version du 21 octobre est sortie, combien de temps a-t-il fallu avant que les processus qui servent réellement ton trafic exécutent le nouveau code, en heures, et pas d'après ce qu'affichait ton gestionnaire de paquets ?