📡 TP SISR — Sonde IDS/IPS + alerting mail · 2 séances · 3h

Bloc B3 Option SISR Suricata + Brevo 3h
🎬 Mode présentation (slides) 📄 Export PDF
PublicBTS SIO 2e année, option SISR
DuréeSéance 1 (1h) + Séance 2 (2h) = 3h
LienFait suite au cours théorique 2h — Sécurité proactive
CompétencesB3.4 Assurer la cybersécurité d'une infrastructure réseau
PrérequisLinux 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émentDétail
VM Debian 122 vCPU, 4 GB RAM, 20 GB disque
2 interfaces réseaueth0 admin · eth1 port miroir du switch
Compte Brevo gratuitPlan gratuit = 300 mails/jour
Domaine d'expéditionalerte-ids@<binome>.iris-lab.local ou domaine validé Brevo
VM Kali ou ParrotPour 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èreSuricataSnort 3ZeekWazuh
TypeIDS + IPS réseauIDS + IPS réseauIDS + analyseSIEM + HIDS
IPS inlineOui (NFQUEUE)Oui (DAQ)NonNon
Format règlesSnort-compatSnort natifScript ZeekXML OSSEC
Sortie structuréeEVE JSONAlert JSONJSONJSON
RulesetsET Open, ET ProTalos, ET OpenPackages ZeekWazuh
LicenceGPLv2GPLv3BSD-likeGPLv2

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")'
📦 Rendu fin de séance 1
  1. Tableau comparatif avec justification écrite
  2. systemctl status suricata — actif
  3. Ruleset chargé : wc -l /var/lib/suricata/rules/suricata.rules
  4. 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)

  1. Créer un compte sur brevo.com (plan gratuit 300 mails/jour).
  2. Account → SMTP & API → API Keys → Generate new API key → copier xkeysib-...
  3. 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 severityNiveau interneActionDélai cible
1CRITICALMail immédiat CTO + SOC< 1 min
2HIGHMail immédiat SOC< 5 min
3MEDIUMMail agrégé horaire SOC< 1 h
infoLOWLog 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)

  1. Depuis Kali, relancer le brute force SSH.
  2. /var/log/suricata/eve.json : alerte 9000001 (priority 1 = CRITICAL).
  3. Attendre 1 min (prochain tick du timer).
  4. Boîte mail CRITICAL : mail reçu [IDS-CRITICAL] LOCAL SSH brute force...
  5. Générer 5 alertes severity 3 → observer l'agrégation horaire.
  6. journalctl -u ids-alert.service -f pour 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

  1. Tableau comparatif IDS/IPS + justification.
  2. Extraits annotés de suricata.yaml.
  3. Les 3 règles custom + capture eve.json des alertes.
  4. Script ids_alert.py expliqué.
  5. .service + .timer + env (clé API caviardée).
  6. Captures : mail CRITICAL reçu, mail MEDIUM agrégé.
  7. Schéma de flux (Kali → Suricata → eve.json → script → Brevo → mail) au format DrawIO ou Mermaid.

4. Grille d'évaluation

CritèrePoids
Choix IDS/IPS argumenté10 %
Install Suricata + ruleset ET Open15 %
3 règles custom pertinentes15 %
Script d'alerting fonctionnel (sans doublons)20 %
4 niveaux de priorité opérationnels15 %
Service + timer systemd durcis10 %
Démo end-to-end (mail reçu)10 %
Doc + schéma + zéro emoji5 %

5. Pièges à éviter

  • Interface pas en promiscuous → aucun paquet capturé. Vérifier avec ip link show eth1.
  • HOME_NET mal réglé → règles ne déclenchent rien. Tester avec alert icmp any any -> $HOME_NET any.
  • Clé API Brevo dans le code → non. Fichier env sé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 ?

  1. sudo apt install et-open
  2. sudo suricata-update enable-source et/open
  3. sudo suricata --et-open
  4. sudo 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 ?

  1. severity = 1
  2. severity = 3
  3. severity = 5
  4. 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 ?

  1. En dur dans le script Python
  2. Dans /etc/ids-alerting/env avec permissions 0600, owner root, chargé par systemd
  3. Dans suricata.yaml
  4. 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