07 — Pare-feu avancé et segmentation réseau#
La sécurité réseau repose sur la capacité à contrôler précisément quels flux sont autorisés entre quels composants. Ce chapitre explore les modèles de filtrage modernes, la segmentation en profondeur (DMZ, VLAN, micro-segmentation), les Web Application Firewalls et les techniques de détection passive comme les honeypots.
Prérequis
Ce chapitre approfondit linux/10_parefeu.md. Familiarité avec nftables, les couches OSI (L3–L7), les Network Policies Kubernetes et les protocoles HTTP/TLS requise.
Modèles de filtrage#
Filtrage stateless (ACL)#
Le filtrage sans état évalue chaque paquet indépendamment, sans mémoire des échanges précédents. Chaque règle examine uniquement les champs de l’en-tête IP/TCP/UDP (src_ip, dst_ip, src_port, dst_port, proto).
Avantage : extrêmement rapide, linéaire, implémentable en matériel (ASIC).
Limite : incapable de distinguer un paquet de réponse légitime d’un paquet entrant malveillant avec les mêmes en-têtes.
Utilisé principalement sur les routeurs de cœur de réseau et les ACL de switches L3.
Filtrage stateful (conntrack)#
Le pare-feu avec état maintient une table de connexions (conntrack) qui suit l’état de chaque flux TCP/UDP/ICMP :
État conntrack |
Description |
|---|---|
|
Premier paquet d’une nouvelle connexion |
|
Connexion établie (réponse légitime attendue) |
|
Connexion annexe liée à une connexion établie (FTP, ICMP error) |
|
Paquet ne correspondant à aucun flux connu |
Grâce à conntrack, une règle ct state established,related accept autorise automatiquement les réponses sans règle explicite en retour.
Filtrage applicatif L7 (NGFW)#
Les pare-feu Next Generation (NGFW) inspectent le contenu applicatif : identification du protocole au-delà du port (DPI), contrôle par application, déchiffrement TLS pour inspection du contenu HTTPS.
Comparaison#
Caractéristique |
Stateless |
Stateful |
L7 / NGFW |
|---|---|---|---|
Couche OSI |
L3–L4 |
L3–L4 |
L7 |
Performance |
Très haute |
Haute |
Moyenne–élevée |
Contexte de connexion |
Non |
Oui |
Oui + contenu |
Détection applicative |
Non |
Non |
Oui |
Déchiffrement TLS |
Non |
Non |
Optionnel |
nftables — filtrage moderne sous Linux#
nftables remplace iptables depuis Linux 3.13 et est l’outil de référence pour le filtrage sous Debian/Ubuntu/RHEL modernes.
Architecture : tables, familles, chaînes, règles#
famille : inet (ipv4+ipv6), ip, ip6, arp, bridge
└── table : nom libre (ex. firewall)
├── chaîne input (trafic à destination de la machine)
├── chaîne output (trafic sortant de la machine)
├── chaîne forward (trafic routé à travers la machine)
└── chaîne prerouting / postrouting (NAT)
Configuration nftables de référence#
# /etc/nftables.conf
table inet firewall {
# Sets nommés (listes d'IPs autorisées)
set admin_ips {
type ipv4_addr
elements = { 10.0.0.5, 192.168.1.100 }
}
chain input {
type filter hook input priority 0; policy drop;
# Trafic loopback autorisé
iif lo accept
# Connexions établies / liées
ct state established,related accept
# ICMP limité
ip protocol icmp icmp type { echo-request, echo-reply } limit rate 10/second accept
meta l4proto ipv6-icmp accept
# SSH uniquement depuis admin_ips
tcp dport 22 ip saddr @admin_ips ct state new accept
# HTTPS public
tcp dport { 80, 443 } ct state new accept
# Journaliser les refus avant drop
limit rate 5/second log prefix "[nft-DROP] " flags all
}
chain forward {
type filter hook forward priority 0; policy drop;
# Politique zero-trust : tout refusé sauf règles explicites
}
chain output {
type filter hook output priority 0; policy accept;
# Sortie autorisée par défaut (restriction possible par service)
}
}
# Activation
# nft -f /etc/nftables.conf
# systemctl enable --now nftables
Sets, maps et compteurs#
# Set dynamique (liste noire mise à jour à chaud)
nft add set inet firewall blacklist { type ipv4_addr \; flags dynamic,timeout \; timeout 1h \; }
# Ajouter une IP à la liste noire
nft add element inet firewall blacklist { 1.2.3.4 }
# Règle de blocage référençant le set
nft insert rule inet firewall input ip saddr @blacklist drop
# Counter nommé pour statistiques
nft add counter inet firewall ssh_attempts
nft add rule inet firewall input tcp dport 22 counter name ssh_attempts
nft list counter inet firewall ssh_attempts
nftables vs iptables
nftables offre une syntaxe cohérente, des sets/maps natifs (pas besoin d’ipset), des compteurs nommés et une meilleure performance. Migrer avec iptables-translate pour les règles existantes.
Segmentation réseau#
DMZ (Zone Démilitarisée)#
La DMZ isole les serveurs exposés à internet (web, mail, DNS) du réseau interne. Deux pare-feu distincts (ou un pare-feu avec trois interfaces) contrôlent les flux :
Internet ─── [Pare-feu externe] ─── DMZ ─── [Pare-feu interne] ─── LAN interne
│
Serveurs web, reverse proxy, bastion
Règle fondamentale : aucun flux ne passe directement d’Internet vers le LAN interne sans transiter par la DMZ.
VLAN (Virtual LAN)#
Les VLANs (IEEE 802.1Q) segmentent logiquement un réseau physique en plusieurs domaines de broadcast isolés :
# Création VLAN sur un switch Linux (bridge)
ip link add link eth0 name eth0.100 type vlan id 100 # VLAN 100 : serveurs
ip link add link eth0 name eth0.200 type vlan id 200 # VLAN 200 : utilisateurs
ip link set eth0.100 up
ip link set eth0.200 up
Le routage inter-VLAN est contrôlé par le pare-feu/routeur : par défaut, les VLANs sont isolés.
Micro-segmentation et Zero Trust Networking#
La micro-segmentation pousse le principe jusqu’au niveau de la charge de travail individuelle (VM, conteneur, processus). Chaque workload n’est autorisé à communiquer qu’avec les services dont il a besoin, indépendamment de la topologie réseau.
Outils : Network Policies Kubernetes, eBPF/Cilium, VMware NSX, HashiCorp Consul Connect (mTLS entre services).
East-West vs North-South#
Trafic |
Direction |
Exemple |
Risque |
|---|---|---|---|
North-South |
Externe ↔ Interne |
Client → API publique |
Exposition directe à internet |
East-West |
Interne ↔ Interne |
Service A → Base de données |
Propagation latérale (lateral movement) |
Le trafic East-West est quantitativement dominant dans les architectures microservices (80–90% du trafic total dans les datacenters). Historiquement sous-protégé, il est le vecteur principal de propagation post-compromission.
Lateral Movement
Un attaquant ayant compromis un service peu exposé peut se déplacer latéralement vers des services critiques si le trafic East-West n’est pas filtré. La micro-segmentation limite ce risque : chaque service ne voit que ses dépendances autorisées.
WAF — Web Application Firewall#
Principes#
Un WAF opère au niveau HTTP (L7) et filtre les requêtes web en fonction de signatures de vulnérabilités applicatives :
Injections SQL, XSS, SSRF, XXE, path traversal, deserialization, CSRF…
Limite de taille des requêtes, rate limiting par IP/user.
Protection contre les scanners et bots automatisés.
ModSecurity avec OWASP CRS#
ModSecurity est le WAF open-source de référence, intégrable dans Nginx, Apache et HAProxy. Les règles OWASP Core Rule Set (CRS) couvrent les OWASP Top 10.
# nginx.conf avec ModSecurity (ngx_http_modsecurity_module)
server {
listen 443 ssl;
server_name api.example.com;
modsecurity on;
modsecurity_rules_file /etc/modsecurity/modsec_includes.conf;
location / {
proxy_pass http://backend:8080;
}
}
# /etc/modsecurity/crs-setup.conf
SecRuleEngine DetectionOnly # Mode détection (logs, no block)
# SecRuleEngine On # Mode prévention (block)
SecDefaultAction "phase:2,log,auditlog,pass"
SecRequestBodyLimit 13107200 # 12.5 MB max
SecRequestBodyNoFilesLimit 131072 # 128 KB hors fichiers
# Niveau de paranoïa CRS (1=défaut, 2-4=plus strict, plus de faux positifs)
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=1"
Modes : détection vs prévention#
Mode |
Comportement |
Usage recommandé |
|---|---|---|
DetectionOnly |
Log uniquement, ne bloque pas |
Phase initiale, calibrage des règles |
On (Enforcement) |
Bloque et log |
Production après calibrage |
Le passage en mode prévention sans calibrage préalable génère des faux positifs qui bloquent du trafic légitime (ex. CRS PL2+ sur des API JSON avec payloads atypiques).
Bypasses courants et contre-mesures#
Technique de bypass |
Mécanisme |
Contre-mesure |
|---|---|---|
Encodage URL double ( |
Décodage incomplet |
Normalisation des entrées |
Fragmentation JSON |
WAF ne reconstruit pas |
Inspection du body complet |
Unicode/UTF-8 non-BMP |
Confusion de décodage |
Normalisation Unicode |
Protocol-level smuggling |
HTTP/1.1 vs HTTP/2 |
Proxy frontend uniforme |
Padding/whitespace |
Contournement de regex |
Règles basées sur la sémantique |
WAF ≠ sécurité applicative
Un WAF est une couche de défense en profondeur, pas un substitut à la sécurisation du code. Il n’empêche pas les vulnérabilités logiques (IDOR, broken auth) et peut être contourné par des attaquants déterminés. Il doit compléter, non remplacer, les revues de code et tests SAST/DAST.
Network Policies Kubernetes#
Les Network Policies Kubernetes implémentent la micro-segmentation pour les Pods. Sans Network Policy, tous les Pods peuvent communiquer entre eux (politique ouverte par défaut).
# Politique "default deny" : isoler le namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Sélectionne tous les Pods
policyTypes:
- Ingress
- Egress
---
# Autoriser uniquement api → database sur le port 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-db
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
CNI avec support Network Policies
Les Network Policies Kubernetes nécessitent un CNI (Container Network Interface) qui les implémente réellement : Calico, Cilium, Weave Net. Le CNI par défaut de kubeadm (flannel) ne les supporte pas.
Honeypots et honeynets#
Principes#
Un honeypot est un système délibérément vulnérable et attractif, conçu pour détecter et étudier les attaquants. Tout accès légitime à un honeypot est inexistant par définition — toute interaction est suspecte.
Types :
Low-interaction : simule des services (ports ouverts, bannières), capture les scans et tentatives d’exploitation de base (ex. Cowrie pour SSH, HoneyPy).
High-interaction : système réel en sandbox, capture des attaques sophistiquées et des outils d’attaquants.
Honeynet : réseau complet de honeypots interconnectés.
Honeytokens#
Des honeytokens sont des credentials ou ressources factices semés dans les systèmes légitimes :
# Créer un AWS API key honeytoken (Canarytokens)
# Si cette clé est utilisée, une alerte est déclenchée
# AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE (semé dans config.js)
# AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI... (clé piège, surveillée via CloudTrail)
Légalité des honeypots
En France (RGPD, Code pénal art. 323-x), les honeypots doivent être clairement isolés des systèmes de production, ne pas collecter de données personnelles sans base légale, et leur déploiement doit être documenté. Consulter le RSSI/DPO avant déploiement.
Cellules Python#
Simulation moteur de règles pare-feu#
# --- Moteur de règles nftables-like ---
RULES = [
# (src_network, dst_network, dst_port, proto, action, description)
("0.0.0.0/0", "0.0.0.0/0", None, "any", "accept", "loopback (simplification)"),
("10.0.0.0/8", "10.0.0.0/8", 22, "tcp", "accept", "SSH interne"),
("0.0.0.0/0", "0.0.0.0/0", 80, "tcp", "accept", "HTTP public"),
("0.0.0.0/0", "0.0.0.0/0", 443, "tcp", "accept", "HTTPS public"),
("10.0.1.0/24", "10.0.2.0/24", 5432, "tcp", "accept", "API → PostgreSQL"),
("10.0.1.0/24", "10.0.3.0/24", 6379, "tcp", "accept", "API → Redis"),
("192.168.0.0/16","0.0.0.0/0", None, "any", "drop", "Blocage RFC-1918 externe"),
("0.0.0.0/0", "0.0.0.0/0", None, "any", "drop", "Politique défaut : DROP"),
]
def ip_in_network(ip: str, network: str) -> bool:
try:
return ipaddress.ip_address(ip) in ipaddress.ip_network(network, strict=False)
except ValueError:
return False
def evaluate_rule(rule, flow: dict) -> bool:
src_net, dst_net, port, proto, action, _ = rule
if not ip_in_network(flow["src_ip"], src_net):
return False
if not ip_in_network(flow["dst_ip"], dst_net):
return False
if port is not None and flow["dst_port"] != port:
return False
if proto != "any" and flow["proto"] != proto:
return False
return True
def evaluate_firewall(flow: dict) -> tuple[str, str]:
for rule in RULES:
if evaluate_rule(rule, flow):
return rule[4], rule[5]
return "drop", "Politique défaut"
# Générer des flux synthétiques
random.seed(42)
np.random.seed(42)
def rand_ip(network: str) -> str:
net = ipaddress.ip_network(network, strict=False)
n = net.num_addresses
if n <= 2:
return str(net.network_address)
offset = random.randint(1, min(n - 2, 254))
return str(net.network_address + offset)
test_flows = []
scenarios = [
# (src_network, dst_network, port, proto)
("172.16.0.0/24", "10.0.0.50/32", 443, "tcp"), # Externe → HTTPS
("10.0.0.0/24", "10.0.0.10/32", 22, "tcp"), # Interne → SSH
("10.0.1.0/24", "10.0.2.5/32", 5432,"tcp"), # API → DB
("10.0.1.0/24", "10.0.3.2/32", 6379,"tcp"), # API → Redis
("8.8.8.8/32", "10.0.0.1/32", 22, "tcp"), # Externe → SSH (bloqué)
("192.168.1.0/24","8.8.8.8/32", 80, "tcp"), # RFC-1918 sortant bloqué
("0.0.0.0/8", "10.0.0.1/32", 9200,"tcp"), # Port non autorisé
("10.0.0.0/24", "10.0.0.1/32", 80, "tcp"), # Interne → HTTP
]
for _ in range(80):
s = random.choice(scenarios)
flow = {
"src_ip": rand_ip(s[0]),
"dst_ip": rand_ip(s[1]),
"dst_port": s[2],
"proto": s[3],
}
action, reason = evaluate_firewall(flow)
flow["action"] = action
flow["raison"] = reason
test_flows.append(flow)
df = pd.DataFrame(test_flows)
print("=== Résultats de l'évaluation du pare-feu ===")
print(df.groupby(["action", "raison"]).size().rename("count").to_string())
print(f"\nTotal : {len(df)} flux | Acceptés : {(df.action=='accept').sum()} | Bloqués : {(df.action=='drop').sum()}")
=== Résultats de l'évaluation du pare-feu ===
action raison
accept loopback (simplification) 80
Total : 80 flux | Acceptés : 80 | Bloqués : 0
Heatmap de trafic inter-zones#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Matrice de trafic : zones × zones
zones = ["Internet", "DMZ", "LAN API", "LAN DB", "LAN Admin", "LAN Redis"]
# Trafic autorisé (1) / bloqué (0) — politique de filtrage
traffic_matrix = np.array([
# Internet DMZ API DB Admin Redis
[0, 1, 0, 0, 0, 0], # Internet →
[1, 0, 1, 0, 0, 0], # DMZ →
[0, 1, 0, 1, 0, 1], # LAN API →
[0, 0, 1, 0, 0, 0], # LAN DB →
[1, 1, 1, 1, 0, 1], # LAN Admin →
[0, 0, 1, 0, 0, 0], # LAN Redis →
])
fig, ax = plt.subplots(figsize=(9, 7))
cmap = matplotlib.colors.ListedColormap(["#d9534f", "#5cb85c"])
norm = matplotlib.colors.BoundaryNorm([0, 0.5, 1], cmap.N)
im = ax.imshow(traffic_matrix, cmap=cmap, norm=norm, aspect="auto")
# Annotations
for i in range(len(zones)):
for j in range(len(zones)):
val = traffic_matrix[i, j]
txt = "Autorisé" if val == 1 else "Bloqué"
ax.text(j, i, txt, ha="center", va="center",
fontsize=9, color="white", fontweight="bold")
ax.set_xticks(range(len(zones)))
ax.set_yticks(range(len(zones)))
ax.set_xticklabels(zones, rotation=30, ha="right", fontsize=10)
ax.set_yticklabels(zones, fontsize=10)
ax.set_xlabel("Zone de destination", fontsize=11)
ax.set_ylabel("Zone source", fontsize=11)
ax.set_title("Matrice de filtrage inter-zones\n(politique de segmentation réseau)",
fontsize=12, fontweight="bold")
legend_handles = [
mpatches.Patch(color="#5cb85c", label="Autorisé"),
mpatches.Patch(color="#d9534f", label="Bloqué"),
]
ax.legend(handles=legend_handles, loc="upper right", fontsize=10,
bbox_to_anchor=(1.25, 1.0))
plt.savefig("traffic_matrix.png", dpi=120, bbox_inches="tight")
plt.show()
Courbe précision/rappel du WAF selon le seuil CRS#
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# Simulation : sensibilité des règles CRS vs précision/rappel
np.random.seed(0)
# Niveau de paranoïa CRS : 1 (défaut) à 4 (très strict)
# Plus le niveau monte, plus le rappel augmente (on détecte plus d'attaques)
# Mais aussi plus de faux positifs (précision baisse)
paranoia_levels = np.linspace(1, 4, 50)
# Modélisation heuristique des courbes précision/rappel
recall = 0.30 + 0.55 * (1 - np.exp(-0.7 * (paranoia_levels - 1)))
precision = 0.95 - 0.45 * (1 - np.exp(-0.5 * (paranoia_levels - 1)))
# Ajouter du bruit réaliste
recall += np.random.normal(0, 0.015, len(paranoia_levels))
precision += np.random.normal(0, 0.012, len(paranoia_levels))
recall = np.clip(recall, 0, 1)
precision = np.clip(precision, 0, 1)
# Score F1
f1 = 2 * precision * recall / (precision + recall + 1e-9)
# Trouver le seuil optimal F1
best_idx = np.argmax(f1)
best_pl = paranoia_levels[best_idx]
best_prec = precision[best_idx]
best_rec = recall[best_idx]
best_f1 = f1[best_idx]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# --- Courbe Précision/Rappel ---
ax1 = axes[0]
ax1.plot(recall, precision, color="#4878d0", linewidth=2.5, label="Courbe P/R")
ax1.scatter([best_rec], [best_prec], color="crimson", s=120, zorder=5,
label=f"Optimal (PL≈{best_pl:.1f})\nF1={best_f1:.2f}")
ax1.set_xlabel("Rappel (détection d'attaques réelles)", fontsize=11)
ax1.set_ylabel("Précision (trafic bloqué = réellement malveillant)", fontsize=11)
ax1.set_title("Courbe Précision/Rappel du WAF\nselon le niveau de paranoïa CRS", fontsize=11, fontweight="bold")
ax1.legend(fontsize=9)
ax1.set_xlim(0, 1.05)
ax1.set_ylim(0, 1.05)
# Annotations zones
ax1.axhspan(0.85, 1.05, alpha=0.08, color="green")
ax1.text(0.05, 0.88, "Haute précision\n(peu de faux positifs)", fontsize=8, color="green")
ax1.axvspan(0.75, 1.05, alpha=0.08, color="blue")
ax1.text(0.78, 0.1, "Haute détection\n(peu d'attaques manquées)", fontsize=8, color="#4878d0")
# --- Précision, Rappel et F1 vs niveau de paranoïa ---
ax2 = axes[1]
ax2.plot(paranoia_levels, precision, color="#4878d0", linewidth=2, label="Précision")
ax2.plot(paranoia_levels, recall, color="#ee854a", linewidth=2, label="Rappel")
ax2.plot(paranoia_levels, f1, color="#6acc65", linewidth=2, linestyle="--", label="F1")
ax2.axvline(x=best_pl, color="crimson", linestyle=":", linewidth=1.5)
ax2.text(best_pl + 0.05, 0.25, f"PL opt.≈{best_pl:.1f}", color="crimson", fontsize=9)
ax2.set_xlabel("Niveau de paranoïa CRS (1 = défaut, 4 = strict)", fontsize=11)
ax2.set_ylabel("Score", fontsize=11)
ax2.set_title("Précision, Rappel et F1\nselon la sensibilité des règles WAF", fontsize=11, fontweight="bold")
ax2.legend(fontsize=9)
ax2.set_xlim(1, 4)
ax2.set_ylim(0, 1.05)
ax2.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(0.5))
plt.savefig("waf_precision_recall.png", dpi=120, bbox_inches="tight")
plt.show()
print(f"Niveau de paranoïa optimal : {best_pl:.2f}")
print(f" Précision : {best_prec:.3f} | Rappel : {best_rec:.3f} | F1 : {best_f1:.3f}")
Niveau de paranoïa optimal : 3.20
Précision : 0.664 | Rappel : 0.751 | F1 : 0.705
Résumé#
Modèles de filtrage : le filtrage stateless (ACL) offre des performances maximales mais ne distingue pas les états de connexion. Le filtrage stateful (conntrack) autorise automatiquement les réponses légitimes. Les NGFW inspectent jusqu’à L7 pour le contrôle applicatif.
nftables : successeur d’iptables, il unifie la syntaxe, introduit les sets/maps natifs et offre de meilleures performances. La politique
default dropet l’usage dect state established,related acceptsont les fondations de toute configuration sécurisée.Segmentation : la DMZ isole les services exposés, les VLANs créent des domaines de broadcast séparés, et la micro-segmentation protège contre le mouvement latéral en traitant chaque workload comme une zone distincte.
East-West vs North-South : le trafic East-West est majoritaire dans les microservices et historiquement sous-protégé. C’est le vecteur principal de propagation post-compromission — la micro-segmentation est la réponse architecturale.
WAF : ModSecurity avec OWASP CRS protège contre les attaques web connues. Le niveau de paranoïa (1–4) contrôle le compromis précision/rappel. Un WAF ne remplace pas la sécurisation applicative.
Network Policies Kubernetes implémentent la micro-segmentation au niveau Pod. Sans politique explicite, tous les Pods d’un cluster communiquent librement — implémenter
default-deny-alldans chaque namespace de production est un prérequis de sécurité.Honeypots : outils de détection passive. Toute interaction est suspecte par définition. Les honeytokens (credentials factices semés dans les systèmes) détectent les compromissions internes avec un taux de faux positifs quasi-nul.