Illustration : vérification du correctif, quatre couches de journaux et une rotation qui ne tournait pas

Ce rapport est anonymisé. Il décrit un incident réel sans nom d'hôte, sans adresse et sans données clients. Le sujet n'est pas la vulnérabilité, mais la chaîne de preuve : que vérifie-t-on lorsqu'il faut décider en quelques heures si un système a été compromis — et avec quelle facilité le résultat peut être mal lu ?

Situation initiale

En l'espace d'une journée, le projet PHP a publié deux vulnérabilités critiques exploitables sans authentification (CVE-2026-109618 et CVE-2026-109619, CVSS 9,2 chacune, désignées ensemble « PHP2Shell »). Sont concernées les installations PHP qui traitent des requêtes web. L'hôte en question exploitait un reverse proxy, une instance Nextcloud sur PHP-FPM et plusieurs sites statiques. La question de l'exploitant était simple :

Sommes-nous concernés ? Le correctif est-il actif ? Et les journaux montrent-ils une exploitation ?

Trois questions, trois problèmes de preuve très différents. C'est là que résidait la difficulté.

Étape 1 — prouver l'exposition au lieu de la supposer

Le premier réflexe (« nous utilisons PHP, donc nous sommes concernés ») est trop grossier. Ce qui compte, c'est la SAPI : seules les installations qui traitent des requêtes web sont exposées. La preuve a donc été concrète :

Étape 2 — preuve du correctif : paquet et exécution

Une mise à jour de sécurité installée n'est pas encore un correctif actif. Entre « le paquet est sur le disque » et « le processus en cours l'utilise » se trouve un redémarrage du maître FPM. La vérification a donc été double :

VérificationRésultatCe qu'elle prouve
Version du paquet8.5.4-0ubuntu1.5 (correctif Ubuntu, USN-8948-1/-2)Le correctif est installé
Date d'installation2026-10-22 06:12 (mises à jour automatiques)Quand le correctif est arrivé
Redémarrage FPM2026-10-22 06:13, maître actif depuisLe correctif s'exécute
Intégrité des fichiersvérification des paquets sans écartAucun binaire manipulé

Ces quatre lignes constituent la véritable preuve du correctif. Tout le reste est interprétation de journaux.

Étape 3 — quatre couches de journaux, quatre questions différentes

Un seul journal ne peut pas prouver un incident. Chaque couche répond à une question différente — et possède son propre angle mort :

CoucheRépond àAngle mort
Journal d'accès du proxyQuelles requêtes sont arrivées, avec quel statut et quelle taille de corps ?Aucun contenu ; la rotation limite la rétrospective
Journal applicatif (Nextcloud)Qu'a fait l'application en interne — erreurs, avertissements, connexions ?Au niveau info, seulement des événements, pas d'accès
Journal FPMPlantage de workers, épuisement mémoire, erreurs fatalesNiveau PHP uniquement, sans contexte HTTP
Journald / noyauOOM kills, segfaults, core dumpsAucune sémantique applicative

En complément : Fail2ban (force brute SSH) et le gestionnaire de paquets (moment du correctif). Seule la combinaison porte une affirmation — et seulement si les angles morts sont nommés.

Le faux constat — « l'application n'écrit pas de journal »

La recherche du journal applicatif a parcouru les répertoires habituels (/srv, /var, /home, profondeur limitée) sans rien trouver. La conclusion s'imposait : il n'y a pas de journal. Elle était fausse.

Deux choses avaient été négligées :

  1. Le répertoire de données se trouvait hors de l'espace de recherche. L'instance ne stockait pas ses données dans le répertoire du programme, mais sur un volume dédié. La recherche était minutieuse — simplement pas là.
  2. Le journaliseur avait un repli. Si le fichier de journal configuré n'existait pas et que son répertoire n'était pas accessible en écriture, l'application écrivait dans un fichier de substitution dans le répertoire de données. C'est un comportement correct — mais il masque une mauvaise configuration.

Le défaut réel se trouvait une couche plus bas : la tâche de rotation lit le chemin du journal dans la configuration. Comme ce chemin n'existait pas, elle comparait la taille d'un fichier inexistant — et ne tournait jamais. Résultat : un seul fichier de 438 Mo au lieu d'une rotation horaire.

