---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# Sécurité réseau

La sécurité réseau est l'ensemble des pratiques, technologies et politiques destinées à protéger l'infrastructure de communication contre les accès non autorisés, les modifications malveillantes et les interruptions de service. Dans ce chapitre, nous examinons les menaces les plus courantes couche par couche, puis les contre-mesures que les ingénieurs déploient pour y faire face.

```{code-cell} python
:tags: [hide-input]

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patheffects as pe
import numpy as np
import pandas as pd
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted")
```

## Modèle de menaces : CIA et surface d'attaque

### La triade CIA

Le cadre fondamental de la sécurité de l'information repose sur trois propriétés, connues sous l'acronyme **CIA** :

- **Confidentialité** (*Confidentiality*) : seules les entités autorisées peuvent lire les données.
- **Intégrité** (*Integrity*) : les données ne peuvent pas être modifiées à l'insu des parties légitimes.
- **Disponibilité** (*Availability*) : les ressources restent accessibles quand les utilisateurs en ont besoin.

Une attaque peut cibler l'une ou plusieurs de ces propriétés simultanément. Par exemple, une attaque DDoS vise la disponibilité ; l'écoute passive (sniffing) vise la confidentialité ; une attaque Man-in-the-Middle peut cibler les trois.

```{code-cell} python
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_aspect('equal')
ax.axis('off')
ax.set_title("La triade CIA — piliers de la sécurité de l'information", fontsize=14, fontweight='bold', pad=20)

triangle = plt.Polygon([[5, 8.5], [1.5, 2.5], [8.5, 2.5]], closed=True,
                        fill=True, facecolor='#e8f4f8', edgecolor='#2c7bb6', linewidth=2.5)
ax.add_patch(triangle)

labels = [
    (5, 9.2, "Confidentialité", "#d73027", "Seules les parties\nautorisées lisent\nles données"),
    (0.8, 1.8, "Intégrité", "#1a9850", "Les données ne\nsont pas altérées\nà l'insu des parties"),
    (9.2, 1.8, "Disponibilité", "#4575b4", "Les ressources\nrestent accessibles\naux utilisateurs"),
]
for x, y, titre, couleur, desc in labels:
    ax.text(x, y, titre, ha='center', va='center', fontsize=13, fontweight='bold', color=couleur)
    ax.text(x, y - 0.9, desc, ha='center', va='center', fontsize=8.5, color='#333333')

exemples = [
    (3.2, 5.8, "Chiffrement\nTLS", "#d73027"),
    (6.8, 5.8, "Signatures\nnumériques", "#1a9850"),
    (5, 3.2, "Redondance\nDDoS mitigation", "#4575b4"),
]
for x, y, texte, couleur in exemples:
    ax.text(x, y, texte, ha='center', va='center', fontsize=8, color=couleur,
            style='italic', bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor=couleur, alpha=0.8))

plt.tight_layout()
plt.savefig('_static/cia_triad.png', dpi=100, bbox_inches='tight')
plt.show()
```

### Surface d'attaque et vecteurs

La **surface d'attaque** désigne l'ensemble des points d'entrée qu'un attaquant peut exploiter. Elle comprend :

- **Les interfaces réseau** : ports ouverts, protocoles exposés (HTTP, SSH, RDP…).
- **Les protocoles réseau eux-mêmes** : ARP, DNS, BGP ont été conçus sans authentification.
- **Les logiciels** : serveurs web, bibliothèques, firmware des équipements.
- **Les utilisateurs** : phishing, ingénierie sociale.

```{admonition} Principe du moindre privilège
:class: tip
Réduire la surface d'attaque passe avant tout par le principe du moindre privilège : n'exposer que les services strictement nécessaires, fermer les ports inutilisés, et limiter les permissions des processus.
```

---

## Attaques sur la couche liaison

### ARP poisoning / spoofing

Le protocole ARP (*Address Resolution Protocol*) fait correspondre une adresse IP à une adresse MAC sur un réseau local. Comme ARP ne prévoit aucune authentification, n'importe quelle machine peut envoyer une réponse ARP gratuite (*gratuitous ARP*) pour empoisonner le cache ARP des autres hôtes.

**Mécanisme :**

1. L'attaquant envoie des réponses ARP non sollicitées à la victime A : « L'IP de B correspond à ma MAC. »
2. Il envoie également des réponses à B : « L'IP de A correspond à ma MAC. »
3. Tout le trafic entre A et B transite désormais par l'attaquant (MitM).

