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 Log

  • Logs 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#

Hide code cell source

import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns
from datetime import datetime, timedelta
import random

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
random.seed(42)
np.random.seed(42)
# 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")
_images/07d5f092c8ab806502764befef139dae21a989ef039bb01b692ed72a8917f8cf.png
É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")
_images/50a252cb088af789770176d1332b152d0c85e8e09600fb78bd24acadea6835c7.png
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}%")
_images/0be51d6f7e9964576c05341bc0c8bb1715f19d35bcbeef5a87265e628592b08a.png
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 :

  1. 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.

  2. 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.

  3. Forensique disque : la chaîne de custody et l’usage d’un write-blocker sont obligatoires pour la recevabilité légale ; dcfldd intègre le hashing pour vérifier l’intégrité de l’image.

  4. Forensique mémoire : LiME (Linux) et winpmem (Windows) pour l’acquisition ; Volatility 3 avec ses plugins pslist, netscan, malfind pour l’analyse — les artefacts volatils (connexions actives, malwares fileless) n’existent que dans la RAM.

  5. Forensique réseau : tcpdump pour la capture, Zeek pour l’analyse automatique de protocoles, reconstruction de flux avec tcpflow.

  6. 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/log2timeline automatise cette tâche.

  7. 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.

  8. 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.