TP 1 — Détection as Code : Sigma → LogQL
1. Objectif & contexte
Section intitulée « 1. Objectif & contexte »📡 Poste SOC · La Réunion — Acte 2, Détecter : vous armez Alfred Faure (Crozet), ~2 900 km. Liaison SATCOM nominale.
La supervision voit (Acte 1). Ici on lui apprend à reconnaître une menace — sans changer de stack : mêmes logs, même Loki, même Grafana.
Le SIEM n’est pas un produit séparé : c’est de la détection-as-code posée sur la stack d’observabilité de l’Acte 1. L’asset central est la règle Sigma — un standard portable, mappé MITRE ATT&CK, que vous écrivez une fois et qui se compile vers n’importe quel backend (Loki, Splunk, Elastic, Sentinel…).
🔴 Au fil de l’enquête
Section intitulée « 🔴 Au fil de l’enquête »Le jour J, l’attaque a laissé des traces dans vos logs : phishing julie.moreau (08h00), authentification VPN depuis 203.0.113.42 (09h12), ouverture du coffre CLASSIFIED-VAULT-X (10h05), exfil de genetic_samples (10h12). Tant que personne n’écrit la règle qui les reconnaît, elles défilent sans alerte. Ici vous écrivez cette règle — la première qui aurait fait sonner le jour J.
Objectifs pédagogiques
Section intitulée « Objectifs pédagogiques »- Comprendre l’asymétrie SIEM : le coût est dans la règle, pas dans le moteur.
- Écrire une règle Sigma valide (logsource, detection, condition, tags ATT&CK).
- Compiler vers LogQL avec
pySigma-backend-lokiet lire la requête générée. - Détecter 3 étapes concrètes de la kill chain du jour J dans Grafana Explore.
- Poser le repo Git qui servira de base à la CI détection-as-code (TP2/TP3).
Architecture du TP1
Section intitulée « Architecture du TP1 »graph LR subgraph "Sources (Acte 1)" FALCO[Falco<br/>runtime] AUTH[Auth VPN/AD<br/>auth.log] PG[PostgreSQL<br/>access_log] end subgraph "Stockage" LOKI[(Loki<br/>:3100)] end subgraph "Détection-as-Code" RULES[/Repo Git<br/>règles Sigma .yml/] SIGMA[sigma CLI<br/>+ pySigma-backend-loki] LOGQL["LogQL généré"] end subgraph "Investigation" EXPLORE[Grafana Explore<br/>:3000] end FALCO --> LOKI AUTH --> LOKI PG --> LOKI RULES --> SIGMA SIGMA --> LOGQL LOGQL --> EXPLORE LOKI --> EXPLORE classDef src fill:#ffcc99,stroke:#333,stroke-width:2px classDef store fill:#99ccff,stroke:#333,stroke-width:2px classDef code fill:#99ff99,stroke:#333,stroke-width:2px classDef ui fill:#ffccff,stroke:#333,stroke-width:2px class FALCO,AUTH,PG src class LOKI store class RULES,SIGMA,LOGQL code class EXPLORE ui
2. Prérequis
Section intitulée « 2. Prérequis »Sur la VM soc
Section intitulée « Sur la VM soc »vagrant ssh soc
# Vérifier que Python 3 est dispopython3 --version # Python 3.10+ attendu
# Installer Sigma CLI + le backend Lokipython3 -m venv ~/sigma-venvsource ~/sigma-venv/bin/activatepip install --upgrade pippip install sigma-cli==1.0.* pysigma-backend-loki==0.13.* pysigma-pipeline-loki==0.13.*
sigma version # → sigma-cli 1.0.xsigma plugin list # → loki backend, loki pipeline (installés)Vérifier Loki et Grafana
Section intitulée « Vérifier Loki et Grafana »Depuis la VM soc (joint la VM monitoring par IP) :
curl -s http://IP_VM1:3100/ready # → readycurl -s http://IP_VM1:3000/api/health # → {"database":"ok",...}Ouvrez Grafana dans votre navigateur :
http://IP_VM1:3000 (admin / AdminTAAF2024!)Dans Explore (icône boussole), datasource Loki, lancez :
{job=~".+"} | json | __error__=""→ Vous devez voir défiler des logs. Si rien ne s’affiche, vérifiez que la VM monitoring tourne et que les logs de l’incident sont peuplés (Acte 0 — Phase 4).
3. Étape 0 — Reconnaissance des sources Loki (R0)
Section intitulée « 3. Étape 0 — Reconnaissance des sources Loki (R0) »Avant d’écrire une règle, il faut savoir quel champ contient quoi dans Loki. C’est la phase « 90 % de la détection ».
3.1 — Lister les labels et les jobs disponibles
Section intitulée « 3.1 — Lister les labels et les jobs disponibles »Dans Grafana Explore (datasource Loki), cliquez sur Label filters > job :
job attendu | Source |
|---|---|
falco | Événements Falco (runtime sensitive file, exec, network) |
auth | Logs auth VPN + AD (02_auth_vpn_ad.log) |
postgresql-logs | Logs PostgreSQL base_af |
windows | Windows Event Logs (event_id 4624/4728) |
zeek (label log=ssl|conn|arp) | Zeek (réseau, downgrade PQC, MitM) |
nextcloud (label log=audit) | NextCloud admin_audit (accès fichiers) |
sat_telemetry | Télémétrie SATCOM |
3.2 — Reconnaître la structure d’un log Falco
Section intitulée « 3.2 — Reconnaître la structure d’un log Falco »{job="falco"} | json | priority="Critical"Cliquez sur une ligne pour voir les champs JSON : time, rule, priority, output_fields.user.name, output_fields.fd.name…
3.3 — Reconnaître la structure d’un log d’auth
Section intitulée « 3.3 — Reconnaître la structure d’un log d’auth »{job="auth"} |= "julie.moreau"⚠️ Le job auth est du texte syslog brut (pas de logfmt, pas de champs) — regardez une ligne :
… vpn_service[1024]: WARNING User julie.moreau authenticated successfully from external IP 203.0.113.42 ….
On y détecte donc par filtres de ligne (|=), pas par champ. Les seules sources JSON exploitables avec | json sont Falco, Windows, NextCloud et la télémétrie.
3.4 — Comptage rapide pour repérer un pic
Section intitulée « 3.4 — Comptage rapide pour repérer un pic »sum by (job) (count_over_time({job=~".+"}[5m]))→ Repérez quels jobs sont actifs autour des heures clés (08h00, 09h12, 10h05, 10h12).
4. Étape 1 — Écrire sa première règle Sigma (R1)
Section intitulée « 4. Étape 1 — Écrire sa première règle Sigma (R1) »On va écrire 3 règles couvrant 3 étapes de la kill chain du jour J. Vous commencez par la plus simple, puis les deux autres en autonomie.
4.1 — Initialiser le repo
Section intitulée « 4.1 — Initialiser le repo »Sur la VM soc :
mkdir -p ~/taaf-detections/rulescd ~/taaf-detectionsgit initmkdir -p rules/{initial_access,credential_access,collection,exfiltration}echo "*.pyc" > .gitignore4.2 — Règle #1 : VPN depuis une IP externe non listée
Section intitulée « 4.2 — Règle #1 : VPN depuis une IP externe non listée »Intention de détection : l’utilisatrice julie.moreau s’authentifie sur le VPN depuis une IP qui n’est ni sur le réseau interne de la base (10.60.0.0/16) ni sur les IP « connues » (VPN sortants légitimes). C’est l’étape 09h12 du jour J (203.0.113.42).
Créez rules/initial_access/vpn-auth-external-ip.yml :
title: VPN Authentication from External IP (TAAF)id: 8c1a7b4e-1c2d-4f3a-9e1b-5a6f7c8d9e10status: experimentaldescription: | Détecte une authentification VPN réussie pour un utilisateur TAAF depuis une IP source qui n'est pas dans le pool interne (10.60.0.0/16) ni dans les IP de sortie connues (sites partenaires).references: - https://attack.mitre.org/techniques/T1078/ - https://attack.mitre.org/techniques/T1133/author: TAAF SOCdate: 2026/06/16logsource: product: vpn service: authdetection: keywords: - 'WARNING' - 'external IP' condition: keywordsfields: - rawfalsepositives: - Utilisateur en déplacement déclaré (vérifier dans le calendrier RH)level: hightags: - attack.initial_access - attack.t1078 - attack.t1133Lecture du fichier :
logsource: ce que la règle vise (le pipeline choisira le bon job Loki).detection.keywords: les chaînes à chercher dans la ligne — le jobauthétant du texte brut, on détecte les authWARNINGdepuis uneexternal IP(exactement la ligne du jour J).condition: ici, présence des mots-clés.tags: techniques ATT&CK pour le TP3 (couverture).
4.3 — Règle #2 : Lecture non autorisée du coffre CLASSIFIED-VAULT-X
Section intitulée « 4.3 — Règle #2 : Lecture non autorisée du coffre CLASSIFIED-VAULT-X »Intention : Falco lève une alerte quand un fichier de CLASSIFIED-VAULT-X est lu par un utilisateur autre que la liste d’administrateurs habilités. C’est l’étape 10h05 (mouvement latéral vers le serveur de fichiers).
Falco (job falco) remonte déjà, dans le rejeu, une lecture de fichier sensible sous la règle
native Read sensitive file untrusted dont l’output contient le chemin du coffre. On détecte
donc cet événement réel — pas besoin de déployer une règle Falco custom. Écrivez la règle Sigma
(rules/collection/falco-classified-vault-read.yml) :
title: Falco — Sensitive read of CLASSIFIED-VAULT-Xid: a4f7c2d1-9e8b-4a3f-b6c5-1d2e3f4a5b6cstatus: stabledescription: Une alerte Falco Critical sur l'ouverture du coffre CLASSIFIED-VAULT-X par un utilisateur non listé.references: - https://attack.mitre.org/techniques/T1213/author: TAAF SOCdate: 2026/06/16logsource: product: falcodetection: selection: rule: 'Read sensitive file untrusted' keywords: - 'CLASSIFIED-VAULT-X' condition: selection and keywordsfields: - rule - outputlevel: criticaltags: - attack.collection - attack.t12134.4 — Règle #3 (autonomie) : pg_dump de genetic_samples (exfil staging)
Section intitulée « 4.4 — Règle #3 (autonomie) : pg_dump de genetic_samples (exfil staging) »Intention : peu après 10h15, l’attaquant lance un pg_dump de genetic_samples. Falco l’observe au runtime (un programme non-fiable lit un fichier sensible) — l’output contient la chaîne pg_dump.
À VOUS de l’écrire — fichier
rules/exfiltration/falco-pgdump-genetic-samples.yml.Indices :
logsource.product: falcodetection.keywords: ['pg_dump'](la chaîne apparaît dans l’outputFalco)- condition :
keywords- tags :
attack.collection/attack.t1005/attack.exfiltration/attack.t1041
(Corrigé en Annexe.)
5. Étape 2 — Compiler Sigma → LogQL (R2)
Section intitulée « 5. Étape 2 — Compiler Sigma → LogQL (R2) »5.1 — Conversion en ligne de commande
Section intitulée « 5.1 — Conversion en ligne de commande »cd ~/taaf-detectionssource ~/sigma-venv/bin/activate
sigma convert -t loki -p loki_grafana \ rules/initial_access/vpn-auth-external-ip.ymlSortie attendue (approx) :
{job="auth"} |= `WARNING` |= `external IP`5.2 — Conversion en batch (tout le repo)
Section intitulée « 5.2 — Conversion en batch (tout le repo) »Créez scripts/compile.sh :
#!/usr/bin/env bashset -euo pipefailOUT="build/logql"mkdir -p "$OUT"find rules -name "*.yml" -type f | while read -r rule; do name=$(basename "$rule" .yml) echo "[+] $rule → $OUT/$name.logql" sigma convert -t loki -p loki_grafana "$rule" > "$OUT/$name.logql"doneecho "[✓] $(ls "$OUT" | wc -l) règles compilées"chmod +x scripts/compile.sh./scripts/compile.shSortie attendue :
[+] rules/initial_access/vpn-auth-external-ip.yml → build/logql/vpn-auth-external-ip.logql[+] rules/collection/falco-classified-vault-read.yml → build/logql/falco-classified-vault-read.logql[+] rules/exfiltration/falco-pgdump-genetic-samples.yml → build/logql/falco-pgdump-genetic-samples.logql[✓] 3 règles compilées5.3 — Lire la requête générée
Section intitulée « 5.3 — Lire la requête générée »Lecture obligatoire avant de l’exécuter :
- Label filters (
{job="auth"}) : à gauche du|. Définissent quels streams Loki sont scannés. Choisir ici réduit la cardinalité. - Line filters (
|= "foo") : matching texte brut, très rapide. - Parsers (
| logfmt,| json) : extraient les champs depuis la ligne. - Label filters post-parsing (
| user="julie.moreau") : filtrage sur champs extraits.
Règle d’or de cardinalité (Loki) : job, host, severity en labels — IP/user/path dans la ligne (extraits par parseur). Sinon Loki explose.
6. Étape 3 — Détecter l’attaque jour J
Section intitulée « 6. Étape 3 — Détecter l’attaque jour J »6.1 — Exécuter la règle #1 dans Explore
Section intitulée « 6.1 — Exécuter la règle #1 dans Explore »Ouvrez Grafana → Explore → Datasource Loki. Collez la requête générée à l’étape 5.1.
Résultat attendu : une ligne (au moins) autour de 09h12 — voici le log brut que Loki remonte (job auth, texte syslog) :
2024-12-15T09:12:00+04:00 TAAF-GW-PAF vpn_service[1024]: WARNING User julie.moreau authenticated successfully from external IP 203.0.113.42 (Non-standard geolocation, outside business hours profile)Pourquoi cette ligne tombe sur la règle :
- elle contient
WARNING✓ (auth signalée anormale par la passerelle) - elle contient
external IP✓ (connexion hors du réseau interne) - le générateur ne produit ce couple que pour l’accès malveillant
203.0.113.42— les auth internes sont enINFO
📸 Capture obligatoire : screenshot de Grafana Explore montrant la détection. Annotez-y le timestamp et l’IP suspecte.
6.2 — Exécuter la règle #2 (Falco)
Section intitulée « 6.2 — Exécuter la règle #2 (Falco) »{job="falco"} | json | rule="Read sensitive file untrusted" |= "CLASSIFIED-VAULT-X"Résultat attendu : un événement autour de 10h05 sur KER-2024-002_analyse-genetique.md. Le log Falco réel (job falco, JSONL) :
{ "output": "10:05:44.774201000: Notice Sensitive NextCloud file read (user=www-data file=/var/www/html/data/julie.moreau/files/CLASSIFIED-VAULT-X/KER-2024-002_analyse-genetique.md ...)", "priority": "Notice", "rule": "Read sensitive file untrusted", "time": "2024-12-15T10:05:44.774201000+04:00", "hostname": "TAAF-DB-PAF"}Lecture : le champ rule (extrait par | json) vaut Read sensitive file untrusted, et le filtre de ligne |= "CLASSIFIED-VAULT-X" isole l’accès au coffre parmi les lectures de fichiers. Le compte www-data sert de relais à svc_backup (corrélez avec le job nextcloud au TP3).
📸 Capture obligatoire : screenshot Grafana Explore avec le JSON Falco déplié (clic sur la ligne).
6.3 — Exécuter la règle #3 (votre rédaction)
Section intitulée « 6.3 — Exécuter la règle #3 (votre rédaction) »À vous d’écrire la LogQL et de la valider sur le job falco. Cible : le pg_dump vu au runtime vers 10h17.
LogQL attendue : {job="falco"} |= "pg_dump". Le log Falco réel :
{ "output": "10:17:05.105667000: Warning Sensitive file opened for reading by non-trusted program (user=postgres program=pg_dump command=pg_dump --no-owner -t genetic_samples ...)", "priority": "Warning", "rule": "Read sensitive file untrusted", "time": "2024-12-15T10:17:05.105667000+04:00"}Pourquoi elle tombe sur la règle :
- l’
outputcontientpg_dump✓ (le programme d’extraction lit la table classifiée) - c’est le seul événement du rejeu contenant cette chaîne → zéro faux positif
Nuance à écrire dans votre dossier : pg_dump et la lecture du coffre (§6.2) partagent la même règle Falco (Read sensitive file untrusted) — ce sont les filtres de ligne (|= "CLASSIFIED-VAULT-X" vs |= "pg_dump") qui les distinguent.
📸 Capture obligatoire : screenshot Grafana Explore montrant la requête malveillante isolée.
6.4 — Bonus : voir les 3 alertes sur un même graphique temporel
Section intitulée « 6.4 — Bonus : voir les 3 alertes sur un même graphique temporel »Dans Explore, basculez en mode graph et utilisez :
sum by (job) ( count_over_time({job=~"falco|auth|windows|nextcloud|zeek"}[5m]))→ Vous voyez la chronologie de l’attaque se dessiner. C’est le socle du TP3 (corrélation).
7. Étape 4 — Versionner les règles
Section intitulée « 7. Étape 4 — Versionner les règles »cd ~/taaf-detectionsgit add rules/ scripts/ .gitignoregit commit -m "feat(detections): TP1 — 3 règles Sigma jour J (T1078/T1213/T1041)"
# Pousser sur votre repo perso (GitLab)git remote add origin git@gitlab.com:VOTRE-LOGIN/taaf-detections.gitgit push -u origin main8. Pièges & bonnes pratiques
Section intitulée « 8. Pièges & bonnes pratiques »| Piège | Symptôme | Solution |
|---|---|---|
| Logsource Sigma mal mappé | sigma convert ne sort rien (silent fail) | Vérifier le pipeline (loki_grafana pour texte/JSON) et le product/service |
| Filtres Loki sur IP en label | Cardinalité explose, Loki devient lent | Mettre l’IP dans la ligne, parsée via ` |
| Backend Loki mal pinné | LogQL généré obsolète (syntaxe Loki 2.x vs 3.x) | Pinner pysigma-backend-loki==0.13.* |
condition sans not autour des filtres | Faux positifs ou faux négatifs | Toujours selection and not (filter_X or filter_Y) |
Pas de tags: attack.txxxx | Pas de couverture ATT&CK au TP3 | Tagger systématiquement (norme MITRE) |
Règle sans falsepositives: | Tunage impossible au TP2 | Lister les FP connus dès l’écriture |
9. Livrables & validation
Section intitulée « 9. Livrables & validation »Livrables (à pousser sur GitLab)
Section intitulée « Livrables (à pousser sur GitLab) »- Repo
taaf-detectionscontenant :rules/initial_access/vpn-auth-external-ip.ymlrules/collection/falco-classified-vault-read.ymlrules/exfiltration/falco-pgdump-genetic-samples.ymlscripts/compile.shexécutableREADME.mdlistant les 3 règles avec un lien ATT&CK
- Règle Falco custom déposée dans le repo
monitoring(falco/rules.d/taaf-classified-vault.yaml). - 3 captures Grafana Explore (PNG) montrant chacune une détection avec timestamp visible.
Grille de validation
Section intitulée « Grille de validation »| Critère | Pondération |
|---|---|
Les 3 règles Sigma compilent sans erreur (sigma convert exit 0) | 25 % |
| LogQL généré correct (testé dans Explore, retourne ≥ 1 ligne sur les logs du jour J) | 25 % |
tags: attack.txxxx présent et cohérent dans chacune des 3 règles | 15 % |
Falco rule custom déployée et visible dans journalctl -u falco-modern-bpf | 10 % |
Repo Git propre (commits atomiques, README, .gitignore) | 10 % |
| Captures Grafana exploitables (lisibles, timestamps annotés) | 15 % |
Format de rendu : PDF nom-prenom-promo.pdf sur Moodle (cf. règle Acte 1). -5 points si nommage non respecté.
10. Annexe enseignant — corrigé
Section intitulée « 10. Annexe enseignant — corrigé »Corrigé de la règle #3 (exfil genetic_samples via pg_dump)
Section intitulée « Corrigé de la règle #3 (exfil genetic_samples via pg_dump) »rules/exfiltration/falco-pgdump-genetic-samples.yml :
title: Falco — pg_dump of genetic_samples (data staged for exfil)id: f3a4b5c6-d7e8-4f9a-b0c1-d2e3f4a5b6c7status: experimentaldescription: | Falco observe un pg_dump lisant la table classifiée genetic_samples au runtime (programme non-fiable lisant un fichier sensible).references: - https://attack.mitre.org/techniques/T1005/ - https://attack.mitre.org/techniques/T1041/author: TAAF SOCdate: 2024/12/15logsource: product: falcodetection: keywords: - 'pg_dump' condition: keywordsfields: - rule - outputlevel: criticaltags: - attack.collection - attack.t1005 - attack.exfiltration - attack.t1041LogQL générée :
{job="falco"} |= `pg_dump`Évaluation des erreurs courantes
Section intitulée « Évaluation des erreurs courantes »condition: selection and selection_2au lieu decondition: selection and not filter→ l’étudiant n’a pas compris la logique d’exclusion. -10 %.- Règle sans
id→ invalide pour le repo de détection-as-code. -5 %. - Tags ATT&CK absents ou
attack.txxxxsans le préfixeattack.→ S2AN au TP3 ne pourra pas générer le layer. -10 %. - Hardcode d’IP dans la règle sans
filter_known→ règle non maintenable. -5 %.
Pont vers le TP2
Section intitulée « Pont vers le TP2 »À la fin du TP1, l’étudiant a 3 règles qui se matchent dans Explore. Au TP2, la même règle Sigma se compile avec un autre backend Sigma (grafana_alerting) pour produire une alerte Grafana routée vers Discord. La règle ne change pas : c’est tout l’intérêt de Sigma.