```{code-cell} python
# Simulation pédagogique d'un cache ARP et de son empoisonnement
import struct
import time

class CacheARP:
    """Cache ARP simplifié — associe IP → MAC avec horodatage."""

    def __init__(self):
        self._table: dict[str, tuple[str, float]] = {}

    def apprendre(self, ip: str, mac: str):
        self._table[ip] = (mac, time.time())

    def resoudre(self, ip: str) -> str | None:
        entree = self._table.get(ip)
        return entree[0] if entree else None

    def afficher(self):
        print(f"{'IP':<18} {'MAC':<20} {'Âge (s)':<10}")
        print("-" * 50)
        now = time.time()
        for ip, (mac, ts) in self._table.items():
            print(f"{ip:<18} {mac:<20} {now - ts:.2f}")

# État initial — réseau légitime
cache_a = CacheARP()
cache_a.apprendre("192.168.1.1", "aa:bb:cc:dd:ee:01")  # Routeur légitime
cache_a.apprendre("192.168.1.2", "aa:bb:cc:dd:ee:02")  # Hôte B légitime

print("=== Cache ARP de A (état légitime) ===")
cache_a.afficher()

# Empoisonnement : l'attaquant (MAC ff:ff:ff:ff:ff:aa) se fait passer pour le routeur et pour B
print("\n=== Après ARP poisoning par l'attaquant ===")
cache_a.apprendre("192.168.1.1", "ff:ff:ff:ff:ff:aa")  # Le routeur pointe vers l'attaquant
cache_a.apprendre("192.168.1.2", "ff:ff:ff:ff:ff:aa")  # B pointe vers l'attaquant
cache_a.afficher()

print("\n⚠  Tout le trafic de A vers le routeur et vers B transite maintenant par l'attaquant.")
```

```{code-cell} python
# Construction manuelle d'une trame ARP gratuite avec struct (pédagogique)
def forge_arp_gratuit(mac_src: str, ip_src: str) -> bytes:
    """
    Forge une réponse ARP gratuite (non envoyée — uniquement illustratif).
    Format : Ethernet header + ARP payload.
    """
    def mac_bytes(mac: str) -> bytes:
        return bytes(int(x, 16) for x in mac.split(':'))

    def ip_bytes(ip: str) -> bytes:
        return bytes(int(x) for x in ip.split('.'))

    mac_src_b = mac_bytes(mac_src)
    mac_bcast  = b'\xff\xff\xff\xff\xff\xff'
    ip_src_b   = ip_bytes(ip_src)

    # En-tête Ethernet : dst(6) src(6) type(2)
    eth = mac_bcast + mac_src_b + b'\x08\x06'

    # Payload ARP (RFC 826)
    # htype=1 (Ethernet), ptype=0x0800 (IPv4), hlen=6, plen=4
    # oper=2 (reply), sha, spa, tha, tpa
    arp = struct.pack('!HHBBH',
                      1,       # htype : Ethernet
                      0x0800,  # ptype : IPv4
                      6,       # hlen
                      4,       # plen
                      2)       # oper : reply
    arp += mac_src_b + ip_src_b   # SHA + SPA (expéditeur)
    arp += mac_bcast + ip_src_b   # THA + TPA (cible = broadcast pour gratuitous)

    trame = eth + arp
    return trame

trame = forge_arp_gratuit("de:ad:be:ef:00:01", "192.168.1.1")
print(f"Trame ARP gratuite forgée : {len(trame)} octets")
print(f"Hexdump : {trame.hex(' ')}")
print(f"\nEn-tête Ethernet (14 octets) : {trame[:14].hex(' ')}")
print(f"Payload ARP (28 octets)      : {trame[14:].hex(' ')}")
```

```{admonition} Contre-mesures ARP
:class: note
- **Dynamic ARP Inspection (DAI)** sur les commutateurs managés : valide les mappages ARP contre une table DHCP snooping.
- **ARP statique** pour les équipements critiques (routeurs, serveurs).
- **Détection** : surveiller les changements fréquents de MAC pour une même IP (journaux du commutateur).
```

### MAC flooding

Un commutateur maintient une **table CAM** (*Content Addressable Memory*) associant port ↔ MAC. Si un attaquant inonde le commutateur avec des milliers de fausses MAC, la table sature et le commutateur se comporte comme un concentrateur (*hub*), diffusant tout le trafic sur tous les ports.

**Contre-mesure** : *Port Security* — limiter le nombre de MAC autorisées par port.

---

## Attaques sur la couche réseau

### IP spoofing

L'en-tête IP ne contient pas de mécanisme d'authentification de la source. Un attaquant peut donc forger l'adresse IP source de ses paquets.

**Usages malveillants :**
- Masquer l'origine réelle lors d'une attaque DDoS.
- Exploiter des protocoles qui font confiance à l'adresse source (rsh, anciennes imprimantes).
- Amplification DNS/NTP (les réponses sont envoyées à la victime usurpée).

**Contre-mesure** : *BCP 38 / uRPF (Unicast Reverse Path Forwarding)* — les routeurs vérifient que le paquet entrant provient bien d'une interface par laquelle ils pourraient atteindre la source déclarée.

### ICMP redirect

Un routeur peut légitimement envoyer un message ICMP Redirect pour indiquer à un hôte qu'il existe un meilleur chemin. Un attaquant sur le même segment peut exploiter ce mécanisme pour rediriger le trafic vers lui-même.

**Contre-mesure** : désactiver l'acceptation des ICMP Redirect sur les hôtes Linux (`sysctl -w net.ipv4.conf.all.accept_redirects=0`).

