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

TP 1 — Détection as Code : Sigma → LogQL

Acte 2 · SIEM Avancé Sigma · pySigma · LogQL · MITRE ATT&CK


📡 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…).

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.

  • 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-loki et 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).
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

Fenêtre de terminal
vagrant ssh soc
# Vérifier que Python 3 est dispo
python3 --version # Python 3.10+ attendu
# Installer Sigma CLI + le backend Loki
python3 -m venv ~/sigma-venv
source ~/sigma-venv/bin/activate
pip install --upgrade pip
pip install sigma-cli==1.0.* pysigma-backend-loki==0.13.* pysigma-pipeline-loki==0.13.*
sigma version # → sigma-cli 1.0.x
sigma plugin list # → loki backend, loki pipeline (installés)

Depuis la VM soc (joint la VM monitoring par IP) :

Fenêtre de terminal
curl -s http://IP_VM1:3100/ready # → ready
curl -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 ».

Dans Grafana Explore (datasource Loki), cliquez sur Label filters > job :

job attenduSource
falcoÉvénements Falco (runtime sensitive file, exec, network)
authLogs auth VPN + AD (02_auth_vpn_ad.log)
postgresql-logsLogs PostgreSQL base_af
windowsWindows 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_telemetryTé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.

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.

Sur la VM soc :

Fenêtre de terminal
mkdir -p ~/taaf-detections/rules
cd ~/taaf-detections
git init
mkdir -p rules/{initial_access,credential_access,collection,exfiltration}
echo "*.pyc" > .gitignore

4.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-5a6f7c8d9e10
status: experimental
description: |
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 SOC
date: 2026/06/16
logsource:
product: vpn
service: auth
detection:
keywords:
- 'WARNING'
- 'external IP'
condition: keywords
fields:
- raw
falsepositives:
- Utilisateur en déplacement déclaré (vérifier dans le calendrier RH)
level: high
tags:
- attack.initial_access
- attack.t1078
- attack.t1133

Lecture 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 job auth étant du texte brut, on détecte les auth WARNING depuis une external 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-X
id: a4f7c2d1-9e8b-4a3f-b6c5-1d2e3f4a5b6c
status: stable
description: 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 SOC
date: 2026/06/16
logsource:
product: falco
detection:
selection:
rule: 'Read sensitive file untrusted'
keywords:
- 'CLASSIFIED-VAULT-X'
condition: selection and keywords
fields:
- rule
- output
level: critical
tags:
- attack.collection
- attack.t1213

4.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: falco
  • detection.keywords: ['pg_dump'] (la chaîne apparaît dans l’output Falco)
  • condition : keywords
  • tags : attack.collection / attack.t1005 / attack.exfiltration / attack.t1041

(Corrigé en Annexe.)


Fenêtre de terminal
cd ~/taaf-detections
source ~/sigma-venv/bin/activate
sigma convert -t loki -p loki_grafana \
rules/initial_access/vpn-auth-external-ip.yml

Sortie attendue (approx) :

{job="auth"} |= `WARNING` |= `external IP`

Créez scripts/compile.sh :

#!/usr/bin/env bash
set -euo pipefail
OUT="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"
done
echo "[✓] $(ls "$OUT" | wc -l) règles compilées"
Fenêtre de terminal
chmod +x scripts/compile.sh
./scripts/compile.sh

Sortie 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ées

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.


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 en INFO

📸 Capture obligatoire : screenshot de Grafana Explore montrant la détection. Annotez-y le timestamp et l’IP suspecte.

{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).

À 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’output contient pg_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).


Fenêtre de terminal
cd ~/taaf-detections
git add rules/ scripts/ .gitignore
git 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.git
git push -u origin main

PiègeSymptômeSolution
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 labelCardinalité explose, Loki devient lentMettre 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 filtresFaux positifs ou faux négatifsToujours selection and not (filter_X or filter_Y)
Pas de tags: attack.txxxxPas de couverture ATT&CK au TP3Tagger systématiquement (norme MITRE)
Règle sans falsepositives:Tunage impossible au TP2Lister les FP connus dès l’écriture

  1. Repo taaf-detections contenant :
    • rules/initial_access/vpn-auth-external-ip.yml
    • rules/collection/falco-classified-vault-read.yml
    • rules/exfiltration/falco-pgdump-genetic-samples.yml
    • scripts/compile.sh exécutable
    • README.md listant les 3 règles avec un lien ATT&CK
  2. Règle Falco custom déposée dans le repo monitoring (falco/rules.d/taaf-classified-vault.yaml).
  3. 3 captures Grafana Explore (PNG) montrant chacune une détection avec timestamp visible.
CritèrePondé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ègles15 %
Falco rule custom déployée et visible dans journalctl -u falco-modern-bpf10 %
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é.


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-d2e3f4a5b6c7
status: experimental
description: |
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 SOC
date: 2024/12/15
logsource:
product: falco
detection:
keywords:
- 'pg_dump'
condition: keywords
fields:
- rule
- output
level: critical
tags:
- attack.collection
- attack.t1005
- attack.exfiltration
- attack.t1041

LogQL générée :

{job="falco"} |= `pg_dump`
  • condition: selection and selection_2 au lieu de condition: 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.txxxx sans le préfixe attack. → 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 %.

À 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.