📡 TP SISR — Sonde IDS/IPS + alerting mail · 2 séances · 3h
| Public | BTS SIO 2e année, option SISR |
|---|---|
| Durée | Séance 1 (1h) + Séance 2 (2h) = 3h |
| Lien | Fait suite au cours théorique 2h — Sécurité proactive |
| Compétences | B3.4 Assurer la cybersécurité d'une infrastructure réseau |
| Prérequis | Linux en ligne de commande, bases TCP/IP, notions de VLAN |
0. Contexte pédagogique
Mediaschool Nice exploite un SI avec une DMZ (web, mail), un LAN étudiant (VLAN 10), un LAN formateurs (VLAN 20), un VLAN serveurs (VLAN 30) et un pare-feu pfSense. La direction a reçu un courrier CNIL rappelant l'obligation de détecter les intrusions sur les données personnelles (RGPD art. 32 — sécurité du traitement). Ordre du CTO : déployer une sonde réseau IDS avec alerting mail multi-niveaux sous 3h.
1. Objectifs pédagogiques
- Comparer Suricata, Snort 3, Zeek et Wazuh et justifier un choix.
- Installer Suricata sur Debian 12.
- Gérer les rulesets communautaires (ET Open) avec
suricata-update. - Écrire des règles Snort-compatibles adaptées à un contexte métier.
- Parser le flux EVE JSON et extraire les alertes par sévérité.
- Envoyer des alertes mail via Brevo avec 4 niveaux de priorité.
2. Prérequis matériels
| Élément | Détail |
|---|---|
| VM Debian 12 | 2 vCPU, 4 GB RAM, 20 GB disque |
| 2 interfaces réseau | eth0 admin · eth1 port miroir du switch |
| Compte Brevo gratuit | Plan gratuit = 300 mails/jour |
| Domaine d'expédition | alerte-ids@<binome>.iris-lab.local ou domaine validé Brevo |
| VM Kali ou Parrot | Pour générer du trafic d'attaque |
Séance 1 — Choix, install, premières règles (1h)
Étape 1.1 — Comparer et choisir (10 min)
| Critère | Suricata | Snort 3 | Zeek | Wazuh |
|---|---|---|---|---|
| Type | IDS + IPS réseau | IDS + IPS réseau | IDS + analyse | SIEM + HIDS |
| IPS inline | Oui (NFQUEUE) | Oui (DAQ) | Non | Non |
| Format règles | Snort-compat | Snort natif | Script Zeek | XML OSSEC |
| Sortie structurée | EVE JSON | Alert JSON | JSON | JSON |
| Rulesets | ET Open, ET Pro | Talos, ET Open | Packages Zeek | Wazuh |
| Licence | GPLv2 | GPLv3 | BSD-like | GPLv2 |
Recommandation TP : Suricata (polyvalent, doc excellente, ET Open accessible, EVE JSON facile à parser). Wazuh admis pour un contexte host-based.
Étape 1.2 — Installer Suricata (15 min)
sudo apt update
sudo apt install -y suricata jq
suricata --build-info | head -20
Config /etc/suricata/suricata.yaml — interface d'écoute :
af-packet:
- interface: eth1
threads: auto
cluster-id: 99
cluster-type: cluster_flow
defrag: yes
use-mmap: yes
tpacket-v3: yes
Déclarer HOME_NET :
vars:
address-groups:
HOME_NET: "[10.0.10.0/24,10.0.20.0/24,10.0.30.0/24]"
EXTERNAL_NET: "!$HOME_NET"
Activer EVE JSON (si pas déjà) :
outputs:
- eve-log:
enabled: yes
filetype: regular
filename: eve.json
types:
- alert:
metadata: yes
tagged-packets: yes
- http
- dns
- tls
- flow
Étape 1.3 — Télécharger le ruleset ET Open (10 min)
sudo apt install -y suricata-update
sudo suricata-update list-sources
sudo suricata-update enable-source et/open
sudo suricata-update
wc -l /var/lib/suricata/rules/suricata.rules
Étape 1.4 — 3 règles custom (20 min)
Fichier /etc/suricata/rules/local.rules :
# Règle 1 — SSH brute force (> 5 tentatives en 60s)
alert tcp $EXTERNAL_NET any -> $HOME_NET 22 ( \
msg:"LOCAL SSH brute force attempt detected"; \
flow:to_server; \
threshold: type both, track by_src, count 5, seconds 60; \
classtype:attempted-admin; \
sid:9000001; rev:1; priority:1;)
# Règle 2 — Exfiltration DNS (TXT anormalement longues)
alert dns $HOME_NET any -> any any ( \
msg:"LOCAL Suspicious long DNS TXT query (possible exfil)"; \
dns.query; content:"txt"; nocase; \
dns.query; bsize:>100; \
classtype:policy-violation; \
sid:9000002; rev:1; priority:2;)
# Règle 3 — Accès /admin/ depuis EXTERNAL
alert http $EXTERNAL_NET any -> $HOME_NET any ( \
msg:"LOCAL Unauthorized access to internal /admin/"; \
flow:to_server,established; \
http.uri; content:"/admin/"; startswith; \
classtype:web-application-attack; \
sid:9000003; rev:1; priority:1;)
Déclarer le fichier dans la config + tester :
sudo suricata -T -c /etc/suricata/suricata.yaml -v
sudo systemctl restart suricata
sudo systemctl status suricata
Étape 1.5 — Générer une alerte (5 min)
Depuis Kali :
hydra -l fake -P /usr/share/wordlists/rockyou.txt.gz -t 4 ssh://<IP_IRIS>
Sur la VM Suricata :
sudo tail -f /var/log/suricata/eve.json | jq 'select(.event_type=="alert")'
- Tableau comparatif avec justification écrite
systemctl status suricata— actif- Ruleset chargé :
wc -l /var/lib/suricata/rules/suricata.rules - 3 règles custom testées, au moins 1 alerte dans
eve.json
Séance 2 — Alerting mail Brevo multi-niveaux (2h)
Étape 2.1 — Compte Brevo + clé API (15 min)
- Créer un compte sur brevo.com (plan gratuit 300 mails/jour).
- Account → SMTP & API → API Keys → Generate new API key → copier
xkeysib-... - Valider un expéditeur : Senders → Add a sender.
SMTP alternatif : smtp-relay.brevo.com, ports 587/465/2525. Référence : developers.brevo.com/docs/send-a-transactional-email.
Étape 2.2 — Matrice des priorités (10 min)
| Suricata severity | Niveau interne | Action | Délai cible |
|---|---|---|---|
| 1 | CRITICAL | Mail immédiat CTO + SOC | < 1 min |
| 2 | HIGH | Mail immédiat SOC | < 5 min |
| 3 | MEDIUM | Mail agrégé horaire SOC | < 1 h |
| info | LOW | Log seulement | — |
Sujet mail standardisé : [IDS-<NIVEAU>] <signature> depuis <src_ip> — sid=<signature_id>
Étape 2.3 — Script Python (40 min)
Fichier /opt/ids-alerting/ids_alert.py — lecture incrémentale du eve.json via offset persistant (pas de doublons entre runs) :
#!/usr/bin/env python3
"""IDS Alerting — lit eve.json de Suricata, envoie des mails Brevo par niveau.
Usage :
python3 ids_alert.py --eve /var/log/suricata/eve.json \
--state /var/lib/ids-alerting/state.json
"""
import argparse, json, os, sys, time
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from urllib import request, error
BREVO_API_URL = "https://api.brevo.com/v3/smtp/email"
BREVO_API_KEY = os.environ["BREVO_API_KEY"]
SENDER_EMAIL = os.environ["IDS_SENDER_EMAIL"]
SENDER_NAME = os.environ.get("IDS_SENDER_NAME", "IDS Mediaschool")
RECIPIENTS = {
"CRITICAL": json.loads(os.environ["IDS_TO_CRITICAL"]),
"HIGH": json.loads(os.environ["IDS_TO_HIGH"]),
"MEDIUM": json.loads(os.environ["IDS_TO_MEDIUM"]),
}
SEVERITY_MAP = {1: "CRITICAL", 2: "HIGH", 3: "MEDIUM"}
AGGREGATION_WINDOW_SEC = 3600 # MEDIUM : récap horaire
def send_mail(level, subject, html_body):
payload = {
"sender": {"name": SENDER_NAME, "email": SENDER_EMAIL},
"to": RECIPIENTS[level],
"subject": subject,
"htmlContent": html_body,
"tags": [f"ids-{level.lower()}"],
}
req = request.Request(
BREVO_API_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type":"application/json","api-key":BREVO_API_KEY,"Accept":"application/json"},
method="POST",
)
try:
with request.urlopen(req, timeout=10) as resp:
if resp.status not in (200, 201, 202):
print(f"[WARN] Brevo {resp.status}", file=sys.stderr)
except error.HTTPError as exc:
print(f"[ERROR] Brevo HTTP {exc.code}: {exc.read().decode()}", file=sys.stderr)
def format_alert_html(alert):
a = alert.get("alert", {})
return f"""
<h2>IDS Alert</h2>
<ul>
<li><strong>Timestamp :</strong> {alert.get("timestamp","?")}</li>
<li><strong>Signature :</strong> {a.get("signature","?")}</li>
<li><strong>SID :</strong> {a.get("signature_id","?")}</li>
<li><strong>Source :</strong> {alert.get("src_ip","?")}:{alert.get("src_port","?")}</li>
<li><strong>Destination :</strong> {alert.get("dest_ip","?")}:{alert.get("dest_port","?")}</li>
<li><strong>Severity :</strong> {a.get("severity","?")}</li>
</ul>
"""
# (fonctions format_digest_html, load_state, save_state, read_new_lines, main
# identiques au MD de référence — voir tp-sisr-ids-ips-alerting-brevo.md)
Code complet + gestion de la rotation logrotate + déduplication dans le MD de référence BGTO.
Étape 2.4 — Timer systemd (20 min)
/etc/systemd/system/ids-alert.service :
[Unit]
Description=IDS Alerting — Brevo dispatcher
After=suricata.service
Wants=suricata.service
[Service]
Type=oneshot
User=ids-alert
Group=ids-alert
EnvironmentFile=/etc/ids-alerting/env
ExecStart=/usr/bin/python3 /opt/ids-alerting/ids_alert.py \
--eve /var/log/suricata/eve.json \
--state /var/lib/ids-alerting/state.json
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/ids-alerting
ReadOnlyPaths=/var/log/suricata
/etc/systemd/system/ids-alert.timer :
[Unit]
Description=Run IDS Alerting every minute
[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
Unit=ids-alert.service
[Install]
WantedBy=timers.target
/etc/ids-alerting/env (permissions 0600, owner root) :
BREVO_API_KEY=xkeysib-...
IDS_SENDER_EMAIL=alerte-ids@<binome>.iris-lab.local
IDS_SENDER_NAME=IDS Mediaschool
IDS_TO_CRITICAL=[{"email":"cto@iris-lab.local"},{"email":"soc@iris-lab.local"}]
IDS_TO_HIGH=[{"email":"soc@iris-lab.local"}]
IDS_TO_MEDIUM=[{"email":"soc@iris-lab.local"}]
sudo useradd -r -s /usr/sbin/nologin ids-alert
sudo chown -R ids-alert:ids-alert /var/lib/ids-alerting /opt/ids-alerting
sudo chmod 0600 /etc/ids-alerting/env
sudo systemctl daemon-reload
sudo systemctl enable --now ids-alert.timer
sudo systemctl list-timers | grep ids-alert
Étape 2.5 — Test end-to-end (20 min)
- Depuis Kali, relancer le brute force SSH.
/var/log/suricata/eve.json: alerte9000001(priority 1 = CRITICAL).- Attendre 1 min (prochain tick du timer).
- Boîte mail CRITICAL : mail reçu
[IDS-CRITICAL] LOCAL SSH brute force... - Générer 5 alertes severity 3 → observer l'agrégation horaire.
journalctl -u ids-alert.service -fpour suivre.
Étape 2.6 — Bonus si temps
- Passer Suricata en IPS inline sur VLAN serveurs (NFQUEUE + iptables). Doc IPS Concept.
- Dashboard Grafana + Elasticsearch + Filebeat.
- Déduplication LRU (hash sid + src_ip, fenêtre 10 min).
- Rate limiting Brevo : circuit-breaker vers log seul si quota atteint.
3. Rendu attendu
- Tableau comparatif IDS/IPS + justification.
- Extraits annotés de
suricata.yaml. - Les 3 règles custom + capture eve.json des alertes.
- Script
ids_alert.pyexpliqué. .service+.timer+env(clé API caviardée).- Captures : mail CRITICAL reçu, mail MEDIUM agrégé.
- Schéma de flux (Kali → Suricata → eve.json → script → Brevo → mail) au format DrawIO ou Mermaid.
4. Grille d'évaluation
| Critère | Poids |
|---|---|
| Choix IDS/IPS argumenté | 10 % |
| Install Suricata + ruleset ET Open | 15 % |
| 3 règles custom pertinentes | 15 % |
| Script d'alerting fonctionnel (sans doublons) | 20 % |
| 4 niveaux de priorité opérationnels | 15 % |
| Service + timer systemd durcis | 10 % |
| Démo end-to-end (mail reçu) | 10 % |
| Doc + schéma + zéro emoji | 5 % |
5. Pièges à éviter
- Interface pas en promiscuous → aucun paquet capturé. Vérifier avec
ip link show eth1. HOME_NETmal réglé → règles ne déclenchent rien. Tester avecalert icmp any any -> $HOME_NET any.- Clé API Brevo dans le code → non. Fichier
envséparé, 0600, owner root. - Pas de déduplication → un scan = 300 mails en 1 min = quota Brevo cramé.
- Script sans reprise sur rotation logrotate → le champ offset doit être comparé à la taille du fichier (fait dans le script fourni).
- Mode IPS sans phase IDS préalable → risque de couper la prod sur un faux positif. IDS d'abord, IPS ensuite.
🎮 Quiz — Testez vos connaissances
3 QCM · 1 Vrai/Faux · 1 Question ouverte
QCM 1 — suricata-update
Quelle commande active la source ET Open dans Suricata ?
sudo apt install et-opensudo suricata-update enable-source et/opensudo suricata --et-opensudo apt install suricata-rules-et
📋 Correction
B — sudo suricata-update enable-source et/open. Puis lancer sudo suricata-update pour télécharger et compiler. Source : docs.suricata.io — suricata-update.
QCM 2 — Niveau CRITICAL dans notre pipeline
Dans la matrice du TP, quelle valeur de alert.severity Suricata déclenche un mail immédiat CRITICAL ?
- severity = 1
- severity = 3
- severity = 5
- severity = "critical"
📋 Correction
A — severity = 1. Suricata utilise 1-3 (1 = le plus critique). Notre script mappe {1: "CRITICAL", 2: "HIGH", 3: "MEDIUM"}.
QCM 3 — Stockage de la clé API
Où stocke-t-on la clé API Brevo dans le TP ?
- En dur dans le script Python
- Dans
/etc/ids-alerting/envavec permissions 0600, owner root, chargé par systemd - Dans
suricata.yaml - En argument de ligne de commande dans le
.timer
📋 Correction
B. La clé est chargée via EnvironmentFile= dans le service systemd, depuis un fichier en 0600 root. Le script la lit via os.environ. A est inacceptable (secret dans le code), C et D exposent la clé dans les logs / processus.
Vrai / Faux
⬜ Il faut toujours déployer Suricata en IPS inline dès la première semaine, car c'est plus efficace qu'un IDS.
📋 Correction
FAUX. Un IPS inline en production dès le jour 1 présente un risque élevé de faux positif qui coupe un service légitime (ex : backup qui passe pour de l'exfil). La bonne pratique est d'opérer en IDS pendant une phase d'observation, tuner les règles (désactiver, ajuster les seuils), puis basculer en IPS sur les segments critiques une fois la confiance acquise. L'ANSSI recommande cette approche progressive dans ses guides architecture.
Question ouverte
En 5 lignes, expliquez pourquoi la déduplication est essentielle dans le pipeline d'alerting. Donnez un exemple de stratégie simple.
📋 Éléments de réponse
Un seul scan brute force SSH peut générer des centaines d'alertes identiques en quelques secondes. Sans déduplication, chaque alerte déclenche un mail → boîte du SOC noyée, quota Brevo explosé (plan gratuit = 300 mails/jour), risque de loupé le vrai signal. Stratégie simple : hash (sid, src_ip) + fenêtre 10 min dans un cache LRU en mémoire. Si même hash vu dans la fenêtre, on incrémente un compteur et on attend. Passée la fenêtre, on envoie un mail unique avec n × occurrences. Autre approche : s'appuyer sur le threshold natif des règles Suricata pour ne déclencher l'alerte qu'une fois par N occurrences.
Sources officielles
- Suricata docs — docs.suricata.io
- Suricata — EVE JSON Format — docs.suricata.io — EVE JSON
- Suricata — IPS Concept — docs.suricata.io — IPS
- Suricata — suricata-update — docs.suricata.io — Update
- Emerging Threats Open — rules.emergingthreats.net
- Brevo — Transactional email API — developers.brevo.com
- Brevo — SMTP Integration — developers.brevo.com — SMTP
- ANSSI — Passerelle Internet sécurisée — messervices.cyber.gouv.fr
- ANSSI — Bons réflexes intrusion — cert.ssi.gouv.fr
- RGPD art. 32 — cnil.fr
