Aller au contenu
TAAF-OPS --:-- UTC

TP 2 — Alerting & Notification Discord

Acte 2 · SIEM Avancé Grafana Alerting · Discord · Atomic Red Team · flightsim

Auteur : Thibaut Fontaine Institution : Université de la Réunion Date : 2026



📡 Poste SOC · La Réunion — Acte 2, Détecter : vous armez Alfred Faure (Crozet), ~2 900 km. Liaison SATCOM nominale.

Une détection ne sert à rien si personne ne la voit. On ferme la boucle : règle Sigma → requête LogQL → alerte Grafana → notification Discord — sur la même stack Grafana, sans composant externe.

La règle existe (TP 1) mais elle dort. Le jour J, aucune alerte n’a jamais tonné — personne n’avait fermé la boucle. Ici vous la fermez : vous déclenchez l’attaque depuis votre poste, et vous regardez la notification tomber dans Discord. C’est la seconde qui aurait réveillé l’astreinte ce jour-là.

  • Comprendre la chaîne règle ⇒ alert rule ⇒ notification policy ⇒ contact point.
  • Provisionner une alerte par YAML (alerting-as-code) — pas par clics dans l’UI.
  • Configurer des notification policies par sévérité (Discord channel routing).
  • Déclencher l’attaque correspondante (Atomic Red Team / flightsim) et voir la notification tomber.
  • Tuner les faux positifs avec du bruit de fond (GHOSTS).
