HardStacked
Partager :

Audit · signaux trompeurs

Cinq voyants verts qui mentaient : autopsie d'un checkup Linux

2026-07-20 · HardStacked · 7 min de lecture

Un checkup de routine sur une de nos machines Linux durcies. Verdict final : rien à réparer, rien de suspect, score de durcissement stable. Une heure banale, en apparence. Sauf que sur le chemin de ce « tout vert », cinq signaux parfaitement rassurants ont essayé de nous mentir, et deux ont bien failli passer.

Auditer, ce n'est pas collecter des OK. C'est demander, devant chaque chiffre : qu'est-ce qui pourrait masquer la vérité ici ? Voici les cinq pièges de cette tournée, dans l'ordre où ils se sont présentés, avec l'habitude qui désamorce chacun.

1. Un grep qui ne renvoie rien n'est pas un zéro

Premier réflexe du checkup : relire le score du dernier audit dans le fichier de rapport. La commande sort, ne renvoie rien. Lecture tentante : pas de ligne, pas de score, la machine n'a peut-être jamais été scannée. Lecture vraie : le fichier est lisible par root seulement, et le grep est mort sur un refus de permission, pas sur une absence de résultat.

$ grep 'hardening_index' /var/log/lynis-report.dat $ echo $? 2 ← code 2 : erreur de lecture. « Rien trouvé » aurait été 1. $ ls -l /var/log/lynis-report.dat -rw-r----- 1 root root 92779 jui 19 20:10 /var/log/lynis-report.dat

Une sortie vide a trois causes possibles : le motif est absent, le fichier est illisible, ou la commande elle-même a échoué. Elles se ressemblent à l'écran et ne veulent pas du tout dire la même chose.

L'habitude

L'absence de sortie n'est pas une donnée. Avant de conclure, echo $? et un ls -l sur la cible : grep distingue proprement « rien trouvé » (code 1) de « pas pu lire » (code 2).

2. La RAM « libre » ment, la RAM « disponible » dit vrai

Deuxième arrêt : la mémoire. Sur une machine de 8 Go, free affiche 351 Mio libres. Lecture tentante : la machine étouffe, il faut tuer quelque chose. Lecture vraie : le noyau emploie volontairement la RAM inoccupée comme cache disque, et ce cache se libère instantanément dès qu'un programme en a besoin.

$ free -h total used free shared buff/cache available Mem: 7.9Gi 5.5Gi 351Mi 264Mi 2.5Gi 2.4Gi ↑ la panique ↑ la vérité

Le seul chiffre qui mesure la pression réelle est available : l'estimation du noyau de ce qui peut être servi sans toucher au swap. Ici, 2,4 Gio, soit 30 % de la machine. Aucune pression. Un moniteur réglé sur free aurait sonné l'alarme pour rien ; pire, quelqu'un aurait pu « libérer » de la mémoire en tuant un service sain.

L'habitude

Les seuils d'alerte mémoire se règlent sur available (MemAvailable dans /proc/meminfo), jamais sur free. Une machine Linux en bonne santé a presque toujours un free minuscule : c'est le signe que le noyau travaille, pas qu'il se noie.

3. « inactive » veut aussi dire « ce nom n'existe pas »

Celui-là, c'est notre erreur, et on la garde dans l'article parce qu'elle est instructive. Au moment de vérifier le HIDS de la machine, on a interrogé le service sous son nom d'agent : réponse inactive. Pendant plusieurs minutes, le HIDS a été considéré comme éteint. Il tournait parfaitement : il roule en mode manager, sous un autre nom d'unité, et celui qu'on interrogeait n'a jamais existé sur cette machine.

Le piège est structurel : systemctl is-active répond inactive pour une unité inexistante, avec le même code de sortie qu'un service réellement arrêté (noms d'unités génériques ci-dessous) :

$ systemctl is-active hids-agent.service inactive $ echo $? 3 ← identique à un service arrêté. Or cette unité n'existe pas. $ systemctl list-unit-files | grep -i hids hids-manager.service enabled ← le vrai nom. Il tournait depuis le début.

Dans un rapport d'audit, « le HIDS est éteint » et « j'ai mal épelé le HIDS » produisent exactement la même ligne de sortie. Seul le second regard les départage.

L'habitude

Avant de déclarer un service mort, confirmer que le nom existe : systemctl list-unit-files | grep -i <nom>. Un inactive sur un nom jamais vérifié ne prouve rien du tout.

4. Un fichier d'hier peut porter un chiffre d'il y a quinze jours

Retour au score. Faute de pouvoir lire le rapport officiel sans privilèges, on cherche une copie lisible dans nos archives de travail, triée par date de modification, la plus récente d'abord. Elle annonce un score de 70. La machine est à 80. Dix points envolés ? Non : le fichier avait été copié la veille, mais son contenu datait de deux semaines. Le tri par mtime avait remonté une vieille photo dans un cadre neuf.