### BGP hijacking

BGP (*Border Gateway Protocol*) est le protocole de routage inter-domaines d'Internet. Il repose sur la confiance mutuelle entre opérateurs (*AS — Autonomous Systems*). Un AS malveillant (ou compromis) peut annoncer des préfixes IP qu'il ne possède pas légitimement, détournant ainsi le trafic mondial.

```{admonition} Exemples historiques
:class: warning
- **2010** : China Telecom (AS23724) a annoncé ~50 000 préfixes étrangers pendant 18 minutes, capturant du trafic destiné à des services US, européens et asiatiques.
- **2018** : Détournement BGP visant des serveurs DNS Amazon Route53 pour dérober des cryptomonnaies.
- **2022** : Multiples incidents liés à des erreurs de configuration (*route leaks*) chez des opérateurs européens.
```

**Contre-mesure : RPKI (Resource Public Key Infrastructure)** — lie cryptographiquement chaque préfixe IP à son AS légitimes via des *Route Origin Authorizations* (ROA).

```{code-cell} python
fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# --- Schéma BGP hijacking ---
ax = axes[0]
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.axis('off')
ax.set_title("BGP Hijacking — mécanisme", fontsize=12, fontweight='bold')

as_legit  = dict(xy=(2, 6), label="AS légitime\n(10.0.0.0/8)", color='#2ca02c')
as_victime = dict(xy=(8, 6), label="Victime\n(destinataire)", color='#1f77b4')
as_attaq  = dict(xy=(2, 2), label="AS attaquant\n(annonce 10.0.0.0/8)", color='#d62728')
as_isp    = dict(xy=(5, 4), label="ISP / IX\n(reçoit 2 annonces)", color='#ff7f0e')

for nd in [as_legit, as_victime, as_attaq, as_isp]:
    x, y = nd['xy']
    ax.add_patch(mpatches.FancyBboxPatch((x-1.2, y-0.5), 2.4, 1.1,
                 boxstyle="round,pad=0.1", facecolor=nd['color'], alpha=0.2,
                 edgecolor=nd['color'], linewidth=2))
    ax.text(x, y + 0.05, nd['label'], ha='center', va='center', fontsize=8.5, fontweight='bold')

flèches = [
    (as_legit['xy'],  as_isp['xy'],  "Annonce légitime", '#2ca02c', '--'),
    (as_attaq['xy'],  as_isp['xy'],  "Annonce forgée", '#d62728', '-'),
    (as_isp['xy'],    as_victime['xy'], "Trafic dévié →\nvers attaquant", '#d62728', '-'),
]
for (x1,y1),(x2,y2),label,col,ls in flèches:
    ax.annotate("", xy=(x2,y2), xytext=(x1,y1),
                arrowprops=dict(arrowstyle='->', color=col, lw=2, linestyle=ls))
    mx, my = (x1+x2)/2, (y1+y2)/2
    ax.text(mx+0.1, my+0.15, label, fontsize=7.5, color=col, ha='center')

# --- Évolution du déploiement RPKI ---
ax2 = axes[1]
années = [2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]
couverture = [5, 10, 18, 28, 40, 52, 62, 70]
ax2.bar(années, couverture, color='#4575b4', alpha=0.75, edgecolor='white')
ax2.plot(années, couverture, 'o-', color='#d73027', linewidth=2, markersize=6)
ax2.set_xlabel("Année", fontsize=11)
ax2.set_ylabel("% préfixes IPv4 couverts par RPKI", fontsize=11)
ax2.set_title("Adoption de RPKI dans le monde", fontsize=12, fontweight='bold')
ax2.set_ylim(0, 100)
for x, y in zip(années, couverture):
    ax2.text(x, y+1.5, f"{y}%", ha='center', fontsize=8.5, color='#333333')

plt.tight_layout()
plt.savefig('_static/bgp_hijacking.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## Man-in-the-Middle (MitM)

Un attaquant positionné entre deux parties peut **lire, modifier et réinjecter** des paquets à la volée. La technique repose souvent sur une combinaison d'ARP poisoning (pour se placer sur le chemin) et d'interception applicative.

### SSL stripping

Le SSL stripping (Moxie Marlinspike, 2009) consiste à dégrader une connexion HTTPS en HTTP :

1. L'utilisateur tape `http://banque.fr` (ou clique un lien non-HTTPS).
2. L'attaquant MitM intercepte la requête, établit une connexion HTTPS avec le serveur, mais sert de l'HTTP à la victime.
3. La victime croit naviguer en clair ; l'attaquant voit tout.

### HSTS — contre-mesure

**HTTP Strict Transport Security** (RFC 6797) force le navigateur à n'utiliser HTTPS que pour un domaine donné, pendant une durée maximale. Après une première visite HTTPS, le navigateur refuse tout accès HTTP à ce domaine.

```
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
```

La liste **HSTS Preload** va plus loin : les navigateurs embarquent en dur une liste de domaines qui sont toujours HTTPS, même pour la toute première visite.

