14. Durcissement du système#
Le durcissement (hardening) consiste à réduire la surface d’attaque d’un système en supprimant ce qui n’est pas nécessaire, en confinant ce qui reste, et en surveillant ce qui se passe. Ce chapitre présente les techniques et outils essentiels pour sécuriser un serveur Linux en production.
Surface d’attaque et principes fondamentaux#
La surface d’attaque est l’ensemble des points d’entrée qu’un attaquant peut exploiter : ports ouverts, services en cours d’exécution, fichiers accessibles, interfaces réseau, utilisateurs avec privilèges excessifs, logiciels installés.
Principe du moindre privilège#
Chaque composant (utilisateur, service, processus) ne doit disposer que des droits strictement nécessaires à sa fonction. Ce principe limite l’impact d’une compromission : un attaquant qui prend le contrôle d’un service confiné ne peut pas se propager librement.
Défense en profondeur#
La défense en profondeur (defense in depth) empile plusieurs couches de sécurité indépendantes. La compromission d’une couche ne doit pas suffire à compromettre le système entier :
Internet → Pare-feu réseau → Pare-feu hôte (nftables)
→ AppArmor/SELinux → Permissions POSIX
→ Monitoring/auditd → Alertes SIEM
Aucune mesure prise isolément n’est suffisante. La combinaison de plusieurs mécanismes complémentaires est ce qui rend un système résillient.
Important
Le durcissement n’est pas un état final mais un processus continu. Les nouvelles CVE, les changements d’architecture et les mises à jour d’application nécessitent des réévaluations régulières.
Réduction de la surface d’attaque#
Désactiver les services inutiles#
# Lister tous les services actifs
systemctl list-units --type=service --state=running
# Désactiver et stopper un service inutile
systemctl stop bluetooth
systemctl disable bluetooth
systemctl mask bluetooth # Empêche toute réactivation accidentelle
# Vérifier les services activés au démarrage
systemctl list-unit-files --type=service --state=enabled
Services fréquemment inutiles sur un serveur : avahi-daemon (mDNS), cups (impression), bluetooth, ModemManager, wpa_supplicant (si pas de WiFi).
Supprimer les paquets superflus#
# Debian/Ubuntu : lister les paquets installés manuellement
apt-mark showmanual | sort
# Supprimer un paquet et ses dépendances orphelines
apt purge telnet rsh-client ftp
apt autoremove --purge
# Vérifier les paquets sans dépendant
deborphan
Inspecter les ports ouverts#
# Tous les ports en écoute avec le processus associé
ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678))
LISTEN 0 128 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=9012))
# Ports UDP
ss -ulnp
# Connexions établies
ss -tnp state established
Tip
PostgreSQL écoute sur 127.0.0.1:5432 (liaison locale uniquement). Si un service écoute sur 0.0.0.0 alors qu’il n’a pas besoin d’être accessible depuis l’extérieur, c’est une surface d’attaque inutile à fermer.
AppArmor#
AppArmor (Application Armor) est un système de contrôle d’accès obligatoire (MAC) basé sur des profils. Il confine les programmes en définissant précisément les fichiers, capabilities et sockets auxquels ils ont accès.
Modes de fonctionnement#
Mode |
Description |
|---|---|
enforce |
Le profil est appliqué ; les violations sont bloquées et journalisées |
complain |
Les violations sont journalisées mais pas bloquées (phase d’apprentissage) |
unconfined |
Aucun profil actif pour ce programme |
Consulter l’état d’AppArmor#
aa-status
apparmor module is loaded.
63 profiles are loaded.
49 profiles are in enforce mode.
/usr/bin/evince
/usr/sbin/nginx
/usr/sbin/sshd
...
14 profiles are in complain mode.
/usr/bin/firefox
...
0 profiles are in kill mode.
2 processes have profiles defined.
2 processes are in enforce mode.
0 processes are in complain mode.
Générer un profil — aa-genprof#
# Démarrer la génération de profil pour une application
aa-genprof /usr/local/bin/mon-service
# Dans un autre terminal, utiliser l'application normalement
# Revenir et valider les accès détectés
# Mettre un profil en mode enforce
aa-enforce /etc/apparmor.d/usr.local.bin.mon-service
# Recharger tous les profils
systemctl reload apparmor
Syntaxe d’un profil AppArmor#
# /etc/apparmor.d/usr.sbin.nginx
#include <tunables/global>
/usr/sbin/nginx {
#include <abstractions/base>
#include <abstractions/nameservice>
capability net_bind_service,
capability setuid,
capability setgid,
/usr/sbin/nginx mr,
/etc/nginx/** r,
/var/log/nginx/*.log w,
/var/www/html/** r,
/run/nginx.pid rw,
# Interdire l'accès aux données sensibles
deny /etc/shadow r,
deny /root/** rwx,
}
Les permissions dans AppArmor : r (read), w (write), x (execute), m (mmap), l (link), k (lock).
Diagnostiquer les violations#
# Voir les violations AppArmor dans les logs
grep "apparmor" /var/log/syslog | grep "DENIED"
# Ou avec journald
journalctl -k | grep "apparmor.*DENIED"
SELinux — bases#
SELinux (Security-Enhanced Linux) est le système MAC dominant sur Red Hat/CentOS/Fedora. Plus puissant qu’AppArmor, il est aussi plus complexe à administrer.
Contextes de sécurité#
Chaque processus et fichier possède un contexte SELinux de la forme :
utilisateur:rôle:type:niveau
# Voir le contexte d'un processus
ps -eZ | grep httpd
system_u:system_r:httpd_t:s0 1234 ? 00:00:01 httpd
# Voir le contexte d'un fichier
ls -Z /var/www/html/index.html
system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
Modes SELinux#
# Voir le mode courant
getenforce
# Enforcing
# Informations détaillées
sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux mount point: /sys/fs/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
# Passer temporairement en mode permissif (diagnostic)
setenforce 0
# Repasser en enforcing
setenforce 1
# Configuration persistante : /etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted
Diagnostiquer et corriger avec audit2allow#
# Voir les refus SELinux
grep "avc: denied" /var/log/audit/audit.log | tail -20
# Générer une règle de politique à partir des refus
grep "avc: denied" /var/log/audit/audit.log | audit2allow -M mon_module
semodule -i mon_module.pp
Note
audit2allow est utile pour débloquer des applications légitimes, mais il ne faut pas l’utiliser aveuglément. Analyser chaque refus avant de l’autoriser ; parfois la solution correcte est de repositionner le contexte du fichier avec restorecon, pas de créer une exception.
Namespaces et isolation#
Les namespaces Linux permettent d’isoler différents aspects de l’environnement d’un processus.
Types de namespaces#
Namespace |
Flag |
Isole |
|---|---|---|
UTS |
|
Nom d’hôte et nom de domaine |
PID |
|
Arbre des processus |
NET |
|
Interfaces réseau, routes, ports |
MNT |
|
Points de montage |
USER |
|
UIDs/GIDs (mappage utilisateur) |
IPC |
|
Queues de messages, sémaphores, mémoire partagée |
Cgroup |
|
Vue de la hiérarchie cgroups |
Time |
|
Horloges système (noyau ≥ 5.6) |
nsenter — rejoindre un namespace existant#
# Rejoindre le namespace réseau d'un conteneur Docker
PID=$(docker inspect -f '{{.State.Pid}}' mon_conteneur)
nsenter -t $PID -n ip addr
cgroups v2#
Les cgroups (control groups) permettent de limiter, surveiller et prioriser l’utilisation des ressources par les processus.
Hiérarchie cgroups v2#
Sous cgroups v2, tous les contrôleurs partagent une hiérarchie unifiée montée sur /sys/fs/cgroup.
# Voir la hiérarchie
ls /sys/fs/cgroup/system.slice/nginx.service/
cgroup.controllers cpu.max cpu.stat io.max memory.current
memory.max memory.stat pids.current pids.max
Contrôleurs principaux#
Contrôleur |
Ressource contrôlée |
|---|---|
|
Temps CPU (quota, poids) |
|
Mémoire RAM et swap |
|
Débit disque (lecture/écriture) |
|
Nombre maximum de processus |
|
Affinité CPU et mémoire NUMA |
Limites via systemd#
# /etc/systemd/system/mon-service.service (ou override)
[Service]
# Limiter la mémoire RAM à 512 Mo
MemoryMax=512M
# Limiter le swap à 128 Mo
MemorySwapMax=128M
# Limiter à 50% d'un cœur CPU
CPUQuota=50%
# Limiter les processus fils
TasksMax=100
# Priorité IO (0 à 100, plus bas = plus prioritaire)
IOWeight=50
# Appliquer les changements
systemctl daemon-reload
systemctl restart mon-service
# Vérifier les limites actives
systemctl show mon-service | grep -E "Memory|CPU|Tasks|IO"
auditd#
Le démon auditd est le sous-système d’audit du noyau Linux. Il enregistre les événements système dans /var/log/audit/audit.log avec un niveau de détail très élevé.
Configuration des règles#
# Surveiller les modifications de /etc/passwd
auditctl -w /etc/passwd -p wa -k passwd_changes
# Surveiller les appels système execve de l'utilisateur 1001
auditctl -a always,exit -F arch=b64 -S execve -F uid=1001 -k user_cmds
# Surveiller l'utilisation de sudo
auditctl -w /usr/bin/sudo -p x -k sudo_use
# Lister les règles actives
auditctl -l
# Règles persistantes
# /etc/audit/rules.d/audit.rules
Rechercher dans les logs — ausearch#
# Rechercher par clé
ausearch -k passwd_changes
# Rechercher par utilisateur
ausearch -ua alice --start today
# Rechercher les connexions échouées
ausearch -m USER_LOGIN --success no
# Rechercher les commandes sudo des dernières 24h
ausearch -k sudo_use --start yesterday --end now
Rapports — aureport#
# Rapport général
aureport --summary
# Rapport des authentifications
aureport --auth
# Rapport des commandes exécutées
aureport --executable
# Rapport des accès aux fichiers
aureport --file
# Rapport des anomalies
aureport --anomaly
Exemple de sortie ausearch#
----
time->Mon Mar 24 10:15:32 2026
type=SYSCALL msg=audit(1711274132.456:1234): arch=c000003e syscall=2
success=yes exit=3 a0=7f... a1=0 a2=1b6 a3=...
items=1 ppid=5432 pid=5433 auid=1000 uid=0 gid=0 euid=0
key="passwd_changes"
type=PATH msg=audit(1711274132.456:1234): item=0
name="/etc/passwd" inode=131073 dev=08:01 mode=0100644
chroot et jails#
Principe du chroot#
chroot change le répertoire racine apparent d’un processus. Le processus ne peut plus accéder aux fichiers en dehors de la nouvelle racine.
# Créer un environnement chroot minimal
mkdir -p /srv/jail/{bin,lib,lib64,usr/lib}
# Copier les binaires nécessaires
cp /bin/bash /srv/jail/bin/
ldd /bin/bash # Voir les dépendances
cp /lib/x86_64-linux-gnu/libtinfo.so.6 /srv/jail/lib/
cp /lib/x86_64-linux-gnu/libc.so.6 /srv/jail/lib/
cp /lib64/ld-linux-x86-64.so.2 /srv/jail/lib64/
# Entrer dans le chroot
chroot /srv/jail /bin/bash
Limites du chroot#
Le chroot présente plusieurs limitations importantes :
Un processus avec
CAP_SYS_CHROOTpeut s’en échapperLes descripteurs de fichiers ouverts avant le chroot restent valides
Les sockets réseau restent accessibles
Pas d’isolation des processus (on voit toujours le
/procparent si monté)
Avertissement
chroot seul n’est pas une solution de sécurité suffisante. Il doit être combiné avec d’autres mécanismes (suppression des capabilities, namespaces, seccomp) pour offrir un confinement réel. Les conteneurs modernes (Docker, Podman) combinent chroot + namespaces + cgroups + seccomp + AppArmor/SELinux.
Différence avec les conteneurs#
Mécanisme |
chroot seul |
Conteneur (Docker) |
|---|---|---|
Système de fichiers isolé |
Oui |
Oui (OverlayFS) |
Namespaces PID/NET/UTS |
Non |
Oui |
cgroups (limites ressources) |
Non |
Oui |
Profil seccomp |
Non |
Oui (défaut) |
AppArmor/SELinux |
Non |
Oui (optionnel) |
Checklist de durcissement#
CIS Benchmark#
Le CIS (Center for Internet Security) publie des guides de durcissement détaillés pour chaque distribution. Les recommandations sont classées en deux niveaux :
Niveau 1 : mesures de base, impact minimal sur les fonctionnalités
Niveau 2 : durcissement avancé, peut affecter certaines fonctionnalités
Exemples de recommandations CIS :
Désactiver les protocoles réseau inutilisés (DCCP, SCTP, RDS)
Configurer sysctl pour le durcissement réseau (
net.ipv4.tcp_syncookies,net.ipv4.conf.all.rp_filter)Configurer PAM pour la politique de mots de passe
Activer et configurer auditd
S’assurer que
/tmpest monté avecnoexec,nosuid,nodev
Lynis — audit automatisé#
# Installer lynis
apt install lynis
# Lancer un audit complet
lynis audit system
# Résultat : score sur 100 par domaine
# Rapport dans /var/log/lynis.log
Lynis security scan details:
Hardening index : 67 [############# ]
Tests performed : 243
Plugins enabled : 2
Components:
- Firewall [ENABLED]
- Malware scanner [NOT FOUND]
Scan mode:
Normal [ ] Forensics [ ] Integration [ ] Pentest [V] (running non-privileged)
Lynis modules:
- Compliance status [UNKNOWN]
- Security audit [ENABLED]
- Vulnerability scan [ENABLED]
AIDE — détection d’intrusion par intégrité de fichiers#
# Installer AIDE
apt install aide
# Initialiser la base de données (après durcissement initial)
aideinit
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Vérifier l'intégrité
aide --check
# Planifier via cron
echo "0 3 * * * root /usr/bin/aide --check | mail -s 'AIDE check' admin@example.com" \
>> /etc/crontab
Démonstrations Python#
Analyse d’une sortie aa-status simulée#
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Sortie aa-status simulée parsée en dictionnaire Python
aa_status = {
"enforce": [
"nginx", "sshd", "mysqld", "cupsd", "evince",
"firefox", "thunderbird", "libreoffice",
"NetworkManager", "ntpd", "chronyd", "named",
"dovecot", "postfix", "rsyslogd", "apache2",
"snapd", "lxc-container", "docker-default",
"man_filter", "man_groff", "pidgin",
"totem", "totem-open-location", "totem-pl-parser",
"usr.lib.snapd.snap-confine",
"usr.sbin.tcpdump", "usr.sbin.traceroute",
"usr.bin.ping", "usr.bin.python3.8",
"usr.bin.ruby", "usr.sbin.avahi-daemon",
"usr.sbin.cups-browsed", "usr.sbin.dnsmasq",
"usr.sbin.haveged", "usr.sbin.ippusbxd",
"usr.sbin.lightdm", "usr.sbin.mdnsd",
"usr.sbin.ntpd", "usr.sbin.ntp",
"usr.sbin.unbound", "usr.sbin.useradd",
"usr.sbin.userdel", "usr.sbin.usermod",
"usr.sbin.passwd", "xtables-legacy-multi",
"kerberos5-admin-server", "kerberos5-kdc",
],
"complain": [
"mozilla-firefox", "opera-stable", "chromium-browser",
"vlc", "gimp-2.10", "inkscape", "blender",
"steam", "code", "signal-desktop",
"usr.bin.perl", "usr.bin.python2",
],
"unconfined": [
"cron", "bash", "python3", "perl", "ruby",
"java", "node", "php", "ruby2.7",
"samba", "vsftpd",
],
}
comptes = {k: len(v) for k, v in aa_status.items()}
total = sum(comptes.values())
labels = list(comptes.keys())
valeurs = list(comptes.values())
couleurs = ["#4c9be8", "#f0c040", "#e84c4c"]
explode = [0.04, 0.04, 0.08]
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# Camembert
wedges, texts, autotexts = axes[0].pie(
valeurs,
labels=[f"{l.capitalize()}\n({v})" for l, v in zip(labels, valeurs)],
colors=couleurs,
explode=explode,
autopct="%1.1f%%",
startangle=120,
textprops={"fontsize": 10},
)
for at in autotexts:
at.set_fontweight("bold")
axes[0].set_title(f"AppArmor — répartition des profils\n(total : {total} profils)", fontsize=11)
# Barres horizontales avec exemples
y_pos = range(len(labels))
bars = axes[1].barh(
[l.capitalize() for l in labels],
valeurs,
color=couleurs,
edgecolor="white",
linewidth=1.2,
height=0.5,
)
axes[1].set_xlabel("Nombre de profils")
axes[1].set_title("Répartition par mode — détail", fontsize=11)
for bar, val in zip(bars, valeurs):
axes[1].text(bar.get_width() + 0.3, bar.get_y() + bar.get_height() / 2,
str(val), va="center", fontsize=10, fontweight="bold")
axes[1].set_xlim(0, max(valeurs) * 1.15)
# Annotations exemples
exemples = {
"enforce": "ex: nginx, sshd, mysqld…",
"complain": "ex: firefox, vlc, gimp…",
"unconfined": "ex: cron, bash, java…",
}
for i, (label, ex) in enumerate(exemples.items()):
axes[1].text(0.5, i, ex, va="center", fontsize=8, color="#555555", style="italic")
plt.suptitle("État AppArmor — analyse des profils de sécurité", fontsize=13, y=1.02)
plt.show()
Visualisation de la hiérarchie cgroups v2#
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Structure hiérarchique cgroups v2 simulée
# (nœud, parent, ressources)
noeuds = [
("/", None, "cpu, memory, io, pids"),
("system.slice", "/", "cpu, memory, io"),
("user.slice", "/", "cpu, memory"),
("machine.slice", "/", "cpu, memory, io"),
("nginx.service", "system.slice", "CPUQuota=200%\nMemoryMax=512M"),
("postgresql.service", "system.slice", "CPUQuota=400%\nMemoryMax=2G"),
("sshd.service", "system.slice", "TasksMax=100"),
("user-1000.slice", "user.slice", "MemoryMax=4G"),
("user-1001.slice", "user.slice", "MemoryMax=2G"),
("docker.service", "machine.slice", "MemoryMax=8G"),
]
# Positions manuelles pour lisibilité
positions = {
"/": (0.50, 0.92),
"system.slice": (0.22, 0.72),
"user.slice": (0.50, 0.72),
"machine.slice": (0.78, 0.72),
"nginx.service": (0.08, 0.45),
"postgresql.service": (0.22, 0.45),
"sshd.service": (0.38, 0.45),
"user-1000.slice": (0.50, 0.45),
"user-1001.slice": (0.64, 0.45),
"docker.service": (0.78, 0.45),
}
couleurs_noeud = {
"/": "#2c3e50",
"system.slice": "#2980b9",
"user.slice": "#27ae60",
"machine.slice": "#8e44ad",
"nginx.service": "#3498db",
"postgresql.service": "#3498db",
"sshd.service": "#3498db",
"user-1000.slice": "#2ecc71",
"user-1001.slice": "#2ecc71",
"docker.service": "#9b59b6",
}
fig, ax = plt.subplots(figsize=(14, 7))
ax.set_xlim(0, 1)
ax.set_ylim(0.3, 1.05)
ax.axis("off")
# Arêtes
for nom, parent, _ in noeuds:
if parent and parent in positions:
x1, y1 = positions[parent]
x2, y2 = positions[nom]
ax.plot([x1, x2], [y1 - 0.03, y2 + 0.04],
color="#aaaaaa", linewidth=1.5, zorder=1)
# Nœuds
for nom, parent, ressources in noeuds:
x, y = positions[nom]
couleur = couleurs_noeud.get(nom, "#3498db")
# Boîte
largeur = 0.14 if nom not in ["/"] else 0.08
hauteur = 0.065
rect = mpatches.FancyBboxPatch(
(x - largeur / 2, y - hauteur / 2),
largeur, hauteur,
boxstyle="round,pad=0.01",
facecolor=couleur, edgecolor="white",
linewidth=1.5, zorder=2
)
ax.add_patch(rect)
ax.text(x, y + 0.005, nom, ha="center", va="center",
fontsize=8 if nom != "/" else 10,
color="white", fontweight="bold", zorder=3)
# Annotations ressources sous les feuilles
if parent is not None and not any(p == nom for _, p, _ in noeuds):
ax.text(x, y - 0.065, ressources,
ha="center", va="top", fontsize=6.5,
color="#444444", style="italic",
multialignment="center")
# Légende
legende = [
mpatches.Patch(color="#2c3e50", label="Racine cgroup"),
mpatches.Patch(color="#2980b9", label="system.slice (services systemd)"),
mpatches.Patch(color="#27ae60", label="user.slice (sessions utilisateurs)"),
mpatches.Patch(color="#8e44ad", label="machine.slice (conteneurs/VMs)"),
]
ax.legend(handles=legende, loc="lower center", ncol=4, fontsize=8,
bbox_to_anchor=(0.5, 0.0))
ax.set_title("Hiérarchie cgroups v2 — organisation des groupes de contrôle",
fontsize=13, pad=10)
plt.show()
Rapport Lynis simulé — radar par domaine#
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Scores lynis simulés par domaine (0-100)
domaines = [
"Démarrage/init",
"Noyau",
"Mémoire",
"Authentification",
"Réseau",
"Stockage",
"Services",
"Journalisation",
"Sécurité fichiers",
"Hardening logiciels",
]
scores_avant = [55, 62, 70, 48, 65, 72, 58, 45, 60, 40]
scores_apres = [80, 78, 82, 85, 88, 79, 75, 82, 87, 78]
N = len(domaines)
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles_closed = angles + [angles[0]]
scores_avant_closed = scores_avant + [scores_avant[0]]
scores_apres_closed = scores_apres + [scores_apres[0]]
fig, ax = plt.subplots(figsize=(9, 9), subplot_kw=dict(polar=True))
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.plot(angles_closed, scores_avant_closed,
"o-", linewidth=2, color="#e84c4c", label="Avant durcissement", markersize=5)
ax.fill(angles_closed, scores_avant_closed, alpha=0.20, color="#e84c4c")
ax.plot(angles_closed, scores_apres_closed,
"o-", linewidth=2, color="#4c9be8", label="Après durcissement", markersize=5)
ax.fill(angles_closed, scores_apres_closed, alpha=0.20, color="#4c9be8")
ax.set_xticks(angles)
ax.set_xticklabels(domaines, size=9)
ax.set_ylim(0, 100)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(["20", "40", "60", "80", "100"], size=8, color="grey")
ax.grid(color="grey", linestyle="--", linewidth=0.5, alpha=0.7)
ax.set_title("Rapport Lynis — scores par domaine\n(simulation avant/après durcissement)",
size=13, pad=25)
ax.legend(loc="upper right", bbox_to_anchor=(1.3, 1.1), fontsize=10)
# Annotations score moyen
moy_avant = np.mean(scores_avant)
moy_apres = np.mean(scores_apres)
fig.text(0.5, 0.02,
f"Score moyen : {moy_avant:.0f}/100 → {moy_apres:.0f}/100 (+{moy_apres - moy_avant:.0f} points)",
ha="center", fontsize=11, color="#333333",
bbox=dict(boxstyle="round,pad=0.4", facecolor="#f0f4ff", edgecolor="#cccccc"))
plt.show()
Matrice risque/effort des mesures de durcissement#
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Mesures de durcissement : (nom, effort 1-5, réduction risque 1-5, priorité)
mesures = [
("Désactiver services\ninutiles", 1.5, 4.0, "haute"),
("Mettre à jour\nle système", 1.0, 5.0, "haute"),
("Configurer le\npare-feu", 2.0, 4.5, "haute"),
("SSH : clés uniquement", 1.5, 4.8, "haute"),
("AppArmor enforce", 3.0, 3.5, "haute"),
("SELinux enforcing", 4.5, 4.5, "haute"),
("auditd + règles", 2.5, 3.0, "moyenne"),
("chattr +i fichiers\ncritiques", 1.5, 2.5, "moyenne"),
("umask 027", 1.0, 1.5, "basse"),
("Capabilities\nau lieu de SUID", 3.5, 3.0, "moyenne"),
("ACL sur partages", 2.0, 2.0, "basse"),
("LUKS partitions\nsensibles", 3.0, 4.0, "haute"),
("2FA sur SSH", 2.5, 4.2, "haute"),
("Fail2ban", 1.5, 3.5, "haute"),
("AIDE (intégrité\nfichiers)", 3.0, 3.8, "moyenne"),
("Namespaces\nisolation services", 4.0, 3.5, "moyenne"),
("cgroups v2 limites", 2.5, 2.5, "basse"),
("Rotation clés\nSSH régulière", 2.0, 2.8, "moyenne"),
]
couleurs_priorite = {"haute": "#e84c4c", "moyenne": "#f0a040", "basse": "#4c9be8"}
tailles_priorite = {"haute": 180, "moyenne": 120, "basse": 80}
fig, ax = plt.subplots(figsize=(12, 7))
for nom, effort, risque, prio in mesures:
ax.scatter(effort, risque,
s=tailles_priorite[prio],
color=couleurs_priorite[prio],
alpha=0.85,
edgecolors="white",
linewidth=1.2,
zorder=3)
ax.annotate(nom, (effort, risque),
textcoords="offset points",
xytext=(7, 3),
fontsize=7.5,
color="#333333")
# Quadrants
ax.axvline(x=2.5, color="grey", linestyle="--", linewidth=0.8, alpha=0.6)
ax.axhline(y=3.0, color="grey", linestyle="--", linewidth=0.8, alpha=0.6)
ax.text(1.1, 4.7, "Wins rapides\n(effort faible, impact élevé)",
fontsize=8.5, color="#2e7d32", style="italic",
bbox=dict(boxstyle="round,pad=0.3", facecolor="#e8f5e9", alpha=0.7))
ax.text(3.2, 4.7, "Investissements\nstratégiques",
fontsize=8.5, color="#c62828", style="italic",
bbox=dict(boxstyle="round,pad=0.3", facecolor="#ffebee", alpha=0.7))
ax.text(1.1, 1.2, "Mesures\ncomplémentaires",
fontsize=8.5, color="#1565c0", style="italic",
bbox=dict(boxstyle="round,pad=0.3", facecolor="#e3f2fd", alpha=0.7))
ax.text(3.2, 1.2, "Effort élevé\nimpact limité",
fontsize=8.5, color="#777777", style="italic",
bbox=dict(boxstyle="round,pad=0.3", facecolor="#f5f5f5", alpha=0.7))
ax.set_xlabel("Effort d'implémentation (1 = minimal, 5 = élevé)", fontsize=10)
ax.set_ylabel("Réduction du risque (1 = faible, 5 = élevée)", fontsize=10)
ax.set_title("Matrice risque/effort — mesures de durcissement système", fontsize=13)
ax.set_xlim(0.5, 5.5)
ax.set_ylim(0.5, 5.5)
import matplotlib.patches as mpatches
legende = [
mpatches.Patch(color="#e84c4c", label="Priorité haute"),
mpatches.Patch(color="#f0a040", label="Priorité moyenne"),
mpatches.Patch(color="#4c9be8", label="Priorité basse"),
]
ax.legend(handles=legende, fontsize=9, loc="lower right")
plt.show()
Résumé#
Ce chapitre a présenté une approche structurée du durcissement système :
Principes directeurs
La surface d’attaque doit être réduite à ce qui est strictement nécessaire
Le principe du moindre privilège s’applique à chaque composant
La défense en profondeur superpose des couches de contrôle indépendantes
Réduction de la surface
Désactiver et masquer les services inutiles avec systemd
Supprimer les paquets non nécessaires
Lier les services à
127.0.0.1quand l’accès externe n’est pas requis
MAC : AppArmor et SELinux
AppArmor confine les programmes par profil (enforce/complain) basé sur les chemins
SELinux utilise des contextes de sécurité sur tous les objets du système
Les deux systèmes bloquent les actions non autorisées même pour root
Isolation
Les namespaces Linux isolent différentes vues du système (PID, réseau, utilisateurs)
Les cgroups v2 limitent les ressources avec des directives systemd (
MemoryMax,CPUQuota)Le chroot est insuffisant seul ; les conteneurs combinent chroot + namespaces + cgroups
Surveillance
auditdoffre une traçabilité granulaire des appels système et accès aux fichiersausearchetaureportpermettent d’exploiter les logs d’audit
Outils d’évaluation
Lynis fournit un score de durcissement par domaine avec des recommandations concrètes
AIDE détecte les modifications non autorisées de fichiers par vérification d’intégrité
Les CIS Benchmarks constituent la référence pour des configurations sécurisées standardisées