graph LR
 subgraph "Détection-as-Code (TP1)"
 RULE[/règle.yml
Sigma/] end subgraph "Compilation" SIGMA[sigma convert
-f grafana_alerting] end subgraph "Grafana Alerting" PROV[/provisioning/
alert-rules.yml/] AR[Alert Rule] NP[Notification Policy
severity routing] CP[Contact Point
Discord webhook] end subgraph "Sortie" DISCORD([Discord
#soc-critical
#soc-info]) end RULE --> SIGMA SIGMA --> PROV PROV --> AR AR -->|severity label| NP NP --> CP CP --> DISCORD classDef code fill:#99ff99,stroke:#333,stroke-width:2px classDef alert fill:#ffccff,stroke:#333,stroke-width:2px classDef out fill:#ffcc99,stroke:#333,stroke-width:2px class RULE,SIGMA,PROV code class AR,NP,CP alert class DISCORD out

Vous arrivez avec un repo taaf-detections contenant au moins :

  • rules/initial_access/vpn-auth-external-ip.yml
  • rules/collection/falco-classified-vault-read.yml
  • rules/exfiltration/postgres-genetic-samples-no-approval.yml
  • scripts/compile.sh
  1. Discord → votre serveur de TP → Paramètres du salonIntégrationsCréer un webhook.
  2. Nommez-le TAAF-SOC-bot, choisissez le salon #soc-critical (créez-le si besoin).
  3. Copiez l’URL du webhook : https://discord.com/api/webhooks/<id>/<token>.

Vérifiez :

Fenêtre de terminal
curl -X POST -H "Content-Type: application/json" \
-d '{"content":"Hello from TAAF SOC 👋"}' \
"$DISCORD_WEBHOOK_CRITICAL"

→ Un message doit apparaître dans #soc-critical.

Créez aussi #soc-info (avec son propre webhook → $DISCORD_WEBHOOK_INFO).

  • Atomic Red Team (déjà installé en Acte 0, sinon voir annexe).
  • flightsim : binaire Go pour générer du trafic suspect (DNS exfil, beaconing). Sur la VM soc :
Fenêtre de terminal
which flightsim || (curl -sL https://github.com/alphasoc/flightsim/releases/download/v2.4.0/flightsim_2.4.0_Linux_x86_64.tar.gz \
| tar xz -C /tmp && sudo mv /tmp/flightsim /usr/local/bin/)
flightsim version # → 2.4.0

3. Étape 1 — Webhook Discord + Contact Point Grafana

Section intitulée « 3. Étape 1 — Webhook Discord + Contact Point Grafana »

Ouvrez Grafana (http://IP_VM2:3000) → AlertingContact points+ Add contact point.

ChampValeur
Namediscord-soc-critical
IntegrationDiscord
Webhook URL<votre webhook critical>
Use Discord usernameTAAF-SOC-bot
Avatar URL(laissez vide ou mettez un logo TAAF)
Message Content{{ template "discord.taaf.message" . }}
Use template for message✅ (on créera le template en 3.3)

Test : bouton Test → vous devez voir un message arriver dans #soc-critical.

Recommencez pour discord-soc-info (webhook info).

3.2 — Provisionner par YAML (recommandé pour la traçabilité)

Section intitulée « 3.2 — Provisionner par YAML (recommandé pour la traçabilité) »

Plutôt que de cliquer, déposez dans le repo monitoring le fichier grafana/provisioning/alerting/contact-points.yml :

apiVersion: 1
contactPoints:
- orgId: 1
name: discord-soc-critical
receivers:
- uid: discord_critical
type: discord
settings:
url: ${DISCORD_WEBHOOK_CRITICAL}
use_discord_username: true
username: TAAF-SOC-bot
message: |
{{ template "discord.taaf.message" . }}
- orgId: 1
name: discord-soc-info
receivers:
- uid: discord_info
type: discord
settings:
url: ${DISCORD_WEBHOOK_INFO}
username: TAAF-SOC-bot
message: |
{{ template "discord.taaf.message" . }}

Et dans .env du compose Grafana :

Fenêtre de terminal
DISCORD_WEBHOOK_CRITICAL=https://discord.com/api/webhooks/.../...
DISCORD_WEBHOOK_INFO=https://discord.com/api/webhooks/.../...
Fenêtre de terminal
docker compose restart grafana

Dans Grafana → AlertingContact pointsNotification templates+ Add template :

{{ define "discord.taaf.message" }}
🚨 **{{ .CommonLabels.alertname }}** — {{ .CommonLabels.severity | toUpper }}
{{ range .Alerts }}
**État :** {{ .Status }}
**Règle :** `{{ .Labels.rulename }}` (ATT&CK : {{ .Labels.attack_id }})
**Source :** {{ .Labels.job }}
**Détails :** {{ .Annotations.summary }}
**Lien :** [Explore]({{ .GeneratorURL }})
{{ end }}
{{ end }}

Reprenez le repo taaf-detections du TP1.

4.1 — Conversion avec le format grafana_alerting

Section intitulée « 4.1 — Conversion avec le format grafana_alerting »
Fenêtre de terminal
cd ~/taaf-detections
source ~/sigma-venv/bin/activate
sigma convert \
-t loki \
-f grafana_alerting \
-p loki_grafana_logfmt \
rules/initial_access/vpn-auth-external-ip.yml \
> build/alerts/vpn-auth-external-ip.yml

Sortie attendue (extrait) :

apiVersion: 1
groups:
- orgId: 1
name: taaf-detections
folder: SOC-TAAF
interval: 1m
rules:
- uid: vpn-auth-external-ip
title: VPN Authentication from External IP (TAAF)
condition: A
data:
- refId: A
datasourceUid: loki
model:
expr: |
{job="auth"} | logfmt | service=`vpn` | auth_status=`success`
| src_ip!~`^(10\.42\.|192\.168\.).*`
| src_ip!~`^(198\.51\.100\.10|198\.51\.100\.11)$`
noDataState: OK
execErrState: Error
for: 1m
labels:
severity: high
attack_id: T1078
attack_id_secondary: T1133
annotations:
summary: "Auth VPN externe pour utilisateur TAAF"
description: "Auth VPN réussie depuis IP non-interne, non-partenaire."
#!/usr/bin/env bash
set -euo pipefail
OUT_LOGQL="build/logql"
OUT_ALERTS="build/alerts"
mkdir -p "$OUT_LOGQL" "$OUT_ALERTS"
find rules -name "*.yml" -type f | while read -r rule; do
name=$(basename "$rule" .yml)
echo "[+] $rule"
sigma convert -t loki -p loki_grafana_logfmt "$rule" > "$OUT_LOGQL/$name.logql"
sigma convert -t loki -f grafana_alerting -p loki_grafana_logfmt "$rule" > "$OUT_ALERTS/$name.yml"
done
echo "[✓] $(ls "$OUT_LOGQL" | wc -l) LogQL · $(ls "$OUT_ALERTS" | wc -l) alertes Grafana"
Fenêtre de terminal
./scripts/compile.sh

5.1 — Déposer les alertes dans le provisioning Grafana

Section intitulée « 5.1 — Déposer les alertes dans le provisioning Grafana »

Copiez build/alerts/*.yml dans le volume monté par Grafana :

Fenêtre de terminal
# Depuis le repo monitoring
mkdir -p grafana/provisioning/alerting/rules
cp ~/taaf-detections/build/alerts/*.yml grafana/provisioning/alerting/rules/
# Redémarrer Grafana pour charger
docker compose restart grafana
docker compose logs -f grafana | grep -i alert

Vérification : Grafana → AlertingAlert rules → dossier SOC-TAAF → 3 règles visibles.

Dans l’UI de l’alerte → Preview → exécute la query et montre les résultats actuels. Si vos logs du jour J sont peuplés, la query doit retourner ≥ 1 ligne → l’alerte passera en Pending puis Firing après for: 1m.


6. Étape 4 — Notification policies par sévérité

Section intitulée « 6. Étape 4 — Notification policies par sévérité »

Grafana → AlertingNotification policiesDefault policy+ New nested policy :

ChampValeur
Matching labelsseverity = critical
Contact pointdiscord-soc-critical
Group byalertname, attack_id
Group wait15s
Group interval1m
Repeat interval4h

Recommencez :

| severity = high | discord-soc-critical | | severity = medium | discord-soc-info | | severity = low | discord-soc-info |

grafana/provisioning/alerting/notification-policies.yml :

apiVersion: 1
policies:
- orgId: 1
receiver: discord-soc-info # default
group_by: [alertname]
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
routes:
- receiver: discord-soc-critical
matchers:
- severity =~ "critical|high"
group_by: [alertname, attack_id]
group_wait: 15s
group_interval: 1m
repeat_interval: 4h
- receiver: discord-soc-info
matchers:
- severity =~ "medium|low"
Fenêtre de terminal
docker compose restart grafana
Mauvaise pratiqueSymptôme
Tout en severity: criticalL’astreinte se déconnecte au bout d’1 semaine
Pas de group_by50 alertes pour 1 incident — analyste noyé
repeat_interval: 1mSpam Discord, on désactive le webhook
Pas de silences pour la maintenanceFaux positifs pendant les déploiements

→ Documentez votre politique d’alerting dans le README.md du repo taaf-detections.


Depuis la VM monitoring (où l’AD/VPN est branché — ou simulez via injection de log) :

Fenêtre de terminal
# Simuler une auth VPN depuis 203.0.113.42 (correspond à jour J 09:12Z)
logger -t auth -p auth.info \
'service=vpn user=e.bernard src_ip=203.0.113.42 auth_status=success ts='$(date -u +%FT%TZ)

→ Le log part vers Alloy → Loki. Attendez ~ 1 min (intervalle d’évaluation de l’alerte). → La notification doit tomber dans #soc-critical.

📸 Capture #1 : Grafana → Alert state Firing. 📸 Capture #2 : message Discord avec template rendu.

Simule un beaconing périodique vers une IP “C2” :

Fenêtre de terminal
sudo flightsim run c2 --dry-run # voir ce qui va être généré
sudo flightsim run c2 --no-bypass --max=30

→ Zeek capture le trafic, génère des conn.log → Loki. → Si vous avez écrit une règle Sigma T1071.001 - C2 beaconing, l’alerte tombe.

7.3 — Atomic Red Team — T1213 (Falco read sensitive)

Section intitulée « 7.3 — Atomic Red Team — T1213 (Falco read sensitive) »

Sur la VM monitoring :

Fenêtre de terminal
docker exec -u root nextcloud-alfred-faure \
cat /var/www/html/data/admin.cloud.af/files/CLASSIFIED-VAULT-X/KER-2024-002.fasta \
> /dev/null

→ Falco déclenche Unauthorized Read of CLASSIFIED Vault (user root n’est pas dans vault_authorized_users mappé à NextCloud). → Alerte Grafana Firing → Discord #soc-critical.

📸 Capture #3 : message Discord avec severity=critical, mention du fichier.


8.1 — Générer du bruit de fond bénin (GHOSTS)

Section intitulée « 8.1 — Générer du bruit de fond bénin (GHOSTS) »

GHOSTS simule de l’activité utilisateur “normale” (browsing, mails, ouverture de docs). Sur la VM monitoring :

Fenêtre de terminal
docker run -d --name ghosts-noise \
--network monitoring_default \
-e GHOSTS_USERS=5 \
-e GHOSTS_RATE=normal \
ghcr.io/cmu-sei/ghosts:latest

Laissez tourner 30 min.

Vérifiez #soc-info : combien d’alertes medium/low sont tombées sans qu’il y ait eu d’attaque ?

Ajoutez des filter_* dans les règles Sigma. Exemple — exclure les requêtes PostgreSQL d’adminer qui consultent légitimement genetic_samples pour les admins :

detection:
selection:
query|contains: 'genetic_samples'
approved_by: null
filter_admin_review:
user: 'admin'
application: 'adminer'
condition: selection and not filter_admin_review

Recompilez (./scripts/compile.sh), redéployez (docker compose restart grafana), relancez GHOSTS — comparez le nombre de FP avant/après.

RègleFP avant tuningCause FPFiltre ajoutéFP après tuning
postgres-genetic-samples-no-approval8 / heureAdminer adminfilter_admin_review0
vpn-auth-external-ip00
falco-classified-vault-read2 / heureBackup nightlyvault_authorized_users += backup-cron0

À mentionner dans votre rapport : un paragraphe de ~10 lignes sur le pourquoi du choix Discord (pédagogique) et le plan de migration vers un canal souverain pour la prod.


  1. Repo taaf-detections enrichi :
    • build/alerts/*.yml (3 alertes Grafana compilées)
    • scripts/compile.sh étendu (LogQL + alerts)
    • README.md avec la politique d’alerting (sévérités, groupings, repeat intervals)
  2. Repo monitoring enrichi :
    • grafana/provisioning/alerting/contact-points.yml
    • grafana/provisioning/alerting/notification-policies.yml
    • grafana/provisioning/alerting/rules/*.yml (3 fichiers copiés du build)
    • grafana/templates/discord.taaf.tmpl (notification template)
  3. 3 captures :
    • Grafana Alert state Firing
    • Message Discord #soc-critical (template rendu)
    • Tableau Notification policies
  4. Tableau de tuning FP (markdown ou PDF) — § 8.4
CritèrePondération
Webhook Discord configuré et testé (test message reçu)10 %
Contact points provisionnés par YAML (pas par clics)15 %
Compilation Sigma → grafana_alerting fonctionnelle (3 alertes)20 %
Notification policies par sévérité (≥ 2 routes)15 %
Au moins une attaque déclenchée fait tomber une notification Discord20 %
Tableau de tuning FP rempli avec valeurs réelles10 %
Paragraphe souveraineté + plan de migration10 %

Format de rendu : PDF nom-prenom-promo.pdf sur Moodle.


Fenêtre de terminal
# Sur la VM monitoring
sudo apt install -y powershell
pwsh -c "Install-Module -Name invoke-atomicredteam -Force -Scope CurrentUser; \
Install-AtomicRedTeam -getAtomics -Force"

(Voir § 6.2 — vérifier qu’elle est strictement identique chez l’étudiant.)

  • Webhook hardcodé dans contact-points.yml (sans ${DISCORD_WEBHOOK_CRITICAL}) → push d’un secret sur Git. -15 %.
  • Pas de group_by → la promo a découvert l’effet en TP : 30+ notifications pour 1 attaque. -10 %.
  • for: à 0s → l’alerte tombe puis se résout aussitôt (oscillation). Doit être ≥ 1 min. -5 %.
  • Sévérité non cohérente avec la règle Sigma (level: critical dans Sigma mais severity: medium dans le label Grafana) → -5 %.

À la fin du TP2, l’étudiant a 3 alertes provisionnées qui tombent quand l’attaque est rejouée. Au TP3, on va mesurer la couverture ATT&CK de ces règles (S2AN → Navigator) et construire une corrélation multi-étapes (phishing → puis → escalade → puis → exfil) pour reconstruire la kill chain, pas juste voir des alertes éparses.