flowchart TD C["Configuration :
logfile = dir-programme/data"] --> L{"Fichier présent ?"} L -->|non| F["Le journaliseur utilise le repli :
dir-données/nextcloud.log"] L -->|oui| W["Le journaliseur écrit le fichier configuré"] C --> R["La tâche de rotation lit
le même chemin configuré"] R --> N{"Fichier là ?"} N -->|non| X["Aucune rotation
(le fichier croît sans limite)"] F --> X

La leçon est inconfortable, car elle concerne notre propre travail : « non trouvé » n'est pas un constat tant que l'espace de recherche n'est pas documenté. Un constat négatif exige le même soin qu'un constat positif — y compris l'indication des endroits où l'on n'a pas cherché.

Le deuxième faux constat — le pare-feu

Un port d'administration était « accessible uniquement dans le LAN ». La vérification n'a trouvé aucune autorisation spécifique à ce port et a noté : aucune autorisation, donc fermé de l'extérieur. C'était également trop court.

En réalité, une règle générale pour tout le sous-réseau LAN existait, autorisant tous les ports. Le port était donc ouvert dans le LAN — la protection venait de la topologie réseau, non de la règle de port. Le résultat (« non accessible depuis Internet ») était correct, la justification non. Et une justification erronée est le début de la décision erronée suivante.

Règle : on lit un pare-feu dans son ensemble — politique par défaut, autorisations générales, ordre. Un simple regard sur « manque-t-il une autorisation de port ? » ne suffit pas.

Ce que les journaux montraient réellement

Après correction du chemin du journal, l'évaluation était possible — et le tableau s'est révélé banal :

Le seul résultat « chaud » était un scanner envoyant quatre requêtes d'environ 1 Mo au site Adminer orphelin — exactement le schéma d'une tentative d'exploitation. Les quatre se sont terminées en 502 car le socket PHP n'existait pas. L'attaque visait un backend mort. C'est aussi un résultat : le constat le plus dangereux a démontré l'efficacité d'un durcissement (involontaire).

La limite de la preuve — et pourquoi il faut sécuriser les preuves

Deux règles de rétention limitaient l'affirmation :

  1. Journaux du proxy : rotation 5 × 10 Mo — environ dix jours de rétrospective. La période était entièrement couverte, pas davantage.
  2. Journal applicatif : la rotation ne conserve qu'un seul fichier précédent. À la rotation suivante, la période évaluée aurait été écrasée.

C'est pourquoi la période a été sécurisée avant la rotation suivante sous forme d'archive propre : compressée, propriétaire root, droits 640 (le fichier contient des noms d'utilisateur et des adresses IP), avec une somme de contrôle documentée. Une preuve non reproductible vaut peu lors d'un audit — une somme de contrôle transforme un fichier en affirmation vérifiable.

flowchart LR A["Journal en production
(un seul fichier tourné)"] --> B["Extrait de période
2026-10-15 … 2026-10-25"] B --> C["Archive gzip
root:root 640"] C --> D["SHA-256 dans le rapport"] D --> E["Reproduction :
gunzip + wc -l"]

Ce qui a bien fonctionné

Ce que nous ferons autrement la prochaine fois

  1. Documenter l'espace de recherche avant la recherche. N'écrire « non trouvé » que si les chemins et profondeurs sont nommés.
  2. Associer une contre-épreuve à chaque affirmation de configuration. Chemin du journal : non seulement le lire, mais vérifier que le fichier existe, croît et tourne.
  3. Tester la rotation activement (dépasser la taille artificiellement, prouver la rotation dans le journal) — ne pas supposer qu'elle fonctionne.
  4. Lire le pare-feu dans son ensemble (politique par défaut, règles générales de sous-réseau, ordre).
  5. Démanteler immédiatement les sites orphelins. Un site PHP mort n'est pas un risque, mais une source de bruit permanente dans les journaux et un angle mort de l'évaluation.
  6. Sécuriser les preuves avant la rotation — avec propriétaire, droits et somme de contrôle.
  7. Corriger les faux constats, ne pas les lisser. Le rapport contient la correction avec date et cause. Un faux constat réparé en silence détruit plus la confiance que l'erreur elle-même.

Transférabilité

L'incident lui-même fut peu spectaculaire : corrigé, non exploité, aucune notification requise. Ce qui a de la valeur, c'est l'appareil de preuve : exposition via inventaire, correctif via paquet et exécution, exploitation via plusieurs couches de journaux, évaluation avec limites de preuve explicites, preuve avec somme de contrôle — et un rapport avec historique des modifications qui contient aussi notre propre erreur d'appréciation. Cette structure peut être versionnée dans Git et vérifiée des années plus tard.

Sources