$ ls -l archives/lynis-report-copie.dat -rw-r--r-- 1 user user 92779 jui 19 00:24 archives/lynis-report-copie.dat ← « hier » $ grep '^report_datetime_start' archives/lynis-report-copie.dat report_datetime_start=2026-07-05 21:12:04 ← le contenu, lui, a quinze jours

Toute copie, tout backup, tout rsync mal réglé rajeunit un mtime sans rajeunir une donnée. Sur un chiffre d'audit, l'écart entre les deux peut faire conclure à une régression qui n'existe pas, ou masquer une régression réelle.

L'habitude

Date le contenu, jamais le fichier. Les rapports sérieux portent leur horodatage à l'intérieur (report_datetime_start chez Lynis) ; c'est celui-là qui fait foi, et c'est le premier champ à lire avant de citer un chiffre.

5. Le score dit que la porte est verrouillée, pas si quelqu'un a secoué la poignée

Le score de durcissement était bon, stable, avec deux warnings connus et documentés comme faux positifs de la distribution. La tentation à ce stade : fermer le dossier. Mais un score est une photo de la configuration au repos. Il ne dit rien de ce qui s'est réellement passé sur la machine. La seconde moitié du checkup vit dans trois endroits que le score ne regarde jamais.

D'abord, la surface réelle. Des clés SSH trouvées sur le disque ont fait lever un sourcil : surface exposée ? Non. Des clés font de la machine un client qui sort. La surface d'attaque, c'est un démon qui écoute, et ce qu'il attire quand il écoute, on l'a mesuré ailleurs :

$ ss -tln | grep ':22' $ ← rien n'écoute. Des clés sur le disque, aucune porte ouverte. $ faillock --user admin admin: When Type Source Valid $ ← table vide : pas une seule authentification échouée.

Ensuite, les compteurs d'échec. Une table faillock vide et un journal d'authentification où chaque élévation de privilèges se rattache à une action connue, c'est la preuve vivante que personne n'a même essayé. Aucun score ne contient cette information.

Enfin, les listes blanches. La machine porte une liste d'exclusions pour son contrôle d'intégrité de fichiers. Chaque ligne d'une liste blanche est un angle mort choisi : « ne me préviens plus si ce fichier change ». On les a relues une par une. Trois se justifiaient d'elles-mêmes ; la quatrième a exigé de retrouver qui avait modifié quoi, et pourquoi. Elle était légitime. Mais la seule façon de le savoir était de poser la question.

L'habitude

Termine chaque checkup par la moitié vivante : journal d'authentification, compteurs de verrouillage, et relecture de chaque ligne d'allowlist avec la même question : « suis-je encore d'accord pour être aveugle à ce fichier ? »

Les quatre règles d'un voyant qu'on peut croire

  1. L'absence de sortie n'est pas une donnée. Code de retour et permissions avant toute conclusion : vide-parce-qu'absent et vide-parce-qu'illisible ne se distinguent pas à l'écran.
  2. Nomme exactement ce que tu mesures. Unité systemd, chemin, champ : un verdict posé sur un nom jamais vérifié est un verdict sur rien.
  3. Date le contenu, pas le fichier. Un mtime frais ne rajeunit pas un chiffre ; toute copie fabrique de la fausse fraîcheur.
  4. Score statique + logs vivants, toujours les deux. L'un dit que la porte est verrouillée, l'autre dit si quelqu'un a secoué la poignée. Chacun seul est une demi-vérité.

Ce que la tournée a donné au final

Rien. Et c'est le plus beau résultat possible : score stable à 80 (comment ce chiffre se gagne et se perd, on l'a mesuré fixe par fixe), deux warnings identifiés de longue date comme faux positifs, table d'échecs vide, journaux propres, listes blanches revalidées ligne par ligne. Zéro correctif appliqué.

Mais les cinq verts qu'on a fini par croire sont des verts qu'on a d'abord contestés. Le même checkup mené au premier degré aurait déclaré un HIDS éteint qui tournait, une machine asphyxiée qui respirait, et une régression de dix points qui n'a jamais existé. Trois fausses alertes, trois « corrections » possibles sur un système sain. Le doute méthodique n'a pas seulement évité des conclusions fausses : il a évité des actions fausses.

La leçon de fond

Aucun de ces pièges n'est exotique. Ils se déjouent tous avec une vérification de plus : un code de retour, un nom confirmé, un horodatage interne. Exactement les vérifications qu'on saute quand le voyant est vert et qu'on veut passer à autre chose.

C'est pour ça que HardVps ne rapporte jamais un « succès » déclaré : chaque fixe est appliqué avec l'original capturé d'abord, chaque restauration est vérifiée à l'octet près par SHA-256, et le rapport montre le delta mesuré avant/après, pas une case cochée.

Voir ce que HardVps fait →

Sur un Raspberry Pi ? Commence gratuitement avec audit-pi, notre audit de sécurité open source.

Partager :