Pare-feu nftables#
Pourquoi#
Un serveur NTP public est une cible évidente : le protocole NTP a
historiquement été exploité pour des attaques par amplification, et un
serveur exposé reçoit du trafic de scan en continu dès sa mise en ligne.
Le pare-feu doit filtrer ce bruit avant qu’il n’atteigne les services
applicatifs, avec deux objectifs distincts : limiter le débit par IP
(anti-abus) et éviter que le suivi de connexion (conntrack) ne sature
sous une charge de paquets UDP sans état.
Ce guide ne vise pas l’exhaustivité en matière de sécurité. Les règles ci-dessous couvrent les besoins de ce projet précis, pas un modèle de pare-feu universel. Elles doivent être adaptées à la topologie réseau propre à chaque déploiement, et complétées par un audit (chapitre Lynis) plutôt que considérées comme suffisantes telles quelles.
Comment#
Règles#
#!/usr/sbin/nft -f
flush ruleset
# Réseau d'administration autorisé pour SSH : préfixe de documentation
# (RFC 5737), à remplacer par le LAN privé
define ADMIN_NET_V4 = 192.0.2.0/24
table inet filter {
counter cnt_drop_ntp { }
counter cnt_reject_nts { }
counter cnt_state_invalid { }
set ntp_meter { type ipv4_addr ; flags dynamic ; timeout 5s ; }
set ntp_meter_v6 { type ipv6_addr ; flags dynamic ; timeout 5s ; }
set nts_meter { type ipv4_addr ; flags dynamic ; timeout 60s ; }
set nts_meter_v6 { type ipv6_addr ; flags dynamic ; timeout 60s ; }
# `timeout` fixe la durée de vie d'une entrée dynamique par IP dans
# le set : elle doit couvrir la fenêtre sur laquelle le débit est
# évalué, sans quoi l'entrée expirerait avant que la limite ait un
# sens. 5 s suffit pour un seuil par seconde (NTP), 60 s est
# nécessaire pour un seuil par minute (NTS-KE).
chain input {
type filter hook input priority filter; policy drop;
iif "lo" accept
ct state established,related accept
ct state invalid counter name "cnt_state_invalid" drop
# NTP (UDP/123) : abusif > 10/s/IP -> drop silencieux + compteur
# Seuil volontairement large : ce filtre réseau est aveugle au
# protocole (contrairement au `ratelimit` de chrony, chapitre
# dédié, plus fin), son rôle est de contenir un abus grossier
# avant même que le paquet n'atteigne chronyd, pas de remplacer
# la régulation applicative
udp dport 123 meta nfproto ipv4 add @ntp_meter { ip saddr limit rate over 10/second } counter name "cnt_drop_ntp" drop
udp dport 123 meta nfproto ipv6 add @ntp_meter_v6 { ip6 saddr limit rate over 10/second } counter name "cnt_drop_ntp" drop
udp dport 123 accept
# NTS-KE (TCP/4460) : abusif > 4/min/IP -> reset TCP + compteur
tcp dport 4460 meta nfproto ipv4 ct state new add @nts_meter { ip saddr limit rate over 4/minute } counter name "cnt_reject_nts" reject with tcp reset
tcp dport 4460 meta nfproto ipv6 ct state new add @nts_meter_v6 { ip6 saddr limit rate over 4/minute } counter name "cnt_reject_nts" reject with tcp reset
tcp dport 4460 accept
# SSH restreint au réseau d'administration
ip saddr $ADMIN_NET_V4 tcp dport 22 ct state new accept
# Web public
tcp dport { 80, 443 } accept
# ICMP / ICMPv6 (nécessaire pour PMTU et IPv6)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
udp sport 547 udp dport 546 accept
}
chain forward { type filter hook forward priority filter; policy drop; }
chain output { type filter hook output priority filter; policy accept; }
}
# Contournement du suivi de connexion pour NTP (performance + résilience DDoS)
table inet raw {
chain prerouting {
type filter hook prerouting priority raw; policy accept;
udp dport 123 notrack
}
chain output {
type filter hook output priority raw; policy accept;
udp sport 123 notrack
}
}nft -c -f /etc/nftables.conf # validation syntaxique avant activation
systemctl enable --now nftablesLe pipeline en un schéma#
Paquet UDP/123 entrant
│
▼
┌───────────────────┐
│ table inet raw │ udp dport 123 → notrack
│ hook prerouting │ ───────────────────────────────► paquet marqué « non suivi »
└───────────────────┘
│
▼
┌─────────────────────┐
│ CONNTRACK │ ← bypassé : aucune entrée allouée en RAM
│ (allocation RAM) │ (défense anti-épuisement mémoire)
└─────────────────────┘
│
▼
┌─────────────────────┐
│ table inet filter │
│ chain input │
│ rate > 10/s/IP ? │
└─────────────────────┘
│
┌─────────┴─────────┐
oui non
▼ ▼
drop silencieux ┌───────────┐
│ accept │ ────────────────────► socket chronyd
└───────────┘Erreurs à éviter#
notracksur le trafic NTP n’est pas juste une optimisation de performance : c’est une protection anti-DDoS. Sans cette directive, chaque paquet UDP/123 (y compris d’attaquants) alloue une entrée dans la tableconntrackdu noyau. Cette table a une capacité finie ; un flot suffisant de paquets peut l’épuiser, ce qui affecte alors toutes les connexions du serveur, pas seulement NTP. Le protocole NTP étant intrinsèquement sans état côté serveur (chaque requête est indépendante), le suivi de connexion n’apporte aucun bénéfice ici, seulement un risque.
- Adresse
ADMIN_NET_V4oubliée ou mal ajustée. La politique par défaut de la chaîneinputestdrop: sans la règle d’exception SSH correctement configurée sur le réseau d’administration réel, c’est l’accès qui se coupe soi-même. Cette valeur doit être vérifiée avant d’activer les règles, pas après. - Oublier la validation syntaxique avant activation.
nft -c -fvalide le fichier sans l’appliquer, une erreur de syntaxe dans un fichier appliqué directement peut laisser le pare-feu dans un état incohérent, potentiellement bloquant tout accès distant y compris SSH.
Vérifier#
nft list ruleset
# Relit la configuration active, doit correspondre au fichier
# Test depuis une machine tierce (pas depuis le serveur lui-même) :
nc -zv time.example.org 123Le vrai test de la limite de débit nécessite de générer artificiellement
un trafic dépassant 10 requêtes/seconde depuis une même IP, et de vérifier
que le compteur cnt_drop_ntp progresse :
watch -n1 'nft list counters table inet filter'Ce rate-limiting réseau agit en dehors du protocole NTP : il ne distingue pas un client légitime d’un abusif, il coupe au-delà d’un seuil brut. NTP dispose par ailleurs de son propre mécanisme protocolaire de rétorsion, le Kiss-o’-Death (codes
RATE/DENYrenvoyés dans le champ Reference ID quandStratum = 0). Les deux mécanismes sont complémentaires : le KoD régule les clients coopératifs, nftables contient ceux qui ne le sont pas. Voir le chapitre Anatomie du paquet NTP.
📚 Pour aller plus loin
Les attaques par amplification NTP exploitent historiquement la commande de monitoring
monlistdu démonntpd(retirée depuis longtemps des implémentations modernes, dont chrony) : une requête de quelques octets pouvait générer une réponse des centaines de fois plus volumineuse, dirigée vers une victime usurpée. Le rate-limiting par IP mis en place ici, combiné à l’absence de fonctionnalitémonlistdans chrony, ferme ce vecteur d’attaque à la source.