Mercredi 21 octobre, 14h. Les sites des préfectures de France s'éteignent les uns après les autres, dans le calme, comme des réverbères à l'aube. Personne ne les a piratés. L'ANSSI a simplement estimé qu'un site hors ligne, c'est un site qu'on ne peut pas piratér, ce qui est une définition de la cybersécurité à laquelle je n'avais pas pensé mais que je respecte. Les Pays-Bas, la Finlande et la Flandre font pareil. Pendant quelques heures, l'administration européenne a eu exactement la disponibilité d'un guichet de préfecture un vendredi à 16h30. Au moins c'était cohérent.
Le responsable s'appelle PHP. C'est le langage qui fait tourner 70 % du web, dont la page que vous lisez, dont le site de votre mairie ...
Mercredi soir, après avoir lu tout ça, j'ai fait ce que fait n'importe quel admin un peu inquiet : j'ai ouvert les logs avec la même tête que quand on retourne un matelas d'hôtel. Et j'ai découvert que le matin même, à 8h06, pendant que je me servais une C4 bien fraiche, quelqu'un avait déjà essayé la faille sur mon serveur. 4055 fois. En huit minutes. Huit heures avant que le correctif n'existe.
Ça n'a pas marché. Et le plus vexant, c'est que je n'y suis pour rien.
La faille, expliquée à quelqu'un qui a une vie
PHP, c'est le moteur qui exécute le code des sites dynamiques. WordPress, Drupal, PrestaShop, les intranets, et cette appli de réservation de salles qu'un stagiaire a codée en 2014 et dont personne ne retrouve le mot de passe.
D'habitude, une « faille PHP », c'est un plugin WordPress développé par un type qui a appris le PHP la semaine d'avant sur un forum indien. Là, non. Les deux bugs sont dans PHP lui-même. Dans le moteur. Peu importe ce que vous faites tourner dessus : si votre serveur sert du PHP, vous avez reçu un carton d'invitation sans l'avoir demandé.
Le premier bug (CVE-2026-109618) est une histoire de mémoire. On envoie au serveur une requête taillée pour que PHP manque de RAM en plein milieu de son travail. PHP fait alors ce que fait PHP : il panique, lâche tout et s'en va. Sauf qu'il oublie de ranger. Il laisse traîner des structures à moitié remplies, et au nettoyage suivant, il les prend pour des données parfaitement valides. C'est le colocataire qui met une casserole sur le feu, sort fumer, revient quarante minutes plus tard et vous demande pourquoi ça sent le plastique. Quelqu'un qui sait exactement quoi envoyer peut, à cet instant précis, glisser son propre code dans la casserole.
Le second (CVE-2026-109619) est du même tonneau : après une erreur fatale, PHP garde des restes de la requête précédente et s'en resert dans la suivante. Comme un restaurant qui recycle le pain. L'un des chemins confirmés passe par l'endpoint batch de l'API REST de WordPress. Retenez ce détail. Il revient plus bas, et c'est lui qui m'a fait rire.
Ce qui rend l'affaire critique, 9,2 sur 10 : pas besoin de compte, pas de mot de passe, pas de plugin particulier. Il suffit d'envoyer des requêtes. Si ça passe, l'attaquant est chez vous, en chaussettes, en train de regarder dans votre frigo. Base de données, fichiers, porte dérobée, rebond vers le reste du réseau. Le surnom donné à l'ensemble, PHP2Shell, est moins un surnom qu'un mode d'emploi.
La journée où tout le monde a couru en même temps
La chronologie explique pourquoi l'État a préféré débrancher plutôt que faire une prière.
- 24 août : des chercheurs signalent les deux bugs à l'équipe PHP, sous embargo. Deux mois de silence. Tout le monde chuchote.
- Mardi 20 octobre, matin : un message sur Hacker News, supprimé depuis, balance tout. Avec, en cadeau, un outil d'attaque clé en main qui prétend fonctionner sur toutes les versions depuis PHP 5.4. Donc depuis 2012. Donc sur le site du club de pétanque, celui avec le compteur de visites.
- Mardi 20, après-midi : des chercheurs confirment que c'est réel. Le CERT-EU publie un avis. L'équipe PHP annonce un correctif pour le lendemain 16h, ce qui en langage éditeur veut dire « on n'a pas dormi ».
- Mercredi 21, matin : les Pays-Bas et la Finlande coupent leurs sites. Avant le patch. Quand les Finlandais paniquent, c'est que le sauna est vraiment en feu.
- Mercredi 21, 14h : l'ANSSI fait couper les sites institutionnels. Les préfectures tombent. Les délais de titres de séjour, eux, restent inchangés.
- Mercredi 21, 16h : le correctif sort. PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35. Pour PHP 8.1 et avant : des patchs source à compiler soi-même, ce qui est une façon polie de dire « vous auriez dû migrer en 2024 ».
- Mercredi 21, 16h15 : premières attaques de masse observées.
Quinze minutes. Le temps de comparer l'ancien code au nouveau, de voir où est le trou, et de reconstituer l'exploit. Ceux qui l'avaient déjà n'ont même pas eu à attendre.
Un État, c'est des centaines de sites PHP chez des dizaines de prestataires, dont certains ont un numéro de téléphone qui ne répond plus depuis 2019. Tout patcher en une après-midi relève de la science-fiction. Couper, c'est la seule protection à 100 %. C'est moche, c'est humiliant, et c'est exactement le bon réflexe.
Pendant ce temps, dans mon placard à balais
Mon serveur est une petite machine sous Rocky Linux, nginx devant, PHP-FPM derrière. Elle héberge ce blog et d'autres sites ou services. Mercredi soir, après la mise à jour, j'ai demandé aux logs qui avait envoyé des POST dans la journée. Réponse :
3348 195.178.110.102 /
414 195.178.110.102 /index.php
21 195.178.110.102 /wp-json/batch/v1
21 195.178.110.102 /wp-json/batch/v1/
21 195.178.110.102 //wp-json/batch/v1
21 195.178.110.102 /wordpress/wp-json/batch/v1
21 195.178.110.102 /?rest_route=/batch/v1
21 195.178.110.102 /?rest_route=%2Fbatch%2Fv14055 requêtes POST. Une seule IP, chez un petit hébergeur bulgare que CrowdSec a dans ses contacts favoris. Le tout entre 8h06 et 8h14.
Relisez la chronologie. 8h06, c'est huit heures avant le correctif. Ce n'était pas quelqu'un qui avait étudié le patch. C'était quelqu'un qui avait l'outil fuité de la veille, et qui balayait Internet avec, à l'heure où je cherchais mes clés.
Le pattern se lit comme une partition. Les milliers de POST sur / et /index.php, c'est la faille mémoire : elle marche sur n'importe quel point d'entrée PHP, donc le scanner tape sur les deux URL les plus probables. Les wp-json/batch/v1 sous huit orthographes différentes, majuscules, double slash, sous-dossier, encodage URL, c'est la seconde faille, celle qui passe par WordPress, déclinée dans toutes les variantes qu'un filtre pourrait rater. Entre deux, il a fait ses courses : thèmes twentytwenty*, readme.txt d'une dizaine de plugins, sitemap, API REST sous toutes ses coutures. Un scanner WordPress, c'est un touriste qui visite chaque pièce de la maison pour vérifier que vous n'avez pas caché un WordPress dans le cellier.
Et maintenant, la colonne qui compte :
3411 405
869 404Pas un 500. Pas un 502. Pas un worker PHP qui a toussé. Aucun fichier inconnu, rien dans /tmp, pas de crontab mystère, aucune connexion sortante. Le type a cogné 4055 fois à la porte. PHP n'a pas levé la tête. PHP n'était même pas dans la pièce.
Ce qui m'a sauvé
Mercredi à 8h06, je ne savais pas que PHP2Shell existait. Si l'exploit avait trouvé un endpoint à se mettre sous la dent, il l'aurait croqué. Ce qui a tenu, ce sont des décisions prises il y a des années, pour des raisons qui n'avaient strictement rien à voir avec la sécurité et tout à voir avec ma flemme.
Pas de WordPress. J'ai déjà raconté ici pourquoi j'ai viré WordPress et réécrit ce site à la main. J'en avais marre de surveiller un CMS comme du lait sur le feu : mises à jour hebdomadaires, plugins orphelins, et un wp-admin qui attire les scanners comme un roti dehors un midi d'été attire les guêpes. On m'a dit que c'était de la perte de temps. Mercredi : 869 requêtes vers des chemins WordPress, 869 réponses 404. L'endpoint batch/v1, le chemin d'attaque de la seconde faille, n'existe pas ici. On ne crochète pas une serrure sur un mur.
Pas d'index.php qui attend le client sur le vhost par défaut. Le scanner m'a attaqué par adresse IP, pas par nom de domaine. Il est donc tombé sur le serveur par défaut de nginx, qui sert une page statique et rien d'autre. Un POST sur une page statique, nginx répond 405 sans même se retourner, comme un serveur de bistrot parisien. 3411 fois. La faille mémoire a besoin que PHP traite la requête. PHP n'a pas été consulté.
Un site qui fait son boulot et rien d'autre. Un site maison, c'est une surface d'attaque de la taille d'un timbre : quelques scripts dont je connais chaque ligne, pas d'API générique, pas de système de plugins qui expose des routes dont j'ignore l'existence. Quand vos URL ne ressemblent à rien de standard, l'outil automatique de l'attaquant tire dans le vide, et le vide répond 404.
Je ne vais pas en tirer une loi universelle. Un site maison, ça veut dire que les bugs sont les miens et que personne ne me préviendra, à part les logs, un soir, à 23h. Mais sur cette faille précise, « moins de trucs installés, moins de trucs à casser » a fonctionné au-delà de mes espérances, c'est-à-dire que je n'en avais aucune.
Ce qui ne m'a pas sauvé
Il y a quand même une leçon de mercredi soir, et elle pique.
CrowdSec tourne sur ce serveur depuis des lustres. Il lit les logs, repère les comportements hostiles, décide de bannir, et partage ses décisions avec la communauté. Mercredi matin, il a fait son travail de détection à la perfection : dès 8h06, dans la première minute, il a décidé de bannir l'IP bulgare pour http-probing, puis http-wordpress-scan, puis http-cve-probing. Cinq décisions de ban en deux minutes. Un sans-faute.
Et l'IP a continué à taper pendant sept minutes.
Parce que CrowdSec, c'est deux morceaux. L'agent, qui détecte et décide. Et le bouncer, qui applique la décision dans le firewall. Le bouncer, je ne l'avais jamais installé. cscli bouncers list renvoyait une liste vide. Des mois de bans soigneusement décidés, consciencieusement transmis à la communauté, et jamais appliqués sur ma propre machine. J'avais embauché un vigile, et il notait les noms. Très proprement. Dans un carnet. Pendant que les gens entraient.
C'est réglé depuis mercredi 23h37. Cinq minutes d'installation, et 17 000 adresses connues sont maintenant jetées par le firewall avant même d'atteindre nginx. Dans les deux premières minutes, 16 paquets bloqués. Seize lignes de 404 que je ne lirai jamais, et c'est le plus beau cadeau que je me suis fait cette année.
Moralité : installer un outil de sécurité ne suffit pas. Il faut vérifier qu'il ferme vraiment la porte. Et la seule façon d'en être sûr, c'est de le regarder pendant une vraie attaque. Méthode que je ne recommande qu'à moitié.
Si vous hébergez du PHP, c'est maintenant
Dans l'ordre. Pas demain. Demain c'est le jour où on lit l'article de quelqu'un d'autre sur sa compromission.
- Mettez à jour PHP. Versions corrigées : 8.5.12, 8.4.27, 8.3.36, 8.2.35. Sur Debian et Ubuntu, le numéro ne bouge pas forcément, le correctif est backporté : lisez le changelog du paquet, pas
php -v. Sur Rocky et Alma, le dépôt Remi avait le correctif dès mercredi soir pendant que la distribution cherchait encore ses lunettes. En 8.1 ou plus vieux, pas de version officielle : passez en 8.2, et profitez-en pour vous demander pourquoi vous étiez encore en 8.1. - Redémarrez PHP-FPM et le serveur web. Sinon le vieux code tourne toujours en mémoire, et vous avez mis à jour pour le sport.
- Vérifiez.
apt changelog php8.3-fpm | grep 109618ourpm -q --changelog php-fpm | grep 109618. Si ça ne sort rien, vous n'êtes pas patché, quoi qu'en dise le joli badge vert de votre hébergeur. - Lisez vos logs. POST groupés par IP, codes de réponse. Des 502 en rafale depuis une même IP, c'est PHP qui a souffert. Des 404 et des 405, c'est le scanner qui a parlé à un mur.
- Si vous aviez un WordPress exposé avant la mise à jour, cherchez les traces : fichiers PHP modifiés récemment, trucs bizarres dans
/tmp, crontabs inconnues, connexions sortantes depuis PHP. Si vous trouvez quelque chose, le serveur est compromis jusqu'à preuve du contraire, et la preuve du contraire est rarement au rendez-vous. - Si vous ne pouvez pas patcher tout de suite, les mesures provisoires de l'équipe PHP réduisent l'exposition sans la supprimer :
memory_limit = -1imposé viaphp_admin_value,post_max_sizebas,client_max_body_sizecôté nginx. Ou faites comme les préfectures : coupez. Ça marche, et ça a le mérite de ne demander aucune compétence. - Vérifiez que votre outil de sécurité fait quelque chose.
cscli bouncers list. Si c'est vide, bienvenue au club. On se réunit le mercredi soir, c'est moi qui apporte les logs.
Et si vous avez un WordPress que vous ne touchez qu'une fois par an, c'est peut-être le moment de lui demander ce qu'il fait encore là.
Sources :
- Avis de sécurité PHP, 21 octobre 2026
- CVE-2026-109618 (GHSA-w8hw-w9fx-j79w)
- CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp)
- Red Hat, CVE-2026-109618
- Sites des préfectures et impôts mis hors ligne sur recommandation de l'Anssi (Renseignement Économique)
- Failles critiques dans PHP : des sites coupés en urgence (Actu17)
- PHP : 2 failles critiques, un correctif et quinze minutes avant les premières attaques (Journal du Coin)
- PHP CVE-2026-109618 and CVE-2026-109619: how to apply the patches (ma.ttias.be)