Sommaire
Introduction
Un nouveau serveur privé virtuel doté d’une adresse IP publique est analysé dans les minutes qui suivent son démarrage. Des bots automatisés essaient les identifiants par défaut sur SSH, recherchent les bases de données et panneaux d’administration exposés et lancent des exploits contre tous les frameworks web populaires. Rien de tout cela n’est ciblé, et c’est justement pourquoi cela fonctionne : il suffit à l’attaquant d’un seul serveur qui a négligé les bases. Ce guide détaille le durcissement d’un VPS Linux, de la première connexion à la vérification continue, avec les commandes de chaque étape et les outils pour contrôler votre travail.
Première connexion : mise à jour, inventaire et horloge
Avant tout durcissement, mettez le système à jour et notez son état. Les images des fournisseurs ont souvent plusieurs semaines, et l’inventaire réalisé maintenant servira de référence pendant un incident.
sudo apt update && sudo apt full-upgrade -y
sudo apt autoremove --purge -y
[ -f /var/run/reboot-required ] && sudo reboot
# RHEL, Rocky, Alma
sudo dnf upgrade --refresh -y
sudo needs-restarting -r || sudo rebootsudo hostnamectl set-hostname web-01.example.com
sudo timedatectl set-timezone UTC
timedatectl status # "System clock synchronized: yes"
sudo ss -tulpn # every listening socket and its process
systemctl list-units --type=service --state=running
last -n 20; sudo lastb -n 20 # recent and failed logins
cat /etc/os-release; uname -rUtilisez UTC sur les serveurs. Les journaux de machines situées dans différentes régions ne s’alignent lors d’une enquête que s’ils partagent la même horloge, et l’heure exacte compte aussi pour TLS, Kerberos et les codes TOTP. Ubuntu et Debian utilisent systemd-timesyncd par défaut ; chrony est un meilleur choix pour les serveurs qui exigent plus de précision, et c’est le choix par défaut sur RHEL.
Utilisateurs, sudo et politique de mots de passe
Ne travaillez jamais en root. Créez un administrateur nommé pour chaque personne, donnez-lui sudo via un groupe et verrouillez le mot de passe root, pour que le seul accès à root passe par un appel sudo audité.
sudo adduser alice # Debian and Ubuntu
sudo usermod -aG sudo alice # use "wheel" on RHEL
sudo groupadd sshusers && sudo usermod -aG sshusers alice
sudo passwd -l root # lock the root password
sudo chage -l alice # check password ageingRenforcez sudo avec un fichier dédié plutôt qu’en modifiant le fichier sudoers principal. Modifiez toujours via visudo, qui refuse d’enregistrer un fichier comportant des erreurs de syntaxe susceptibles de priver tous les administrateurs de sudo.
Defaults use_pty
Defaults logfile="/var/log/sudo.log"
Defaults log_input, log_output
Defaults timestamp_timeout=5
Defaults passwd_tries=3
Defaults !visiblepwL’option use_pty empêche un processus en arrière-plan d’injecter des frappes dans votre terminal après la fin de sudo. La journalisation des entrées et sorties enregistre les sessions sudo complètes dans /var/log/sudo-io, qu’on peut rejouer avec sudoreplay pendant une enquête. Même avec un SSH par clés uniquement, les mots de passe locaux protègent encore sudo : fixez donc une politique raisonnable.
# /etc/security/pwquality.conf (apt install libpam-pwquality)
minlen = 14
minclass = 3
maxrepeat = 3
dictcheck = 1
enforce_for_root
# /etc/login.defs
PASS_MAX_DAYS 365
PASS_MIN_DAYS 1
PASS_WARN_AGE 14
UMASK 027Trouvez et supprimez tout ce qui ne devrait pas pouvoir se connecter : comptes inutilisés, comptes à mot de passe vide et tout compte autre que root ayant l’UID 0.
awk -F: '$3 == 0 {print $1}' /etc/passwd # only "root" expected
sudo awk -F: '$2 == "" {print $1}' /etc/shadow # empty passwords
awk -F: '$7 !~ /(nologin|false)$/ {print $1, $7}' /etc/passwd
sudo usermod -s /usr/sbin/nologin olduser && sudo usermod -L olduserSSH : clés uniquement, pas de root, cryptographie moderne
SSH est la porte d’entrée de presque tous les VPS, et l’authentification par mot de passe est ce que les bots martèlent. Passez à l’authentification par clé, idéalement avec une clé matérielle, puis désactivez complètement les mots de passe.
ssh-keygen -t ed25519 -a 100 -C "alice@laptop"
# or, with a FIDO2 security key such as a YubiKey:
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "alice@yubikey"
ssh-copy-id -i ~/.ssh/id_ed25519.pub alice@203.0.113.10
ssh alice@203.0.113.10 # confirm key login works BEFORE disabling passwordsPlacez vos réglages dans un fichier dédié. Sur Ubuntu et Debian, sshd_config inclut /etc/ssh/sshd_config.d/*.conf au début, et sshd garde la première valeur lue pour chaque option : un fichier nommé 10-hardening.conf l’emporte donc sur le 50-cloud-init.conf que de nombreuses images cloud fournissent avec PasswordAuthentication activé.
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
AllowGroups sshusers
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitUserEnvironment no
LogLevel VERBOSE
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256LogLevel VERBOSE enregistre l’empreinte de la clé utilisée à chaque connexion, ce qui indique quelle clé, et donc quelle personne ou quel système, a ouvert une session. Si vous avez besoin de la redirection TCP pour un bastion ou un tunnel de base de données, activez-la uniquement pour ce groupe avec un bloc Match plutôt que globalement.
sudo sshd -t # syntax check, silent on success
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|kbdinteractive|allowgroups'
sudo systemctl reload ssh # "sshd" on RHEL
# Keep this session open. From a SECOND terminal:
ssh alice@203.0.113.10 # must succeed
ssh -o PubkeyAuthentication=no alice@203.0.113.10 # must be refusedSupprimez les clés d’hôte faibles et les groupes Diffie-Hellman courts. Les clients signaleront un changement de clé d’hôte si vous régénérez les clés : faites-le donc dès le premier jour et communiquez les nouvelles empreintes à votre équipe.
sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
sudo ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
sudo awk '$5 >= 3071' /etc/ssh/moduli | sudo tee /etc/ssh/moduli.safe >/dev/null
sudo mv /etc/ssh/moduli.safe /etc/ssh/moduli
sudo systemctl restart ssh
for k in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$k"; doneL’option la plus solide consiste à retirer complètement SSH de l’Internet public. Placez les administrateurs sur un réseau WireGuard ou Tailscale, liez sshd à cette interface avec ListenAddress et n’autorisez le port 22 que depuis la plage du VPN. Le trafic de force brute n’atteint alors jamais le démon. Vérifiez la configuration finale depuis votre poste avec ssh-audit, qui note l’échange de clés, les chiffrements, les MAC et les clés d’hôte, et signale tout ce qui est faible.
pipx install ssh-audit
ssh-audit 203.0.113.10
ssh-audit -p 2222 203.0.113.10 # if you changed the portPare-feu : tout refuser par défaut
N’autorisez que les ports pour lesquels le serveur existe, refusez tout le reste en entrée et vérifiez que les règles couvrent IPv6 autant qu’IPv4. Utilisez deux niveaux si possible : le pare-feu cloud ou le groupe de sécurité du fournisseur arrête le trafic avant la VM, et le pare-feu de l’hôte vous protège si la règle cloud est mal configurée.
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit 22/tcp comment 'SSH, rate limited'
sudo ufw allow 80,443/tcp comment 'Web'
# admin access from one network only:
# sudo ufw allow from 198.51.100.0/24 to any port 22 proto tcp
grep IPV6 /etc/default/ufw # must be IPV6=yes
sudo ufw enable
sudo ufw status verbose numberedLa règle limit bloque une adresse qui ouvre six connexions ou plus en 30 secondes, ce qui ralentit la force brute sans logiciel supplémentaire. Si vous préférez gérer les règles directement, nftables est le pare-feu moderne du noyau sur toutes les distributions actuelles, et une seule table inet couvre à la fois IPv4 et IPv6.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set ssh_ratelimit {
type ipv4_addr
flags dynamic, timeout
timeout 1m
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state invalid drop
iif "lo" accept
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
tcp dport 22 ct state new add @ssh_ratelimit { ip saddr limit rate over 10/minute } drop
tcp dport 22 accept
tcp dport { 80, 443 } accept
limit rate 5/minute log prefix "nft-drop: " level info
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}Ne bloquez pas la découverte de voisins ICMPv6 ni les messages packet-too-big : IPv6 ne fonctionne pas sans eux. Sur les systèmes de la famille RHEL, firewalld est l’interface prise en charge. Retirez de la zone par défaut les services inutiles, car les images cloud autorisent souvent cockpit et dhcpv6-client d’emblée.
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --permanent --remove-service=dhcpv6-client
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=198.51.100.0/24 service name=ssh accept'
sudo firewall-cmd --permanent --remove-service=ssh # after the rich rule is in place
sudo firewall-cmd --reload && sudo firewall-cmd --list-allLe filtrage en sortie en vaut la peine sur les serveurs à fonction précise. Un serveur web a rarement besoin d’ouvrir des connexions sortantes sur des ports arbitraires, et les bloquer arrête de nombreux reverse shells, mineurs de cryptomonnaie et outils d’exfiltration. Commencez par journaliser le trafic sortant pendant une semaine, puis n’autorisez que DNS, NTP, les miroirs de paquets et les API réellement appelées par l’application.
Protection contre la force brute : Fail2ban ou CrowdSec
Fail2ban surveille les journaux à la recherche d’échecs répétés et bannit l’adresse source dans le pare-feu. Avec un SSH par clés uniquement, les bannissements comptent moins pour la sécurité, mais ils réduisent le volume des journaux et protègent les services qui acceptent encore les mots de passe, comme les connexions web et la messagerie.
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
bantime.increment = true
bantime.maxtime = 1w
backend = systemd
banaction = nftables-multiport
ignoreip = 127.0.0.1/8 ::1 198.51.100.0/24
[sshd]
enabled = true
mode = aggressive
[nginx-http-auth]
enabled = truesudo apt install fail2ban # RHEL: sudo dnf install epel-release fail2ban
sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 192.0.2.44CrowdSec est une alternative moderne. Il analyse les journaux avec des scénarios communautaires, applique ses décisions via des bouncers dans le pare-feu ou le proxy inverse, et partage des signaux d’attaque anonymisés pour bloquer les adresses ayant attaqué d’autres membres avant qu’elles ne vous atteignent. Installez le moteur depuis son dépôt de paquets et ajoutez le bouncer de pare-feu nftables.
curl -s https://install.crowdsec.net | sudo sh # adds the repository; read it first
sudo apt install crowdsec crowdsec-firewall-bouncer-nftables
sudo cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/nginx
sudo cscli metrics
sudo cscli decisions list
sudo cscli alerts listMises à jour de sécurité et redémarrages automatiques
La plupart des serveurs compromis le sont via des vulnérabilités qui avaient déjà un correctif. Installez automatiquement les mises à jour de sécurité et planifiez les redémarrages pour que les nouveaux noyaux et bibliothèques soient réellement utilisés.
sudo apt install unattended-upgrades apt-listchanges needrestart
sudo dpkg-reconfigure -plow unattended-upgrades
# /etc/apt/apt.conf.d/52unattended-upgrades-local
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::Mail "ops@example.com";
Unattended-Upgrade::MailReport "on-change";
sudo unattended-upgrade --dry-run --debug
systemctl list-timers 'apt-daily*'sudo dnf install dnf-automatic
# /etc/dnf/automatic.conf
[commands]
upgrade_type = security
apply_updates = yes
reboot = when-needed
sudo systemctl enable --now dnf-automatic.timerneedrestart indique quels services utilisent encore d’anciennes versions de bibliothèques après une mise à jour et peut les redémarrer automatiquement. Pour les serveurs qui ne peuvent pas redémarrer souvent, Ubuntu Pro Livepatch (gratuit pour un petit nombre de machines) et le service équivalent kpatch sur RHEL appliquent en mémoire les correctifs critiques du noyau. Le livepatching rend les redémarrages moins urgents, mais ne les rend pas inutiles.
Durcissement du noyau et de la pile réseau
Les paramètres du noyau définis par sysctl ferment les techniques d’usurpation, de redirection et de fuite d’informations, et rendent l’élévation locale de privilèges plus difficile. Placez-les dans leur propre fichier pour que les mises à jour de paquets ne les écrasent jamais.
# Network: anti-spoofing, no redirects, no source routing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_rfc1337 = 1
# Kernel: hide pointers and logs, restrict debugging interfaces
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
kernel.unprivileged_bpf_disabled = 1
net.core.bpf_jit_harden = 2
kernel.yama.ptrace_scope = 2
kernel.kexec_load_disabled = 1
kernel.sysrq = 0
kernel.perf_event_paranoid = 3
kernel.randomize_va_space = 2
# File system: block classic /tmp race and link attacks, no setuid core dumps
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
fs.suid_dumpable = 0sudo sysctl --system
sysctl kernel.kptr_restrict net.ipv4.conf.all.rp_filter kernel.yama.ptrace_scope- Laissez net.ipv4.ip_forward à 0 sauf si le serveur route du trafic. Docker, Kubernetes et les passerelles VPN l’activent, et c’est normal pour eux.
- Ne mettez pas net.ipv6.conf.all.accept_ra à 0 sans réfléchir. Plusieurs fournisseurs configurent IPv6 via les annonces de routeur, et le serveur perdrait son adresse IPv6 au prochain redémarrage.
- kernel.yama.ptrace_scope = 2 empêche les utilisateurs normaux d’attacher des débogueurs aux processus. Utilisez plutôt 1 si des développeurs déboguent sur la machine, et jamais 3 sauf si vous êtes sûr de ne pas en avoir besoin avant le prochain redémarrage, car 3 ne peut pas être abaissé à l’exécution.
- kernel.kexec_load_disabled est irréversible jusqu’au redémarrage, et c’est tout l’intérêt : un attaquant root ne peut pas charger un noyau de remplacement.
Bloquez les modules du noyau pour les systèmes de fichiers et protocoles réseau que le serveur n’utilisera jamais. Plusieurs failles récentes d’élévation locale de privilèges se trouvaient dans des modules rarement utilisés, chargés automatiquement à la demande. Utiliser install avec /bin/false empêche le chargement même lorsqu’un programme demande explicitement le module, alors que blacklist n’arrête que le chargement automatique.
install cramfs /bin/false
install freevxfs /bin/false
install hfs /bin/false
install hfsplus /bin/false
install jffs2 /bin/false
install udf /bin/false
install usb-storage /bin/false
install dccp /bin/false
install sctp /bin/false
install rds /bin/false
install tipc /bin/false
install firewire-core /bin/falseMontez les répertoires temporaires partagés de sorte que rien ne puisse y fonctionner en setuid ou comme nœud de périphérique. Ajouter noexec à /tmp neutralise aussi de nombreux droppers courants, mais testez d’abord : certains installateurs et scripts de paquets s’exécutent depuis /tmp. /dev/shm est plus sûr à verrouiller, car peu de programmes légitimes s’y exécutent.
tmpfs /dev/shm tmpfs defaults,nodev,nosuid,noexec 0 0
tmpfs /tmp tmpfs defaults,nodev,nosuid,size=1G 0 0
sudo systemctl daemon-reload && sudo mount -o remount /dev/shm
findmnt /dev/shm /tmp
# Disable core dumps, which can contain keys and passwords
echo '* hard core 0' | sudo tee /etc/security/limits.d/10-nocore.conf
sudo mkdir -p /etc/systemd/coredump.conf.d
printf '[Coredump]\nStorage=none\nProcessSizeMax=0\n' | sudo tee /etc/systemd/coredump.conf.d/disable.confRéduire la surface d’attaque
Chaque service à l’écoute est quelque chose à corriger et à exploiter. Reprenez la sortie de ss de votre première connexion et justifiez chaque ligne. Tout ce dont seul le serveur a besoin, comme une base de données ou un cache, doit écouter sur 127.0.0.1 ou un socket Unix, jamais sur 0.0.0.0.
sudo ss -tulpn
systemctl list-unit-files --type=service --state=enabled
sudo systemctl disable --now avahi-daemon cups rpcbind 2>/dev/null
sudo apt purge -y telnet rsh-client talk xinetd 2>/dev/null
# Bind local services to loopback
# PostgreSQL postgresql.conf listen_addresses = 'localhost'
# MySQL mysqld.cnf bind-address = 127.0.0.1
# Redis redis.conf bind 127.0.0.1 -::1 and protected-mode yes
# MongoDB mongod.conf net.bindIp: 127.0.0.1# setuid and setgid binaries: compare against a known-good list
sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -printf '%M %u %p\n' 2>/dev/null
# File capabilities, a quieter alternative to setuid
sudo getcap -r / 2>/dev/null
# World-writable directories without the sticky bit, and world-writable files
sudo find / -xdev -type d -perm -0002 ! -perm -1000 2>/dev/null
sudo find / -xdev -type f -perm -0002 ! -path '/proc/*' 2>/dev/null
# Files with no owner, often left behind by removed users or attackers
sudo find / -xdev \( -nouser -o -nogroup \) 2>/dev/nullsystemd peut isoler n’importe quel service avec quelques directives et note le niveau d’exposition de chaque unité. Exécutez systemd-analyze security pour voir la liste, puis ajoutez un fichier dédié pour vos propres services applicatifs. Commencez strictement et assouplissez un réglage à la fois si le service échoue, en consultant journalctl pour en connaître la raison.
[Service]
User=myapp
Group=myapp
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/myapp /var/log/myapp
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
CapabilityBoundingSet=
AmbientCapabilities=
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
UMask=0077sudo systemctl daemon-reload && sudo systemctl restart myapp
systemd-analyze security myapp.service # aim for an exposure score under 3
systemd-analyze security # every unit, worst firstAjoutez MemoryDenyWriteExecute=yes pour les services compilés, mais pas pour Node.js, Java et les autres environnements à compilateur JIT, qui ont besoin de mémoire inscriptible et exécutable. Si le service doit se lier au port 80 ou 443 sans être root, accordez exactement cela avec AmbientCapabilities=CAP_NET_BIND_SERVICE et la même valeur dans CapabilityBoundingSet.
Contrôle d’accès obligatoire : AppArmor et SELinux
AppArmor sur Ubuntu et Debian, et SELinux sur RHEL, confinent les processus aux fichiers et capacités autorisés par leur politique, même lorsqu’ils tournent en root. C’est pourquoi de nombreuses compromissions d’applications web restent limitées au serveur web. Gardez-les activés et en mode d’application ; l’erreur de durcissement la plus courante sur RHEL reste de désactiver SELinux pour faire disparaître une erreur.
sudo aa-status # profiles in enforce and complain mode
sudo apt install apparmor-utils apparmor-profiles apparmor-profiles-extra
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx # if the profile exists
sudo aa-genprof /usr/local/bin/myapp # build a profile interactively
sudo aa-logprof # refine it from logged denials
sudo journalctl -k | grep -i apparmor | tailgetenforce && sestatus # expect Enforcing, targeted policy
sudo ausearch -m AVC -ts recent | audit2why # explain recent denials
# Fix the context instead of disabling SELinux
sudo semanage fcontext -a -t httpd_sys_content_t '/srv/www(/.*)?'
sudo restorecon -Rv /srv/www
sudo setsebool -P httpd_can_network_connect on
sudo semanage port -a -t ssh_port_t -p tcp 2222Journalisation et audit
Lorsqu’un problème survient, les questions sont toujours les mêmes : qui s’est connecté, qu’a-t-il exécuté, qu’est-ce qui a changé. Assurez-vous que le serveur enregistre ces réponses, les conserve assez longtemps et en envoie une copie à un endroit qu’un attaquant root ne peut pas modifier.
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=2G
MaxRetentionSec=90day
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usageLe système d’audit Linux enregistre dans le noyau les appels système importants pour la sécurité, en dessous de ce qu’un rootkit en espace utilisateur peut facilement masquer. Un ensemble de règles ciblé couvrant les fichiers d’identité, la configuration de SSH et de sudo, les commandes à privilèges et le chargement de modules du noyau offre l’essentiel de la valeur d’enquête sans saturer le disque. Les règles communautaires de Florian Roth et les exemples auditd des CIS Benchmarks sont de bons points de départ.
-D
-b 8192
-f 1
# Identity and authentication
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd
-w /etc/ssh/sshd_config.d/ -p wa -k sshd
-w /etc/pam.d/ -p wa -k pam
# Persistence locations
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
-w /etc/systemd/system/ -p wa -k systemd
-w /etc/ld.so.preload -p wa -k preload
# Commands run as root by a real logged-in user
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_cmd
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_cmd
# Kernel modules
-a always,exit -F arch=b64 -S init_module,finit_module,delete_module -k modules
# Make the rules immutable until reboot (keep last)
-e 2sudo apt install auditd audispd-plugins # RHEL: installed by default
sudo augenrules --load
sudo auditctl -s # "enabled 2" means locked
sudo auditctl -l
sudo ausearch -k identity -i --start today
sudo ausearch -k root_cmd -i --start recent
sudo aureport --auth --summary
sudo aureport -x --summary # most-run executablesLes journaux d’un serveur ne sont fiables que jusqu’à sa compromission. Transmettez-les quasiment en temps réel vers un SIEM ou un hôte de journaux dédié via TLS. L’agent Wazuh, Elastic Agent, Splunk Universal Forwarder et Vector fonctionnent bien ; rsyslog suffit si vous n’avez besoin que d’une copie distante.
global(DefaultNetstreamDriverCAFile="/etc/ssl/certs/log-ca.pem")
action(
type="omfwd"
target="logs.example.com" port="6514" protocol="tcp"
StreamDriver="gtls" StreamDriverMode="1"
StreamDriverAuthMode="x509/name"
StreamDriverPermittedPeers="logs.example.com"
queue.type="LinkedList" queue.filename="fwd" queue.saveOnShutdown="on"
action.resumeRetryCount="-1"
)Intégrité des fichiers et contrôle des logiciels malveillants
Le contrôle d’intégrité répond à la question qu’un attaquant veut surtout vous éviter : qu’est-ce qui a changé sur le disque ? AIDE construit une base d’empreintes et d’attributs des fichiers système et signale les différences. Construisez la base sur un système sain, gardez-en une copie hors du serveur et lancez un contrôle quotidien.
# Ubuntu and Debian
sudo apt install aide
sudo aideinit
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
sudo aide --check --config /etc/aide/aide.conf
# RHEL, Rocky and Alma
sudo dnf install aide
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
sudo aide --check
# After an intended change such as patching, accept the new state
sudo aide --update --config /etc/aide/aide.confLes gestionnaires de paquets peuvent aussi vérifier les fichiers installés par rapport à leurs sommes de contrôle, ce qui permet de repérer vite un binaire remplacé, comme un sshd ou un ps piégé. Les scanners de rootkits ajoutent des contrôles sur les fichiers de rootkits connus, les processus cachés et les modules suspects du noyau. Ils produisent des faux positifs : établissez donc une référence sur un système sain et examinez les changements plutôt que chaque avertissement.
sudo apt install debsums && sudo debsums -s # changed package files only
sudo rpm -Va | grep -E '^..5' # RHEL: files whose hash changed
sudo apt install rkhunter chkrootkit
sudo rkhunter --update && sudo rkhunter --propupd # baseline on a clean system
sudo rkhunter --check --sk --rwo # report warnings only
sudo chkrootkit -q
# ClamAV for upload directories and shared storage
sudo apt install clamav && sudo freshclam
clamscan -r -i /srv/uploadsPublier les services en sécurité
La plupart des charges d’un VPS finissent derrière un serveur web. Terminez TLS dans un proxy inverse, gardez les serveurs d’applications sur localhost et envoyez les en-têtes qui demandent aux navigateurs d’imposer HTTPS et de refuser l’intégration en cadre et la détection de contenu.
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
# /etc/nginx/conf.d/hardening.conf
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_tickets off;
client_max_body_size 10m;
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
# inside the server block
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
limit_req zone=perip burst=20 nodelay;
proxy_pass http://127.0.0.1:3000;
}Testez le résultat depuis l’extérieur avec testssl.sh ou le scanner Qualys SSL Labs, et exécutez nginx -t avant chaque rechargement. Gardez les panneaux d’administration comme phpMyAdmin, Grafana ou le backend d’un CMS entièrement hors de l’écoute publique ; accédez-y par le VPN ou un tunnel SSH.
Si vous utilisez Docker
- L’appartenance au groupe docker équivaut à être root sur l’hôte. Ne la donnez à personne à qui vous ne donneriez pas sudo, et ne montez jamais /var/run/docker.sock dans un conteneur exposé à Internet.
- Publiez les ports sur la boucle locale, par exemple 127.0.0.1:8080:8080, et n’exposez que le proxy inverse. N’oubliez pas que les ports publiés contournent UFW.
- Exécutez les conteneurs avec un utilisateur non root, un système de fichiers racine en lecture seule, toutes les capacités retirées et no-new-privileges activé. Ne rajoutez que ce dont l’image a besoin.
- Analysez les images à la recherche de vulnérabilités connues, dans la CI et sur l’hôte, avec Trivy ou Grype, et reconstruisez-les régulièrement pour que les corrections des images de base arrivent en production.
- Envisagez Docker rootless ou Podman pour les charges sans besoin de fonctions privilégiées.
{
"icc": false,
"no-new-privileges": true,
"live-restore": true,
"userland-proxy": false,
"log-driver": "local",
"log-opts": { "max-size": "20m", "max-file": "5" }
}
docker run -d --name api \
--user 10001:10001 --read-only --tmpfs /tmp \
--cap-drop ALL --security-opt no-new-privileges \
--memory 512m --pids-limit 200 \
-p 127.0.0.1:8080:8080 registry.example.com/api:1.4.2
trivy image registry.example.com/api:1.4.2Sauvegardes et compte du fournisseur
Le durcissement réduit le risque de compromission ; les sauvegardes décident de sa gravité. Suivez la règle 3-2-1 : trois copies, sur deux types de stockage, dont une hors site et hors de portée des identifiants du serveur. Un instantané du fournisseur est pratique, mais il se trouve dans le compte qu’un attaquant contrôle peut-être déjà.
sudo apt install restic
export RESTIC_REPOSITORY="s3:https://s3.eu-central-1.amazonaws.com/acme-backups/web-01"
export RESTIC_PASSWORD_FILE=/root/.restic-pass # chmod 600, also stored in your vault
export AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... # a key that can write, not delete
restic init
restic backup /etc /var/lib/myapp /srv --exclude-caches
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check
restic restore latest --target /tmp/restore-test --include /etc/nginx- Activez le verrouillage d’objets ou le versionnage sur le bucket de sauvegarde, pour qu’un rançongiciel disposant des identifiants du serveur ne puisse pas effacer l’historique. Lancez la purge depuis une machine distincte et plus fiable.
- Exportez les bases de données avec leurs outils natifs (pg_dump, mysqldump, mongodump) avant la sauvegarde des fichiers, plutôt que de copier des fichiers de données actifs.
- Testez une restauration complète vers un nouveau VPS au moins une fois par trimestre et chronométrez-la. Ce chiffre est votre véritable temps de reprise.
- Protégez le compte du fournisseur lui-même : MFA par clé matérielle pour chaque utilisateur, aucun identifiant partagé, jetons d’API limités et alertes sur les connexions à la console et les nouvelles clés d’API. Quiconque contrôle le compte peut réinitialiser root, lire les disques et supprimer les instantanés sans toucher à SSH.
Vérifier le durcissement
Un changement de durcissement non testé est un espoir, pas un contrôle. Vérifiez le serveur de l’intérieur avec un outil d’audit, de l’extérieur avec un scanner, et selon un référentiel publié si vous avez des obligations de conformité.
sudo apt install lynis # or clone the latest from GitHub
sudo lynis audit system
sudo grep -E 'warning|suggestion' /var/log/lynis-report.dat | head -50
sudo grep hardening_index /var/log/lynis-report.datnmap -Pn -sS -p- --min-rate 2000 203.0.113.10 # all TCP ports
nmap -Pn -sV -sC -p 22,80,443 203.0.113.10 # versions and default scripts
nmap -Pn -sU --top-ports 50 203.0.113.10 # common UDP services
nmap -6 -Pn -sS -p- 2001:db8::10 # do not forget IPv6
testssl.sh https://example.com
ssh-audit 203.0.113.10# RHEL, Rocky and Alma: OpenSCAP with the SCAP Security Guide
sudo dnf install openscap-scanner scap-security-guide
sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_server_l1 \
--results /root/cis-results.xml --report /root/cis-report.html \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
# Ubuntu Pro: the Ubuntu Security Guide
sudo pro enable usg && sudo apt install usg
sudo usg audit cis_level1_serverN’appliquez pas un profil CIS complet avec correction automatique à un serveur de production en service sans l’avoir testé sur une copie. Les référentiels sont écrits pour une machine générique, et certains contrôles casseront quelque chose dont votre charge dépend. Notez chaque exception et sa raison : c’est exactement ce qu’un auditeur demandera.
Le garder durci
Les serveurs dérivent. Les paquets ajoutent des services, des collègues ouvrent des ports pour un test rapide et les clés d’anciens employés restent dans authorized_keys pendant des années. Planifiez les contrôles et, si possible, codez toute la configuration dans Ansible, cloud-init ou un autre outil de gestion de configuration, pour qu’un serveur reconstruit revienne durci par défaut.
| Fréquence | Tâche | Comment |
|---|---|---|
| Chaque jour | Mises à jour de sécurité installées | unattended-upgrades ou dnf-automatic, e-mail en cas de changement |
| Chaque jour | Revue de l’intégrité et des journaux | Contrôle AIDE, résumé Fail2ban ou CrowdSec, alertes du SIEM |
| Chaque semaine | Redémarrages en attente | Fichier reboot-required, needrestart, needs-restarting |
| Chaque mois | Exposition externe | nmap de toutes les adresses, testssl.sh, ssh-audit |
| Chaque mois | Revue des accès | Utilisateurs, sudoers, authorized_keys, membres du compte du fournisseur |
| Chaque trimestre | Référentiel et test de restauration | Rapport Lynis ou OpenSCAP, restauration complète vers un nouveau VPS |
| Chaque année | Rotation des clés et des secrets | Clés SSH, jetons d’API, identifiants de sauvegarde, clés TLS |
Liste de contrôle du durcissement
- Instantané pris, système entièrement à jour, nom d’hôte défini, horloge en UTC et synchronisée.
- Comptes d’administration nommés avec sudo, mot de passe root verrouillé, journalisation sudo et use_pty activés.
- SSH par clés uniquement, sans connexion root, limité à un groupe autorisé, algorithmes modernes, vérifié avec ssh-audit.
- Le pare-feu de l’hôte refuse les entrées par défaut en IPv4 et IPv6, avec un pare-feu du fournisseur en amont.
- Fail2ban ou CrowdSec en service et actif.
- Mises à jour de sécurité automatiques avec redémarrages planifiés, et needrestart installé.
- Durcissement sysctl appliqué, modules du noyau inutilisés bloqués, /dev/shm et /tmp montés en nosuid et nodev.
- Chaque port à l’écoute justifié, services locaux liés à la boucle locale, fichiers setuid examinés.
- Services applicatifs isolés par des directives systemd et exécutés sous leurs propres utilisateurs.
- AppArmor ou SELinux en mode d’application.
- journald persistant, règles auditd chargées et verrouillées, journaux transmis hors de l’hôte via TLS.
- Référence AIDE stockée hors du serveur et contrôlée chaque jour.
- TLS terminé dans un proxy inverse avec en-têtes de sécurité ; interfaces d’administration accessibles uniquement par VPN.
- Sauvegardes chiffrées hors site avec protection contre la suppression et restauration testée.
- Compte du fournisseur protégé par MFA avec clé matérielle et alertes.
Conclusion
Ces étapes suppriment les voies d’accès les plus faciles à un serveur : connexions par mot de passe, services exposés, logiciels non corrigés et journaux manquants. Le durcissement n’est utile que s’il reste en place : relancez donc les vérifications après chaque changement important et revoyez régulièrement la liste de contrôle.
- Linux
- Durcissement
- VPS
- SSH
- Pare-feu
- nftables
- auditd
- SELinux
- AppArmor
- CIS Benchmark