```{code-cell} python
fig, ax = plt.subplots(figsize=(12, 5))
ax.set_xlim(0, 12)
ax.set_ylim(0, 6)
ax.axis('off')
ax.set_title("Mécanisme Man-in-the-Middle avec SSL stripping", fontsize=13, fontweight='bold', pad=15)

entités = [
    (1.2, 3, "Victime\n(navigateur)", '#4575b4'),
    (6,   3, "Attaquant\n(MitM)", '#d62728'),
    (10.8, 3, "Serveur\n(HTTPS)", '#1a9850'),
]
for x, y, label, col in entités:
    ax.add_patch(mpatches.FancyBboxPatch((x-0.9, y-0.6), 1.8, 1.2,
                 boxstyle="round,pad=0.1", facecolor=col, alpha=0.15,
                 edgecolor=col, linewidth=2))
    ax.text(x, y, label, ha='center', va='center', fontsize=10, fontweight='bold', color=col)

étapes = [
    (1.2, 6, "① GET http://banque.fr", 6, '#4575b4', '→'),
    (6,   6, "② GET https://banque.fr", 10.8, '#d62728', '→'),
    (10.8, 6, "③ 200 OK (HTTPS chiffré)", 6, '#1a9850', '←'),
    (6,   6, "④ 200 OK (HTTP en clair!)", 1.2, '#d62728', '←'),
]
y_pos = [5.2, 4.6, 4.0, 3.4]
for i, ((x1,_,texte,x2,col,_), y) in enumerate(zip(étapes, y_pos)):
    x_from = x1
    x_to   = x2
    ax.annotate("", xy=(x_to, y), xytext=(x_from, y),
                arrowprops=dict(arrowstyle='->', color=col, lw=1.8))
    ax.text((x_from+x_to)/2, y+0.18, texte, ha='center', fontsize=8.5, color=col)

ax.text(6, 1.5,
        "L'attaquant voit les données en clair\n(identifiants, cookies…)\nalors que le serveur croit communiquer légitimement.",
        ha='center', va='center', fontsize=9, color='#d62728',
        bbox=dict(boxstyle='round,pad=0.4', facecolor='#fff0f0', edgecolor='#d62728'))

plt.tight_layout()
plt.savefig('_static/mitm_ssl_strip.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## DDoS : attaques par déni de service distribué

Une **attaque DDoS** (*Distributed Denial of Service*) vise à rendre un service indisponible en le submergeant de trafic illégitime provenant de milliers de sources (botnet).

### Amplification DNS/NTP

Ces attaques exploitent des protocoles UDP qui retournent des réponses beaucoup plus volumineuses que les requêtes :

| Protocole | Facteur d'amplification typique |
|-----------|----------------------------------|
| DNS (ANY) | ×50 à ×100 |
| NTP (monlist) | ×200 à ×700 |
| Memcached | ×10 000 à ×51 000 |
| SSDP | ×30 |

Le principe : l'attaquant envoie de petites requêtes UDP avec l'IP source usurpée (celle de la victime). Les serveurs réflecteurs envoient des réponses volumineuses directement à la victime.

### SYN flood

Exploite le three-way handshake TCP : l'attaquant envoie des millions de SYN avec des IP sources forgées. Le serveur alloue des ressources pour chaque connexion semi-ouverte, épuisant sa mémoire.

**Contre-mesure : SYN cookies** — le serveur encode l'état de la connexion dans le numéro de séquence et n'alloue des ressources qu'après réception du ACK final.

```{code-cell} python
fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# Amplification
ax1 = axes[0]
protocoles = ['DNS\n(ANY)', 'NTP\n(monlist)', 'SSDP', 'Memcached']
facteurs = [75, 450, 30, 30000]
couleurs_amp = ['#4575b4', '#d73027', '#fee090', '#d62728']

bars = ax1.barh(protocoles, facteurs, color=couleurs_amp, edgecolor='white', height=0.5)
ax1.set_xscale('log')
ax1.set_xlabel("Facteur d'amplification (échelle log)", fontsize=11)
ax1.set_title("Amplification DDoS par protocole", fontsize=12, fontweight='bold')
for bar, val in zip(bars, facteurs):
    ax1.text(val * 1.1, bar.get_y() + bar.get_height()/2,
             f"×{val:,}", va='center', fontsize=9, color='#333333')

# SYN flood timeline
ax2 = axes[1]
t = np.linspace(0, 20, 500)
trafic_normal = 100 + 10 * np.sin(2 * np.pi * t / 10) + np.random.normal(0, 5, 500)
trafic_ddos = trafic_normal.copy()
trafic_ddos[200:] = trafic_normal[200:] + 1800 * np.exp(-((t[200:] - 12) ** 2) / 4)

