21. Réponse aux incidents et forensique numérique#
La réponse aux incidents (IR) et la forensique numérique constituent la dernière ligne de défense active : quand la prévention a échoué, ces disciplines permettent de comprendre ce qui s’est passé, de contenir les dégâts, d’éradiquer la menace et de rétablir les opérations dans un état de confiance. La forensique nourrit l’IR avec des preuves ; l’IR donne à la forensique son contexte opérationnel.
Le cycle IR selon NIST SP 800-61r3#
Le NIST Special Publication 800-61r3 (révision 3, 2024) structure la réponse aux incidents en six phases interdépendantes, formant un cycle d’amélioration continue.
Phase 1 — Préparation#
La préparation est la phase la plus déterminante : 80 % de la qualité d’une réponse se joue avant que l’incident ne survienne.
Équipe CSIRT (Computer Security Incident Response Team) :
Rôles définis : incident manager, analyste forensique, coordinateur communication, RSSI
Périmètre de responsabilité et escalade clairement documentés
Astreinte 24/7 ou contrat de support externe (MSSP)
Infrastructure de réponse :
SIEM opérationnel avec règles de corrélation actives
Système de ticketing dédié aux incidents (JIRA Security, TheHive)
Environnement d’analyse isolé (sandbox réseau, VM forensiques)
Outils préinstallés et testés : agents EDR, collecteurs de logs, outils forensiques
Documentation :
Inventaire des actifs (CMDB à jour)
Playbooks documentés par type d’incident
Contacts d’urgence : hébergeurs, fournisseurs cloud, CERT national, juridique
Phase 2 — Détection et analyse#
La détection provient de sources multiples : alertes SIEM, EDR, notifications externes (CERT, chercheurs), utilisateurs.
Triage initial :
Confirmation de l’incident vs faux positif
Classification : confidentialité, intégrité, disponibilité affectées
Sévérité : P1 (critique, impact immédiat) → P4 (mineur)
Notification selon la matrice d’escalade
Sources de détection :
Logs système (syslog, Windows Event Log)
Alertes réseau (IDS/IPS, NetFlow)
Alertes endpoint (EDR : CrowdStrike, SentinelOne)
Threat intelligence (IoC connus)
Honeypots et canary tokens
Phase 3 — Containment (confinement)#
Le confinement vise à limiter la propagation sans détruire les preuves.
Confinement à court terme : isolation réseau du système compromis, désactivation des comptes impliqués, blocage des IoC (IP, domaines, hashes) aux points de contrôle réseau.
Confinement à long terme : reconstruction sur image saine, renforcement des contrôles d’accès, surveillance renforcée des systèmes adjacents.
Tension forensique vs opérations
L’isolation d’un système détruit les connexions réseau actives, la mémoire vive et les artefacts volatils. L’ordre prioritaire est : dump mémoire → capture réseau → image disque → isolation. Ne jamais éteindre brutalement un système compromis avant d’avoir collecté les artefacts volatils.
Phase 4 — Éradication#
Suppression complète de tous les artefacts malveillants : malwares, backdoors, comptes créés, tâches planifiées, clés de registre, certificats compromis.
Validation : scan antimalware, revue des comptes privilégiés, audit des modifications récentes de configuration.
Phase 5 — Récupération#
Remise en service progressive avec surveillance renforcée. Validation que la menace est bien éradiquée avant de rétablir la production. Monitoring intensifié pendant 30 à 90 jours post-incident.
Phase 6 — Leçons apprises#
Post-mortem structuré (voir section 21.8) : blameless, axé sur les processus et les systèmes. Mise à jour des playbooks, ajout de règles de détection, correction des lacunes identifiées.
Playbooks d’incident et SOAR#
Un playbook est une procédure structurée, étape par étape, définissant les actions à mener pour un type d’incident donné. Les plateformes SOAR (Security Orchestration, Automation and Response) permettent d’automatiser l’exécution de ces playbooks.
Principales plateformes SOAR :
Cortex XSOAR (Palo Alto) : intégrations natives avec plus de 700 produits, playbooks visuels
Splunk SOAR (ex-Phantom) : intégration native avec Splunk SIEM, Python pour les actions
TheHive + Cortex : stack open-source, analyseurs et répondeurs automatisés
Structure d’un playbook SOAR :
# Exemple de playbook SOAR — Phishing avec lien malveillant
name: "Phishing Email Response"
version: "2.1"
trigger:
type: alert
source: email_gateway
conditions:
- field: verdict
operator: equals
value: "malicious"
tasks:
- id: T01
name: "Extraire les IoC"
action: email.extract_indicators
outputs: [urls, hashes, sender_ip]
- id: T02
name: "Enrichir les IoC"
action: virustotal.lookup
inputs: {indicators: "{{T01.outputs}}"}
depends_on: [T01]
- id: T03
name: "Bloquer l'expéditeur"
action: email_gateway.block_sender
inputs: {address: "{{T01.sender_ip}}"}
depends_on: [T01]
condition: "{{T02.malicious_count}} > 0"
- id: T04
name: "Notifier les utilisateurs"
action: slack.send_message
inputs:
channel: "#security-incidents"
message: "Campagne phishing détectée — IoC bloqués"
depends_on: [T03]
- id: T05
name: "Créer ticket P2"
action: jira.create_issue
inputs:
project: "SEC"
priority: "High"
description: "{{incident.summary}}"
depends_on: [T01]
Métriques SOAR :
MTTD (Mean Time To Detect) : délai entre compromission et détection
MTTR (Mean Time To Respond) : délai entre détection et confinement complet
Taux d’automatisation : % d’actions exécutées sans intervention humaine
Forensique disque#
Chaîne de custody (chain of custody)#
La chaîne de custody est la documentation ininterrompue de la manipulation d’une preuve numérique, garantissant son intégrité et sa recevabilité légale.
Chaque étape documente : qui a manipulé la preuve, quand, dans quel but, avec quel outil, et le hash de vérification avant/après.
Acquisition d’image disque#
# dd — acquisition bit-à-bit
dd if=/dev/sda of=/media/forensics/case001/image.dd \
bs=4096 conv=noerror,sync status=progress
# dcfldd — version forensique de dd (hashing intégré)
dcfldd if=/dev/sda of=/media/forensics/case001/image.dd \
hash=sha256 hashlog=/media/forensics/case001/hash.log \
bs=4096 status=on
# Vérification d'intégrité
sha256sum /dev/sda > /media/forensics/case001/source_hash.txt
sha256sum /media/forensics/case001/image.dd > /media/forensics/case001/image_hash.txt
diff source_hash.txt image_hash.txt # Doivent être identiques
# FTK Imager (Linux CLI)
ftkimager /dev/sda /media/forensics/case001/image \
--e01 --compress 6 --verify
Write-blocker obligatoire
Toute acquisition forensique doit utiliser un write-blocker matériel (ou logiciel via hdparm ou blockdev --setro) pour garantir que le support source n’est pas modifié. Une image acquise sans write-blocker est irrecevable en justice.
Analyse de métadonnées#
# Extraire les métadonnées EXIF d'images
exiftool image.jpg
# Analyser les timestamps de fichiers (MAC times)
stat fichier.txt
# - mtime : dernière modification du contenu
# - atime : dernier accès
# - ctime : dernier changement d'inode (permissions, owner)
# Analyse d'une image disque avec Autopsy/TSK
mmls image.dd # Structure des partitions
fls -r -l image.dd 3 # Liste récursive des fichiers
icat image.dd 3 12345 > fichier_rec # Récupérer un fichier par inode
Forensique mémoire#
La mémoire vive contient des artefacts qui disparaissent à l’extinction : processus en cours, connexions réseau actives, clés de chiffrement, malwares fileless, injections de code.
Acquisition mémoire#
# Linux — LiME (Linux Memory Extractor)
# Charger le module kernel
insmod lime-$(uname -r).ko \
"path=/media/forensics/memory.lime format=lime"
# Windows — winpmem
winpmem_mini_x64.exe memory.aff4
# macOS — osxpmem
osxpmem.app/osxpmem -o memory.aff4
Volatility 3 — analyse mémoire#
# Identifier le profil OS
vol.py -f memory.lime banners.Banners
# Liste des processus (équivalent ps)
vol.py -f memory.lime linux.pslist.PsList
# Processus cachés (détection rootkit)
vol.py -f memory.lime linux.pstree.PsTree
# Connexions réseau actives
vol.py -f memory.lime linux.netstat.Netstat
# Windows :
vol.py -f memory.dmp windows.netscan.NetScan
# Détection d'injection de code (malfind)
vol.py -f memory.dmp windows.malfind.Malfind
# Dump d'un processus suspect (pid 1337)
vol.py -f memory.dmp windows.dumpfiles.DumpFiles --pid 1337
# Analyse des handles ouverts
vol.py -f memory.dmp windows.handles.Handles --pid 1337
# Artefacts réseau — cache ARP
vol.py -f memory.dmp windows.arp.ARP
# Clés de registre récentes (Windows)
vol.py -f memory.dmp windows.registry.hivelist.HiveList
vol.py -f memory.dmp windows.registry.printkey.PrintKey \
--key "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
Forensique réseau#
Capture de trafic#
# tcpdump — capture complète sur eth0
tcpdump -i eth0 -w /forensics/capture.pcap -s 0
# Filtres ciblés
tcpdump -i eth0 'host 192.168.1.50 and not port 22' -w suspect.pcap
tcpdump -i eth0 'tcp port 443 and (tcp[tcpflags] & tcp-syn != 0)' -w syn.pcap
# Capture avec rotation (fichiers de 100 MB, 50 fichiers max)
tcpdump -i eth0 -w /forensics/cap_%Y%m%d_%H%M%S.pcap \
-G 3600 -C 100 -W 50
# Analyse avec tshark (CLI Wireshark)
tshark -r capture.pcap -Y "http.response.code == 200" \
-T fields -e ip.src -e http.host -e http.request.uri
# Statistiques de conversation
tshark -r capture.pcap -q -z conv,tcp
Reconstruction de sessions TCP#
# tcpflow — reconstruit les flux applicatifs
tcpflow -r capture.pcap -o /forensics/sessions/
# NetworkMiner (GUI) ou Zeek pour analyse automatique
zeek -r capture.pcap
# Génère : conn.log, http.log, dns.log, ssl.log, files.log
Timeline forensique et corrélation multi-sources#
La timeline forensique est la reconstruction chronologique de tous les événements pertinents, en corrélant des sources hétérogènes.
Sources à corréler :
Logs système :
/var/log/auth.log,/var/log/syslog, Windows Event LogLogs réseau : pare-feu, proxy, DNS
Logs applicatifs : serveurs web, bases de données, applications métier
Artefacts disque : timestamps MAC, journaux de fichiers
Logs EDR : télémétrie endpoint
plaso / log2timeline :
# Extraire tous les artefacts temporels d'une image
log2timeline.py --storage-file case001.plaso image.dd
# Filtrer et exporter en CSV
psort.py -o L2tcsv case001.plaso \
"date > '2024-01-15 08:00:00' AND date < '2024-01-15 18:00:00'" \
> timeline.csv
Indicateurs de compromission (IoC)#
Les IoC sont des artefacts observables indiquant une compromission probable ou avérée.
Type |
Exemples |
Volatilité |
|---|---|---|
Hash de fichier |
MD5, SHA1, SHA256 de malware |
Faible |
Adresse IP |
C2, scanner |
Haute (rotation rapide) |
Domaine |
C2, phishing |
Moyenne |
URL |
Payload, dropper |
Moyenne |
Mutex |
Identifiant unique malware |
Faible |
Clé de registre |
Persistance |
Faible |
User-agent |
Beacon de RAT |
Moyenne |
Certificat TLS |
Hash ou serial |
Faible |
La Pyramide de la Douleur (David Bianco) classe les IoC par difficulté pour l’attaquant de les modifier : les TTPs (Tactics, Techniques, Procedures) au sommet sont les plus résistants aux changements.
Extraction automatisée d’IoC :
MISP (Malware Information Sharing Platform) : plateforme de partage d’IoC
OpenCTI : plateforme de Cyber Threat Intelligence
Yara : règles de détection basées sur des patterns de fichiers
Post-mortem d’incident#
Le post-mortem est conduit en mode blameless : l’objectif est l’amélioration systémique, pas la désignation de responsables.
Structure standard :
## Post-mortem — Incident SEC-2024-0147
**Date de l'incident :** 2024-03-15 14:23 UTC
**Date de résolution :** 2024-03-16 03:45 UTC
**Durée totale :** 13h22
**Sévérité :** P1 — Exfiltration de données
### Timeline
- 14:23 — Première connexion de l'attaquant (comptes compromis)
- 16:45 — Mouvement latéral vers serveur de base de données
- 19:12 — Début de l'exfiltration (détecté a posteriori)
- 22:57 — Alerte SIEM déclenchée
- 23:15 — CSIRT notifié, début de l'investigation
- 00:30 — Confinement du système compromis
- 03:45 — Éradication complète confirmée
### Impact
- Données clients affectées : 12 400 enregistrements
- Services indisponibles : 4h12 (CRM)
- Coût estimé : 45 000 €
### Causes racines
1. MFA non activé sur le VPN pour les comptes non-admin
2. Règle SIEM trop permissive — seuil d'alerte trop élevé
3. Absence de segmentation entre DMZ et réseau interne
### Actions correctives
1. Activation MFA obligatoire pour tous les accès VPN — délai 7 jours
2. Révision des règles SIEM DataExfiltration — délai 48h
3. Mise en place VLAN de segmentation — délai 30 jours
Cellule 1 — Simulation de timeline forensique multi-sources#
# Génération d'une timeline forensique synthétique multi-sources
base_time = datetime(2024, 3, 15, 14, 0, 0)
evenements = [
# Source, timestamp, description, catégorie
("Système", base_time + timedelta(minutes=23), "Connexion SSH depuis 185.220.101.42", "auth"),
("Système", base_time + timedelta(minutes=31), "sudo su — élévation de privilèges", "priv_esc"),
("Réseau", base_time + timedelta(minutes=28), "SYN scan depuis 185.220.101.42", "scan"),
("Réseau", base_time + timedelta(minutes=45), "Connexion vers 10.0.0.15 port 5432", "lateral"),
("Applicatif", base_time + timedelta(minutes=35), "Échec auth × 47 sur /admin", "bruteforce"),
("Applicatif", base_time + timedelta(minutes=52), "Requête SQL anormale — UNION SELECT", "sql_inj"),
("Système", base_time + timedelta(minutes=67), "cron job ajouté : /tmp/.update", "persistence"),
("Réseau", base_time + timedelta(minutes=75), "DNS vers exfil.evil-domain.com", "exfil"),
("Réseau", base_time + timedelta(minutes=82), "Transfert sortant 2.3 GB vers 185.220.101.42", "exfil"),
("Système", base_time + timedelta(minutes=90), "Modification /etc/passwd", "tamper"),
("Applicatif", base_time + timedelta(minutes=95), "Accès 15 000 enregistrements clients", "exfil"),
("Système", base_time + timedelta(minutes=110), "Suppression logs /var/log/auth.log", "antiforensic"),
("Réseau", base_time + timedelta(minutes=115), "Connexion C2 maintenue (beacon 60s)", "c2"),
("Système", base_time + timedelta(minutes=180), "Alerte EDR — process suspect", "detection"),
("Réseau", base_time + timedelta(minutes=185), "Alerte SIEM — exfiltration", "detection"),
]
df = pd.DataFrame(evenements, columns=["Source", "Timestamp", "Description", "Catégorie"])
df = df.sort_values("Timestamp").reset_index(drop=True)
# Visualisation scatter temporel
couleurs_source = {"Système": "#2196F3", "Réseau": "#FF5722", "Applicatif": "#4CAF50"}
shapes_cat = {
"auth": "o", "priv_esc": "^", "scan": "s", "lateral": "D",
"bruteforce": "v", "sql_inj": "P", "persistence": "*",
"exfil": "X", "tamper": "h", "antiforensic": "p",
"c2": "8", "detection": "H"
}
fig, ax = plt.subplots(figsize=(14, 6))
y_positions = {"Système": 2, "Réseau": 1, "Applicatif": 0}
y_labels = {0: "Applicatif", 1: "Réseau", 2: "Système"}
for _, row in df.iterrows():
y = y_positions[row["Source"]]
x = (row["Timestamp"] - base_time).total_seconds() / 60
color = couleurs_source[row["Source"]]
marker = shapes_cat.get(row["Catégorie"], "o")
size = 200 if row["Catégorie"] == "detection" else 120
ax.scatter(x, y, color=color, marker=marker, s=size, zorder=3,
edgecolors="white", linewidths=0.8)
if row["Catégorie"] in ("detection", "exfil", "persistence", "lateral"):
ax.annotate(row["Description"][:35] + "…" if len(row["Description"]) > 35 else row["Description"],
(x, y), xytext=(0, 12), textcoords="offset points",
fontsize=7, ha="center", color=color,
arrowprops=dict(arrowstyle="-", color=color, lw=0.5))
# Zone d'attaque
ax.axvspan(23, 115, alpha=0.08, color="#FF5722", label="Fenêtre d'attaque")
# Ligne de détection
ax.axvline(x=180, color="#9C27B0", linestyle="--", linewidth=2, label="Détection EDR/SIEM (T+3h)")
ax.set_yticks([0, 1, 2])
ax.set_yticklabels(["Applicatif", "Réseau", "Système"])
ax.set_xlabel("Temps écoulé depuis le début de l'incident (minutes)")
ax.set_title("Timeline forensique multi-sources — Incident SEC-2024-0147", fontsize=13, fontweight="bold")
patches = [mpatches.Patch(color=v, label=k) for k, v in couleurs_source.items()]
ax.legend(handles=patches + [
plt.Line2D([0], [0], linestyle="--", color="#9C27B0", lw=2, label="Détection"),
mpatches.Patch(color="#FF5722", alpha=0.2, label="Fenêtre d'attaque")
], loc="upper left", fontsize=9)
ax.grid(axis="x", alpha=0.4)
fig.suptitle(f"Base : {base_time.strftime('%Y-%m-%d %H:%M UTC')}", fontsize=9, color="gray")
plt.show()
print(f"Événements corrélés : {len(df)}")
print(f"Durée fenêtre d'attaque : {115 - 23} minutes avant détection")
print(f"MTTD (temps avant détection) : {180} minutes")
Événements corrélés : 15
Durée fenêtre d'attaque : 92 minutes avant détection
MTTD (temps avant détection) : 180 minutes
Cellule 2 — Analyse de logs nginx : détection d’anomalies#
# Génération de logs nginx synthétiques avec anomalies
import re
np.random.seed(0)
n_normal = 800
n_scan = 150
n_bruteforce = 100
ips_normales = [f"192.168.{np.random.randint(1,5)}.{np.random.randint(10,200)}" for _ in range(20)]
ips_suspectes = ["185.220.101.42", "10.10.10.100", "45.33.32.156"]
def gen_logs(n, ip_pool, path_pool, status_pool, ua_pool, base_h=10, spread=8):
logs = []
for _ in range(n):
ts = base_h + np.random.exponential(spread / 3)
ts = min(ts, 24)
logs.append({
"heure": ts,
"ip": np.random.choice(ip_pool),
"path": np.random.choice(path_pool),
"status": np.random.choice(status_pool),
"bytes": int(np.random.exponential(5000) + 200),
"user_agent": np.random.choice(ua_pool),
})
return logs
paths_normaux = ["/", "/index.html", "/api/products", "/api/users/me", "/static/app.js", "/login"]
paths_scan = [
"/admin", "/.env", "/wp-admin", "/phpmyadmin", "/.git/config",
"/backup.zip", "/config.php", "/../../../etc/passwd", "/api/v1/admin"
]
uas_normaux = [
"Mozilla/5.0 (Windows NT 10.0) Chrome/120",
"Mozilla/5.0 (Macintosh) Safari/537.36",
"Mozilla/5.0 (Linux) Firefox/121.0",
]
uas_suspects = ["sqlmap/1.7.8", "nikto/2.1.6", "masscan/1.3", "python-requests/2.31", "curl/7.88"]
logs_normaux = gen_logs(n_normal, ips_normales, paths_normaux, [200, 200, 200, 304, 404], uas_normaux)
logs_scan = gen_logs(n_scan, ips_suspectes[:2], paths_scan, [404, 403, 200, 500], uas_suspects, base_h=2, spread=3)
logs_bruteforce = gen_logs(n_bruteforce, [ips_suspectes[0]], ["/login"], [401, 401, 401, 200], uas_suspects, base_h=3, spread=1)
all_logs = pd.DataFrame(logs_normaux + logs_scan + logs_bruteforce)
all_logs["heure_bin"] = (all_logs["heure"] // 1).astype(int)
all_logs["suspect"] = all_logs["user_agent"].isin(uas_suspects)
# Visualisation
fig, axes = plt.subplots(2, 2, figsize=(14, 9))
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
# 1. Volume horaire avec distinction normal/suspect
pivot_vol = all_logs.groupby(["heure_bin", "suspect"]).size().unstack(fill_value=0)
pivot_vol.columns = ["Normal", "Suspect"]
pivot_vol.plot(kind="bar", ax=axes[0, 0], color=["#4CAF50", "#F44336"], alpha=0.85, width=0.7)
axes[0, 0].set_title("Volume de requêtes par heure", fontweight="bold")
axes[0, 0].set_xlabel("Heure UTC")
axes[0, 0].set_ylabel("Nombre de requêtes")
axes[0, 0].set_xticklabels(pivot_vol.index, rotation=45)
# 2. Distribution des codes HTTP
status_counts = all_logs.groupby(["status", "suspect"]).size().unstack(fill_value=0)
status_counts.columns = ["Normal", "Suspect"]
status_counts.plot(kind="bar", ax=axes[0, 1], color=["#2196F3", "#FF5722"], alpha=0.85)
axes[0, 1].set_title("Codes HTTP par source", fontweight="bold")
axes[0, 1].set_xlabel("Code HTTP")
axes[0, 1].set_ylabel("Occurrences")
axes[0, 1].set_xticklabels(status_counts.index, rotation=0)
# 3. Top paths suspects
top_paths = all_logs[all_logs["suspect"]]["path"].value_counts().head(10)
sns.barplot(x=top_paths.values, y=top_paths.index, ax=axes[1, 0], palette="Reds_r")
axes[1, 0].set_title("Top 10 chemins accédés par sources suspectes", fontweight="bold")
axes[1, 0].set_xlabel("Nombre de requêtes")
axes[1, 0].set_ylabel("")
# 4. Top user-agents suspects
top_ua = all_logs[all_logs["suspect"]]["user_agent"].value_counts()
sns.barplot(x=top_ua.values, y=top_ua.index, ax=axes[1, 1], palette="Oranges_r")
axes[1, 1].set_title("User-agents suspects détectés", fontweight="bold")
axes[1, 1].set_xlabel("Occurrences")
axes[1, 1].set_ylabel("")
fig.suptitle("Analyse des logs nginx — Détection d'anomalies", fontsize=14, fontweight="bold")
plt.subplots_adjust(hspace=0.4, wspace=0.35)
plt.show()
print(f"Total requêtes : {len(all_logs)}")
print(f"Requêtes suspectes : {all_logs['suspect'].sum()} ({100*all_logs['suspect'].mean():.1f}%)")
print(f"IPs suspectes uniques : {all_logs[all_logs['suspect']]['ip'].nunique()}")
print(f"Taux 4xx/5xx suspects : {100*(all_logs[all_logs['suspect']]['status'] >= 400).mean():.1f}%")
/tmp/ipykernel_19798/3197485416.py:72: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect.
sns.barplot(x=top_paths.values, y=top_paths.index, ax=axes[1, 0], palette="Reds_r")
/tmp/ipykernel_19798/3197485416.py:79: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `y` variable to `hue` and set `legend=False` for the same effect.
sns.barplot(x=top_ua.values, y=top_ua.index, ax=axes[1, 1], palette="Oranges_r")
Total requêtes : 1050
Requêtes suspectes : 250 (23.8%)
IPs suspectes uniques : 2
Taux 4xx/5xx suspects : 72.4%
Cellule 3 — Détection d’exfiltration DNS par entropie Shannon#
import math
def entropie_shannon(chaine: str) -> float:
"""Calcule l'entropie de Shannon d'une chaîne."""
if not chaine:
return 0.0
freq = {}
for c in chaine:
freq[c] = freq.get(c, 0) + 1
n = len(chaine)
return -sum((f / n) * math.log2(f / n) for f in freq.values())
# Génération de requêtes DNS synthétiques
np.random.seed(7)
def gen_sous_domaine_normal():
"""Sous-domaine légitime : mots simples, entropie faible."""
mots = ["www", "mail", "api", "cdn", "static", "app", "blog",
"images", "assets", "media", "docs", "support"]
return ".".join(np.random.choice(mots, np.random.randint(1, 3)))
def gen_sous_domaine_dga():
"""DGA (Domain Generation Algorithm) : pseudo-aléatoire, entropie élevée."""
length = np.random.randint(12, 32)
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
return "".join(np.random.choice(list(chars), length))
def gen_sous_domaine_dns_tunnel():
"""Tunnel DNS (exfiltration) : base64-like, très haute entropie."""
length = np.random.randint(20, 45)
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+="
return "".join(np.random.choice(list(chars), length))
n_normal, n_dga, n_tunnel = 300, 80, 60
dns_data = []
for _ in range(n_normal):
s = gen_sous_domaine_normal()
dns_data.append({"sous_domaine": s, "longueur": len(s),
"entropie": entropie_shannon(s), "type": "Légitime"})
for _ in range(n_dga):
s = gen_sous_domaine_dga()
dns_data.append({"sous_domaine": s, "longueur": len(s),
"entropie": entropie_shannon(s), "type": "DGA"})
for _ in range(n_tunnel):
s = gen_sous_domaine_dns_tunnel()
dns_data.append({"sous_domaine": s, "longueur": len(s),
"entropie": entropie_shannon(s), "type": "Tunnel DNS"})
df_dns = pd.DataFrame(dns_data)
SEUIL_ENTROPIE = 3.5
SEUIL_LONGUEUR = 20
sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Scatter entropie vs longueur
palette = {"Légitime": "#4CAF50", "DGA": "#FF9800", "Tunnel DNS": "#F44336"}
for type_dns, grp in df_dns.groupby("type"):
axes[0].scatter(grp["longueur"], grp["entropie"],
label=type_dns, alpha=0.65, s=40,
color=palette[type_dns], edgecolors="none")
axes[0].axhline(y=SEUIL_ENTROPIE, color="#9C27B0", linestyle="--", linewidth=1.5,
label=f"Seuil entropie ({SEUIL_ENTROPIE})")
axes[0].axvline(x=SEUIL_LONGUEUR, color="#2196F3", linestyle=":", linewidth=1.5,
label=f"Seuil longueur ({SEUIL_LONGUEUR})")
axes[0].set_xlabel("Longueur du sous-domaine")
axes[0].set_ylabel("Entropie de Shannon (bits)")
axes[0].set_title("Détection DNS : entropie vs longueur", fontweight="bold")
axes[0].legend(fontsize=9)
# Distribution des entropies par type
for type_dns, grp in df_dns.groupby("type"):
axes[1].hist(grp["entropie"], bins=20, alpha=0.6, label=type_dns,
color=palette[type_dns], density=True)
axes[1].axvline(x=SEUIL_ENTROPIE, color="#9C27B0", linestyle="--", linewidth=1.5,
label=f"Seuil ({SEUIL_ENTROPIE})")
axes[1].set_xlabel("Entropie de Shannon (bits)")
axes[1].set_ylabel("Densité")
axes[1].set_title("Distribution des entropies par type de DNS", fontweight="bold")
axes[1].legend(fontsize=9)
plt.suptitle("Détection d'exfiltration DNS par analyse d'entropie", fontsize=13, fontweight="bold")
plt.subplots_adjust(wspace=0.3)
plt.show()
# Statistiques de détection
detectes = df_dns[(df_dns["entropie"] > SEUIL_ENTROPIE) | (df_dns["longueur"] > SEUIL_LONGUEUR)]
vrais_positifs = detectes[detectes["type"] != "Légitime"]
faux_positifs = detectes[detectes["type"] == "Légitime"]
print(f"Seuil entropie : {SEUIL_ENTROPIE} | Seuil longueur : {SEUIL_LONGUEUR}")
print(f"Total détectés : {len(detectes)} / {len(df_dns)}")
print(f"Vrais positifs (DGA + Tunnel) : {len(vrais_positifs)}")
print(f"Faux positifs (légitimes) : {len(faux_positifs)}")
print(f"Précision : {100 * len(vrais_positifs) / len(detectes):.1f}%")
print(f"Rappel : {100 * len(vrais_positifs) / (n_dga + n_tunnel):.1f}%")
Seuil entropie : 3.5 | Seuil longueur : 20
Total détectés : 131 / 440
Vrais positifs (DGA + Tunnel) : 128
Faux positifs (légitimes) : 3
Précision : 97.7%
Rappel : 91.4%
Résumé#
Ce chapitre a couvert le cycle complet de réponse aux incidents et les principales techniques forensiques.
Points clés :
Cycle NIST SP 800-61r3 : six phases (Préparation → Détection → Containment → Éradication → Récupération → Leçons apprises), chacune conditionnant la suivante — la préparation est la plus critique.
Playbooks SOAR : automatisent les actions répétitives et réduisent le MTTR ; Cortex XSOAR et Splunk SOAR sont les leaders du marché, TheHive/Cortex l’alternative open-source.
Forensique disque : la chaîne de custody et l’usage d’un write-blocker sont obligatoires pour la recevabilité légale ;
dcflddintègre le hashing pour vérifier l’intégrité de l’image.Forensique mémoire : LiME (Linux) et winpmem (Windows) pour l’acquisition ; Volatility 3 avec ses plugins
pslist,netscan,malfindpour l’analyse — les artefacts volatils (connexions actives, malwares fileless) n’existent que dans la RAM.Forensique réseau :
tcpdumppour la capture, Zeek pour l’analyse automatique de protocoles, reconstruction de flux avectcpflow.Timeline multi-sources : la corrélation de logs hétérogènes (système + réseau + applicatif) est indispensable pour reconstituer la kill chain ;
plaso/log2timelineautomatise cette tâche.Détection DNS par entropie : les tunnels DNS et les DGAs présentent une entropie de Shannon nettement supérieure aux sous-domaines légitimes — un seuil à 3,5 bits permet une détection efficace avec peu de faux positifs.
Post-mortem blameless : la culture du post-mortem sans désignation de coupable est la condition d’une amélioration continue réelle ; chaque incident doit se conclure par des actions correctives mesurables.