guest@6admin.io:~$

Durcir l'OS et le kernel Linux

Une machine Linux fraîchement installée est bâtie pour être commode, ce qui veut dire qu’elle arrive avec des services que vous n’avez pas demandés, des fonctions kernel que vous n’utiliserez jamais et des défauts choisis pour ne surprendre aucun débutant. La durcir est surtout de la soustraction: retirer ce qui écoute, couper les surfaces kernel dont vous n’avez pas besoin, resserrer la poignée de boutons sysctl qui comptent et verrouiller les comptes et le chargeur de modules. Les étapes individuelles sont petites et bien connues. La discipline est de les faire délibérément plutôt que d’hériter de ce que l’installeur a décidé et de garder la machine patchée, parce que les exploits kernel qui comptent (PwnKit, Dirty Pipe et leurs successeurs) sont corrigés bien avant que la plupart des machines n’appliquent le correctif.

Réduire la surface avant de la configurer

La première passe est un inventaire, parce qu’on ne peut durcir ce qu’on n’a pas remarqué. Ce qui écoute, ce qui est activé et ce qui est installé sans nécessité:

ss -tlnp                       # sockets en écoute et leurs processus
systemctl list-unit-files --state=enabled

Chaque service activé qui ne gagne pas sa place est retiré, pas seulement arrêté, parce qu’un paquet désactivé est un paquet qu’une mise à jour ou un admin négligent réactive. Une installation minimale qui n’a jamais eu le service vaut mieux qu’une installation complète avec le service coupé. C’est le durcissement le moins cher qui soit et le plus souvent sauté, parce que la machine marche déjà et que personne ne veut y toucher.

Les boutons kernel qui valent la peine

La surface sysctl est large et l’essentiel est du bruit; un ensemble ciblé fait un vrai travail. Voici ceux que je pose sur chaque serveur, groupés par ce qu’ils défendent:

# /etc/sysctl.d/hardening.conf

# restreindre l'information et le traçage qui aident l'escalade locale
kernel.dmesg_restrict = 1          # les non-privilégiés ne lisent pas le ring buffer
kernel.kptr_restrict = 2           # cacher les pointeurs kernel de /proc
kernel.yama.ptrace_scope = 1       # un processus ne peut ptrace que ses descendants
kernel.unprivileged_bpf_disabled = 1
kernel.kexec_load_disabled = 1     # pas de chargement d'un nouveau kernel à l'exécution

# protections de liens et fifo contre les races de /tmp
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
fs.suid_dumpable = 0               # pas de core dump des binaires setuid

# réseau: ignorer les redirects et le source routing que personne n'utilise légitimement
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1    # filtrage par chemin inverse, anti-spoof
net.ipv4.tcp_syncookies = 1

ptrace_scope, kptr_restrict et dmesg_restrict sont le groupe qui compte le plus, parce qu’ils ferment les chemins d’information et de débogage sur lesquels s’appuie un exploit local: un processus non privilégié qui s’attache à un autre pour voler un identifiant, des adresses kernel qui fuient pour défaire le KASLR, le ring buffer qui remet à l’attaquant la disposition qu’il cherche. Aucun de ces réglages ne coûte quoi que ce soit à une charge normale et ensemble ils rendent une tentative d’escalade locale nettement plus dure.

Il existe un cran que le sysctl ne touche pas: la surface kernel la plus sûre est celle qu’on n’a jamais compilée. Désactiver une fonction vaut mieux que la laisser, mais ne jamais construire un sous-système vaut encore mieux que le désactiver et c’est un sujet à part entière, que je traite dans recompiler son kernel .

Le chargeur de modules

Un module kernel chargeable tourne en ring 0, ce pourquoi un rootkit LKM est le plus puissant et pourquoi le chargeur de modules est une surface à fermer une fois la machine démarrée et les modules nécessaires chargés. Imposez les signatures pour qu’un module non signé soit refusé et sur une machine dont le matériel ne change pas, désactivez entièrement le chargement après le boot:

# refuser les modules non signés
echo 'module.sig_enforce=1' >> ligne de commande kernel   # via la config du bootloader
# une fois les modules nécessaires chargés, claquer la porte pour la session
sysctl -w kernel.modules_disabled=1