ax2.fill_between(t, trafic_normal, alpha=0.5, color='#4575b4', label='Trafic légitime')
ax2.fill_between(t, trafic_ddos, trafic_normal, alpha=0.6, color='#d62728', label='SYN flood')
ax2.axvline(x=8, color='#d62728', linestyle='--', linewidth=1.5)
ax2.text(8.2, 1600, "Début\nattaque", fontsize=8.5, color='#d62728')
ax2.set_xlabel("Temps (secondes)", fontsize=11)
ax2.set_ylabel("Paquets/seconde", fontsize=11)
ax2.set_title("Impact d'un SYN flood sur le trafic", fontsize=12, fontweight='bold')
ax2.legend(fontsize=10)

plt.tight_layout()
plt.savefig('_static/ddos_viz.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## Scanning de ports

Le scanning de ports est une technique de reconnaissance permettant de déterminer quels services sont exposés sur une machine. C'est à la fois un outil légitime d'audit et une phase préparatoire à une attaque.

### Méthodes principales

| Méthode | Description | Avantage |
|---------|-------------|----------|
| TCP Connect | `connect()` complet | Fonctionne sans privilèges |
| TCP SYN (half-open) | Envoie SYN, analyse SYN-ACK ou RST | Moins visible dans les logs |
| UDP scan | Envoie UDP vide, analyse ICMP port unreachable | Lent, peu fiable |
| ACK scan | Détecte les firewalls stateful | Cartographie des règles |
| FIN/NULL/Xmas | Exploite les ambiguïtés RFC | Contourne certains firewalls |

### Nmap — exemples de commandes

```bash
# Scan TCP SYN rapide des 1000 ports les plus courants
nmap -sS 192.168.1.0/24

# Détection de version et OS
nmap -sV -O 192.168.1.10

# Script NSE pour vulnérabilités SMB
nmap --script smb-vuln-ms17-010 192.168.1.10

# Scan UDP des ports NTP, DNS, SNMP
nmap -sU -p 53,123,161 192.168.1.10

# Scan agressif avec traceroute
nmap -A --traceroute 192.168.1.10
```

```{admonition} Remarque légale
:class: warning
Le scanning de ports sans autorisation explicite est illégal dans de nombreux pays et peut constituer une tentative d'intrusion. N'utilisez Nmap que sur vos propres systèmes ou avec une autorisation écrite.
```

```{code-cell} python
import socket

def scan_tcp_connect(hôte: str, ports: list[int], timeout: float = 0.5) -> dict[int, str]:
    """
    Scan TCP Connect sur l'hôte local (127.0.0.1 uniquement pour ce notebook).
    Retourne {port: état} avec état = 'ouvert' ou 'fermé/filtré'.
    """
    résultats = {}
    for port in ports:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            ret = sock.connect_ex((hôte, port))
            résultats[port] = 'ouvert' if ret == 0 else 'fermé/filtré'
        except OSError:
            résultats[port] = 'erreur'
        finally:
            sock.close()
    return résultats

# Scan de quelques ports courants sur localhost
ports_courants = [22, 80, 443, 3000, 5000, 8080, 8888]
résultats = scan_tcp_connect('127.0.0.1', ports_courants)

print(f"{'Port':<8} {'Service':<15} {'État'}")
print("-" * 35)
services = {22: 'SSH', 80: 'HTTP', 443: 'HTTPS', 3000: 'Dev', 5000: 'Dev', 8080: 'HTTP-alt', 8888: 'Jupyter'}
for port, état in résultats.items():
    icône = "✔" if état == 'ouvert' else "✘"
    print(f"{port:<8} {services.get(port,'?'):<15} {icône} {état}")
```

```{code-cell} python
# Détection de scan par comptage de connexions par IP source (IDS simplifié)
from collections import defaultdict
import random

class DétecteurDeScan:
    """Détecte un comportement de scan de ports par comptage de connexions."""

    def __init__(self, seuil_ports: int = 10, fenêtre_s: int = 5):
        self.seuil_ports = seuil_ports
        self.fenêtre_s   = fenêtre_s
        self._connexions: dict[str, list] = defaultdict(list)
        self._alertes: list[str] = []

    def enregistrer(self, ip_src: str, port_dst: int, timestamp: float):
        now = timestamp
        # Nettoyage des entrées hors fenêtre
        self._connexions[ip_src] = [
            (ts, p) for ts, p in self._connexions[ip_src]
            if now - ts <= self.fenêtre_s
        ]
        self._connexions[ip_src].append((now, port_dst))
        ports_uniques = {p for _, p in self._connexions[ip_src]}
        if len(ports_uniques) >= self.seuil_ports:
            alerte = (f"ALERTE SCAN : {ip_src} → {len(ports_uniques)} ports "
                      f"distincts en {self.fenêtre_s}s")
            if alerte not in self._alertes:
                self._alertes.append(alerte)
                print(alerte)

# Simulation : trafic légitime + scan de port
détecteur = DétecteurDeScan(seuil_ports=8, fenêtre_s=3)

# Trafic légitime (accès répétés aux mêmes ports)
t = 0.0
for _ in range(20):
    détecteur.enregistrer("10.0.0.1", random.choice([80, 443]), t)
    t += 0.2

# Attaquant scannant de nombreux ports rapidement
ip_attaquant = "10.0.0.99"
for port in range(20, 35):
    détecteur.enregistrer(ip_attaquant, port, t)
    t += 0.05

if not détecteur._alertes:
    print("Aucune alerte générée.")
```

---

## Firewalls

Un firewall filtre le trafic réseau selon des règles prédéfinies. Il peut opérer à différents niveaux de la pile OSI.

### Stateless vs Stateful

| Critère | Stateless | Stateful |
|---------|-----------|----------|
| Suivi des connexions | Non | Oui |
| Performance | Très élevée | Élevée |
| Sécurité | Basique | Bonne |
| Exemple | ACL routeur | iptables, nftables |

Un firewall **stateful** maintient une table des connexions établies (*connection tracking*), ce qui lui permet d'autoriser automatiquement les paquets de retour d'une connexion initiée depuis l'intérieur.

### iptables / nftables

```bash
# iptables — politique par défaut DROP, autoriser établi + SSH + HTTP
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Autoriser les connexions établies et associées
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Autoriser SSH (port 22) depuis un réseau de confiance
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT

# Autoriser HTTP et HTTPS
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT

# Journaliser et bloquer le reste
iptables -A INPUT -j LOG --log-prefix "iptables-DROP: "
iptables -A INPUT -j DROP
```

```bash
# nftables — équivalent moderne
nft add table inet filter
nft add chain inet filter input '{ type filter hook input priority 0; policy drop; }'
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input ip saddr 192.168.1.0/24 tcp dport 22 accept
nft add rule inet filter input tcp dport { 80, 443 } accept
nft add rule inet filter input log prefix "nft-drop: " drop
```

### Topologie DMZ

Une **DMZ** (*DeMilitarized Zone*) isole les serveurs exposés à Internet des réseaux internes. Le firewall externe protège la DMZ d'Internet, et le firewall interne protège le réseau interne de la DMZ.

```{code-cell} python
fig, ax = plt.subplots(figsize=(12, 6))
ax.set_xlim(0, 12)
ax.set_ylim(0, 7)
ax.axis('off')
ax.set_title("Architecture réseau avec DMZ — deux firewalls", fontsize=13, fontweight='bold', pad=15)

# Zones
zones = [
    (0, 0, 2.5, 7,   "Internet", '#fee0d2', '#d62728'),
    (2.5, 0, 4.5, 7, "DMZ", '#fff7bc', '#d6a500'),
    (4.5, 0, 7, 7,   "Zone\nInterne", '#e5f5e0', '#1a9850'),
]
for x1, y1, x2, y2, label, fc, ec in zones:
    ax.add_patch(mpatches.FancyBboxPatch((x1+0.05, y1+0.1), x2-x1-0.1, y2-y1-0.2,
                 boxstyle="round,pad=0.1", facecolor=fc, edgecolor=ec, linewidth=2, alpha=0.5))
    ax.text((x1+x2)/2, 6.5, label, ha='center', va='center', fontsize=11,
            fontweight='bold', color=ec)

# Firewalls
for x, label in [(2.5, "FW\nextérieur"), (4.5, "FW\nintérieur")]:
    ax.add_patch(mpatches.FancyBboxPatch((x-0.3, 2.8), 0.6, 1.4,
                 boxstyle="round,pad=0.05", facecolor='#525252', edgecolor='#252525', linewidth=1.5))
    ax.text(x, 3.5, label, ha='center', va='center', fontsize=8, color='white', fontweight='bold')

# Entités
entités_dmz = [
    (3.5, 5.2, "Serveur\nWeb"),
    (3.5, 3.5, "Serveur\nMail"),
    (3.5, 1.8, "Reverse\nProxy"),
]
for x, y, label in entités_dmz:
    ax.add_patch(mpatches.FancyBboxPatch((x-0.6, y-0.4), 1.2, 0.8,
                 boxstyle="round,pad=0.05", facecolor='#fdae61', edgecolor='#d6a500', linewidth=1.5))
    ax.text(x, y, label, ha='center', va='center', fontsize=8)

entités_int = [
    (5.8, 5.2, "Base de\ndonnées"),
    (5.8, 3.5, "Serveur\nApplicatif"),
    (5.8, 1.8, "Postes\nclients"),
]
for x, y, label in entités_int:
    ax.add_patch(mpatches.FancyBboxPatch((x-0.6, y-0.4), 1.2, 0.8,
                 boxstyle="round,pad=0.05", facecolor='#a1d99b', edgecolor='#1a9850', linewidth=1.5))
    ax.text(x, y, label, ha='center', va='center', fontsize=8)

# Internet
ax.add_patch(plt.Circle((1.2, 3.5), 0.6, facecolor='#fc8d59', edgecolor='#d62728', linewidth=2))
ax.text(1.2, 3.5, "🌐", ha='center', va='center', fontsize=18)

# Flèches
ax.annotate("", xy=(2.2, 3.5), xytext=(1.8, 3.5),
            arrowprops=dict(arrowstyle='->', color='#d62728', lw=2))
ax.annotate("", xy=(4.2, 3.5), xytext=(2.8, 3.5),
            arrowprops=dict(arrowstyle='->', color='#1a9850', lw=2))

plt.tight_layout()
plt.savefig('_static/dmz_architecture.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## VPN

Un **VPN** (*Virtual Private Network*) crée un tunnel chiffré entre deux points, permettant de sécuriser des communications sur un réseau non fiable (Internet).

### WireGuard

WireGuard est un VPN moderne (noyau Linux depuis 5.6), conçu pour être simple, rapide et sécurisé. Il utilise une cryptographie moderne :

- **ChaCha20-Poly1305** : chiffrement authentifié (AEAD).
- **Curve25519** : échange de clés Diffie-Hellman sur courbe elliptique.
- **BLAKE2s** : fonction de hachage.
- **SipHash** : protection des tables de hachage internes.

```bash
# Génération d'une paire de clés WireGuard
wg genkey | tee privatekey | wg pubkey > publickey

# Configuration serveur (/etc/wireguard/wg0.conf)
[Interface]
PrivateKey = <clé_privée_serveur>
Address    = 10.0.0.1/24
ListenPort = 51820

[Peer]
PublicKey  = <clé_publique_client>
AllowedIPs = 10.0.0.2/32

# Configuration client
[Interface]
PrivateKey = <clé_privée_client>
Address    = 10.0.0.2/24

[Peer]
PublicKey  = <clé_publique_serveur>
Endpoint   = vpn.exemple.fr:51820
AllowedIPs = 0.0.0.0/0   # Tout le trafic passe par le VPN
PersistentKeepalive = 25
```

### IPsec

IPsec opère en couche réseau (couche 3) et peut fonctionner en deux modes :

- **Mode transport** : chiffre uniquement la charge utile IP (entre deux hôtes).
- **Mode tunnel** : encapsule l'intégralité du paquet IP (entre deux réseaux, via des passerelles).

Il utilise deux protocoles :
- **AH** (*Authentication Header*) : intégrité et authentification, pas de chiffrement.
- **ESP** (*Encapsulating Security Payload*) : intégrité + chiffrement.

### Comparaison

```{code-cell} python
fig, ax = plt.subplots(figsize=(10, 4))
ax.axis('off')

colonnes = ['Protocole', 'Couche OSI', 'Cryptographie', 'Facilité', 'Performance', 'Usage typique']
données = [
    ['WireGuard', '3 (réseau)', 'ChaCha20/Poly1305\nCurve25519', '★★★★★', '★★★★★', 'VPN moderne, cloud'],
    ['OpenVPN', '3-4', 'TLS (OpenSSL)', '★★★★', '★★★', 'Entreprise, clients légers'],
    ['IPsec/IKEv2', '3 (réseau)', 'AES-GCM, SHA-2', '★★★', '★★★★', 'VPN site-à-site, mobiles'],
    ['L2TP/IPsec', '2-3', 'IPsec pour chiffrement', '★★★', '★★★', 'Héritage, Windows natif'],
]

table = ax.table(cellText=données, colLabels=colonnes,
                 cellLoc='center', loc='center',
                 colWidths=[0.15, 0.12, 0.22, 0.12, 0.14, 0.22])
table.auto_set_font_size(False)
table.set_fontsize(9)
table.scale(1, 2.2)

for (row, col), cell in table.get_celld().items():
    if row == 0:
        cell.set_facecolor('#2c7bb6')
        cell.set_text_props(color='white', fontweight='bold')
    elif row % 2 == 0:
        cell.set_facecolor('#f0f8ff')
    cell.set_edgecolor('#cccccc')

ax.set_title("Comparaison des protocoles VPN", fontsize=13, fontweight='bold', pad=20)
plt.tight_layout()
plt.savefig('_static/vpn_comparison.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## IDS / IPS

Un **IDS** (*Intrusion Detection System*) surveille le réseau et génère des alertes. Un **IPS** (*Intrusion Prevention System*) peut également bloquer le trafic suspect en temps réel.

### Snort et Suricata

**Snort** (Cisco) et **Suricata** (OISF) sont les deux IDS/IPS open source les plus utilisés. Suricata supporte nativement le multi-threading et l'inspection TLS.

**Exemple de règle Snort/Suricata :**

```
# Détection de scan Nmap (paquet TCP avec flags SYN)
alert tcp any any -> $HOME_NET any (
    msg:"Possible scan de port SYN";
    flags:S;
    threshold: type both, track by_src, count 20, seconds 3;
    sid:1000001; rev:1;
)

# Détection d'une tentative d'exploitation EternalBlue (MS17-010)
alert smb $EXTERNAL_NET any -> $HOME_NET 445 (
    msg:"ET EXPLOIT EternalBlue SMB Remote Code Execution";
    flow:established,to_server;
    content:"|00 00 00 90 ff|SMB";
    depth:9; offset:4;
    sid:2025869; rev:1;
)
```

---

## Kill chain réseau

Le modèle **Cyber Kill Chain** (Lockheed Martin) décrit les phases d'une attaque réseau :

```{code-cell} python
fig, ax = plt.subplots(figsize=(13, 4))
ax.set_xlim(0, 13)
ax.set_ylim(0, 4)
ax.axis('off')
ax.set_title("Cyber Kill Chain — phases d'une attaque réseau ciblée", fontsize=13, fontweight='bold', pad=10)

phases = [
    ("1. Reconnaissance", "Nmap, OSINT\nWHOIS, Shodan"),
    ("2. Armement", "Exploit kit\nPayload forgé"),
    ("3. Livraison", "Phishing\nExploit web"),
    ("4. Exploitation", "RCE, 0-day\nPrivilège"),
    ("5. Installation", "Backdoor\nRootkit, C2"),
    ("6. C2", "Beacon HTTPS\nDNS tunneling"),
    ("7. Objectif", "Exfiltration\nRansomware"),
]
couleurs_kc = ['#4575b4', '#74add1', '#abd9e9', '#fee090', '#fdae61', '#f46d43', '#d73027']

for i, ((titre, desc), col) in enumerate(zip(phases, couleurs_kc)):
    x = 0.8 + i * 1.75
    ax.add_patch(mpatches.FancyBboxPatch((x-0.75, 0.8), 1.5, 2.2,
                 boxstyle="round,pad=0.1", facecolor=col, alpha=0.8,
                 edgecolor='white', linewidth=2))
    ax.text(x, 2.35, titre, ha='center', va='center', fontsize=7.5,
            fontweight='bold', color='white', wrap=True)
    ax.text(x, 1.5, desc, ha='center', va='center', fontsize=7,
            color='white', style='italic')
    if i < len(phases) - 1:
        ax.annotate("", xy=(x+0.85, 1.9), xytext=(x+0.75, 1.9),
                    arrowprops=dict(arrowstyle='->', color='#333333', lw=2))

plt.tight_layout()
plt.savefig('_static/kill_chain.png', dpi=100, bbox_inches='tight')
plt.show()
```

---

## Résumé

```{code-cell} python
fig, ax = plt.subplots(figsize=(11, 6))
ax.axis('off')

données_résumé = [
    ['ARP poisoning', 'Liaison (2)', 'Confidentialité\nIntégrité', 'DAI, ARP statique'],
    ['MAC flooding', 'Liaison (2)', 'Disponibilité\nConfidentialité', 'Port Security'],
    ['IP spoofing', 'Réseau (3)', 'Confidentialité', 'BCP38, uRPF'],
    ['BGP hijacking', 'Réseau (3)', 'Disponibilité\nIntégrité', 'RPKI, ROA'],
    ['MitM / SSL strip', 'Transport (4)', 'Confidentialité\nIntégrité', 'HSTS, cert pinning'],
    ['DDoS amplifié', 'Réseau (3-4)', 'Disponibilité', 'Scrubbing center'],
    ['SYN flood', 'Transport (4)', 'Disponibilité', 'SYN cookies'],
    ['Scan de ports', 'Transport (4)', 'Reconnaissance', 'Firewall, IDS/IPS'],
]
cols_résumé = ['Attaque', 'Couche OSI', 'CIA menacé', 'Contre-mesure principale']

table = ax.table(cellText=données_résumé, colLabels=cols_résumé,
                 cellLoc='center', loc='center',
                 colWidths=[0.22, 0.18, 0.25, 0.30])
table.auto_set_font_size(False)
table.set_fontsize(9)
table.scale(1, 2.0)

for (row, col), cell in table.get_celld().items():
    if row == 0:
        cell.set_facecolor('#2c7bb6')
        cell.set_text_props(color='white', fontweight='bold')
    elif row % 2 == 0:
        cell.set_facecolor('#f5f5f5')
    cell.set_edgecolor('#dddddd')

ax.set_title("Synthèse des attaques réseau et contre-mesures", fontsize=13, fontweight='bold', pad=20)
plt.tight_layout()
plt.savefig('_static/synthese_attaques.png', dpi=100, bbox_inches='tight')
plt.show()
```

```{admonition} Points clés du chapitre
:class: tip
- La sécurité réseau repose sur la triade **CIA** : Confidentialité, Intégrité, Disponibilité.
- Les protocoles réseau historiques (ARP, BGP, DNS) ont été conçus sans authentification et doivent être sécurisés par des mécanismes additionnels (DAI, RPKI, DNSSEC).
- Le **MitM** reste efficace lorsque TLS n'est pas imposé ; **HSTS** et le **certificate pinning** en sont les principales contre-mesures.
- Les **DDoS par amplification** exploitent des protocoles UDP réflecteurs ; BCP38 et les scrubbing centers en limitent l'impact.
- **WireGuard** représente l'état de l'art pour les VPN : cryptographie moderne, faible surface d'attaque, performances élevées.
- Les IDS/IPS comme Suricata complètent les firewalls en détectant les comportements anormaux au niveau applicatif.
```