modules_disabled=1 est un interrupteur à sens unique jusqu’au reboot: plus rien ne peut être chargé, ce qui défait entièrement le rootkit LKM au prix de ne pas pouvoir ajouter un pilote sans reboot, un échange presque toujours bon sur un serveur.

Comptes, authentification et sudo

La surface des comptes est la moitié ennuyeuse et celle que les attaquants utilisent vraiment. Pas de logins partagés, des comptes nommés uniquement et les décisions d’authentification et de privilège prises délibérément: qualité de mot de passe imposée par PAM là où des mots de passe existent, SSH passé en clés seules (son propre sujet) et sudo cadré plutôt que distribué en root global. Le détail sudo qui piège est traité longuement ailleurs et il se ramène ici à une ligne: une concession NOPASSWD sur un interpréteur, un éditeur ou un pager est du root sans mot de passe, donc les seules règles sudo sûres nomment des commandes qu’on ne peut transformer en shell.

Auditez l’inventaire setuid dans la foulée, parce que c’est là que l’escalade locale fait ses courses:

find / -xdev -perm -4000 -type f 2>/dev/null   # la liste doit être courte et connue

Tout ce qui surprend dans cette liste est un constat et un bit setuid dont rien n’a besoin est retiré.

Contrôle d’accès obligatoire et audit

AppArmor ou SELinux est la couche qui confine un service à ce qu’il est censé toucher, pour qu’un démon compromis ne puisse errer dans le système de fichiers même avec les droits que son utilisateur aurait autrement. Le faire tourner en mode enforcing pour les services exposés (le serveur web, le démon de mail) est une large réduction de ce que vaut la compromission d’un seul service et les distributions livrent des profils utilisables pour les plus courants. Le laisser en permissif ou désactivé “parce qu’il a gêné une fois” est renoncer à une frontière réelle pour épargner une après-midi de réglage de profil.

Le démon d’audit est le relevé sous tout cela: auditd avec des règles qui surveillent les fichiers sensibles et les syscalls qui changent le privilège transforme “il s’est passé quelque chose” en chronologie. Il ne prévient rien; c’est ainsi qu’on découvre ce qui a été fait, c’est-à-dire la différence entre un incident qu’on comprend et un qu’on ne fait que soupçonner.

Patcher, parce que l’exploit était corrigé avant d’être utilisé

Les exploits kernel et userspace qui font de vrais dégâts sont, avec une régularité frappante, ceux qui avaient été corrigés en amont des semaines ou des mois avant la brèche. PwnKit a siégé dans pkexec plus d’une décennie, Dirty Pipe a été corrigé dans une version mineure et dans les deux cas les machines tombées étaient celles qui n’avaient pas appliqué la mise à jour. Les mises à jour de sécurité automatiques sur un serveur ne sont pas imprudentes, elles sont l’inverse: le risque d’un reboot inattendu dû à une mise à niveau est petit et planifiable et le risque de faire tourner un kernel exploitable connu pendant des mois ne l’est pas. Automatisez le canal de sécurité, rebootez selon un calendrier pour les mises à jour kernel et traitez un long uptime comme un avertissement plutôt qu’un trophée.

Où atterrir

Réduisez la machine aux services dont elle a besoin et retirez le reste plutôt que de le désactiver. Posez le groupe sysctl ciblé, avec ptrace_scope, kptr_restrict, dmesg_restrict et les protections de liens pour cœur. Imposez la signature de modules et désactivez le chargeur après le boot sur un matériel figé. Gardez les comptes nommés, l’authentification par clé et sudo cadré sur des commandes qui ne peuvent devenir un shell, avec un inventaire setuid connu. Faites tourner AppArmor ou SELinux en enforcing pour les services exposés et auditd en dessous pour le relevé. Et patchez selon un calendrier, parce que presque chaque exploit kernel qui compte est un que quelqu’un aurait pu fermer avant qu’il ne soit utilisé. Le motif est soustraction et délibération: un hôte durci n’est pas celui qui a plus d’installé, c’est celui qui en a moins, choisi exprès et tenu à jour.

[edit: 2026] L’implémentation Ansible de tout ceci: les rôles baseline et unattended-upgrades .