mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
docs(spec): sbxwaf mode detect — essayer une règle avant de l'armer
Né de la question "peut-on dériver des règles WAF depuis les CVE ?", dont l'exploration a montré que ce mode en est le prérequis bloquant. Mais il vaut seul, et il est livré seul. Aujourd'hui rules.go n'a que deux états: catégorie sautée, ou évaluée ET bloquante. Toute nouvelle règle est donc un pari armé en production devant Nextcloud/PeerTube/Gitea — et une régression WAF a déjà servi du JS vide en silence (YaCy blanc). Champ `mode: detect|block` par catégorie. Absent ⇒ block: les 17 catégories existantes ne changent pas. Un défaut detect transformerait le WAF en observateur muet — une panne de sécurité silencieuse. Un match en detect est compté et journalisé (action: "detect") mais laisse passer et ne déclenche AUCUN ban. Invariant: une catégorie en detect doit être aussi inoffensive qu'un enabled:false, à la journalisation près. Donne enfin l'instrument pour prouver le poids mort déjà diagnostiqué: 39/149 patterns voip/xmpp inutiles en HTTP, et une catégorie cve_2024 visant PAN-OS, Ivanti et F5 — trois produits absents de cette box Debian. Générateur CVE→règles explicitement hors périmètre (chantier suivant): source Nuclei (MIT vérifié; les CVE ne contiennent aucun exploit — vérifié sur le KEV), filtre KEV ∧ installé ∧ exposé, expiration à la correction. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
a33e3c0092
commit
572d6eece9
150
docs/superpowers/specs/2026-07-17-sbxwaf-mode-detect-design.md
Normal file
150
docs/superpowers/specs/2026-07-17-sbxwaf-mode-detect-design.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# sbxwaf — mode `detect` (essayer une règle avant de l'armer)
|
||||
|
||||
**Date** : 2026-07-17
|
||||
**Statut** : conception validée, prête pour le plan d'implémentation
|
||||
**Auteur** : Gérald Kerma <devel@cybermind.fr>
|
||||
|
||||
---
|
||||
|
||||
## Objectif
|
||||
|
||||
Pouvoir **essayer une règle WAF sans bloquer** : elle matche, elle est comptée et journalisée, mais
|
||||
la requête passe. Aujourd'hui sbxwaf n'a que deux états — évaluée (et bloquante) ou pas évaluée du
|
||||
tout.
|
||||
|
||||
## Pourquoi maintenant
|
||||
|
||||
Ce spec naît d'une question plus large — *dériver des règles WAF depuis les CVE* — dont l'exploration
|
||||
a montré que le mode `detect` en était le **prérequis bloquant**. Mais il a de la valeur seul, et il
|
||||
est livré seul.
|
||||
|
||||
Trois constats mesurés sur gk2 (2026-07-17) :
|
||||
|
||||
1. **Il n'existe aucun moyen d'essayer une règle.** `cmd/sbxwaf/rules.go:178` — `if cat.Enabled !=
|
||||
nil && !*cat.Enabled` : la catégorie est sautée ou elle bloque. Rien entre les deux. Toute
|
||||
nouvelle règle est donc un pari : on l'arme en production et on découvre les faux positifs sur du
|
||||
trafic réel — devant Nextcloud, PeerTube, Gitea et billets.
|
||||
2. **Le risque est démontré.** Le cache média de sbxwaf a déjà servi du JS vide en avalant le
|
||||
`Content-Encoding` (YaCy blanc). Les régressions WAF cassent en silence des services réels.
|
||||
3. **Le poids mort est déjà là.** 149 patterns sur 17 catégories, dont ~39 (voip/xmpp) inutiles en
|
||||
HTTP, et une catégorie `cve_2024` de 6 patterns visant **PAN-OS, Ivanti et F5** — trois produits
|
||||
absents de cette box Debian. Sans mode `detect`, on ne peut ni prouver qu'un pattern sert, ni le
|
||||
retirer en confiance.
|
||||
|
||||
Le mode `detect` transforme « je crois que cette règle est bonne » en « j'ai mesuré ».
|
||||
|
||||
## Le réel mesuré
|
||||
|
||||
- `/etc/secubox/waf/waf-rules.json` : `{_meta:{version,updated,sources}, categories:{...}}`
|
||||
- 17 catégories, **149 patterns**. Forme d'une catégorie :
|
||||
```json
|
||||
"sqli": { "name": "SQL Injection", "severity": "critical", "enabled": true,
|
||||
"owasp": "A03:2021",
|
||||
"patterns": [ {"id": "sqli-001", "pattern": "union\\s+(all\\s+)?select", "desc": "..."} ] }
|
||||
```
|
||||
- `cve_2024` porte déjà un champ `cve` par pattern → le schéma sait déjà tracer une provenance.
|
||||
- sbxwaf **recharge à chaud** (`watcher`, `reload.Target` dans le binaire) : un changement de règles
|
||||
ne demande pas de redémarrage.
|
||||
- Sévérités utilisées : `critical` (8 catégories), `high` (5), `medium` (3), `low` (1).
|
||||
- Le service tourne sur une box à **118 services, load 5.4 sur 4 cœurs** : le coût par requête compte.
|
||||
|
||||
---
|
||||
|
||||
## Conception
|
||||
|
||||
### Le champ
|
||||
|
||||
Un champ **`mode`** par catégorie, à côté de `enabled` :
|
||||
|
||||
```json
|
||||
"cve_2024": { "name": "...", "severity": "critical", "enabled": true,
|
||||
"mode": "detect", // "detect" | "block"
|
||||
"patterns": [ ... ] }
|
||||
```
|
||||
|
||||
Sémantique, sans ambiguïté :
|
||||
|
||||
| `enabled` | `mode` | Comportement |
|
||||
|---|---|---|
|
||||
| `false` | *(ignoré)* | catégorie non évaluée — coût nul |
|
||||
| `true` | `"block"` | match → requête bloquée (**comportement actuel**) |
|
||||
| `true` | `"detect"` | match → compté + journalisé, **requête laissée passer** |
|
||||
|
||||
**`mode` absent ⇒ `"block"`.** C'est la décision structurante : les 17 catégories existantes ne
|
||||
portent pas le champ et ne doivent rien changer. Un défaut `detect` transformerait silencieusement
|
||||
tout le WAF en observateur — une panne de sécurité muette, le pire des deux mondes. Le même
|
||||
raisonnement que `Enabled *bool` (pointeur, `nil` = absent = `true`) déjà en place ligne 144.
|
||||
|
||||
### Ce que produit un match en `detect`
|
||||
|
||||
Le même enregistrement de menace qu'un blocage, **plus un discriminant explicite** :
|
||||
|
||||
```json
|
||||
{"ts": "...", "ip": "...", "rule": "cve-2024-3400", "category": "cve_2024",
|
||||
"severity": "critical", "action": "detect", "url": "..."}
|
||||
```
|
||||
|
||||
`action` distingue `detect` de `block`. Sans ce champ, les statistiques mélangeraient « bloqué » et
|
||||
« aurait bloqué » — et le compteur de menaces (198k aujourd'hui) deviendrait un mensonge.
|
||||
|
||||
**Un match en `detect` ne déclenche AUCUN effet de bord** : pas de ban CrowdSec, pas de décision nft,
|
||||
pas de compteur de bannissement. `detect` veut dire *observer*, pas *punir plus tard*. C'est
|
||||
l'invariant central : une catégorie en `detect` doit être exactement aussi inoffensive qu'un
|
||||
`enabled: false`, à la journalisation près.
|
||||
|
||||
### Ce qu'on peut alors faire (et qu'on ne peut pas aujourd'hui)
|
||||
|
||||
- Armer une nouvelle règle en `detect`, regarder une semaine de trafic réel, **puis** décider.
|
||||
- Basculer `cve_voip`/`cve_xmpp` en `detect` et **prouver** par les compteurs qu'ils ne matchent
|
||||
jamais — avant de les retirer. Aujourd'hui on ne peut que supposer.
|
||||
- Faire atterrir toute règle générée automatiquement (chantier CVE→WAF à venir) en `detect` par
|
||||
construction.
|
||||
|
||||
---
|
||||
|
||||
## Hors périmètre (YAGNI)
|
||||
|
||||
- **Pas de `mode` par pattern.** La catégorie suffit : c'est l'unité que l'opérateur manipule, et un
|
||||
`mode` par pattern multiplierait les états à comprendre sans besoin démontré.
|
||||
- **Pas de promotion automatique `detect` → `block`.** Un WAF qui s'arme tout seul est un WAF qui se
|
||||
coupe tout seul. L'humain décide, toujours.
|
||||
- **Pas de nettoyage des 149 patterns** dans ce chantier. Le mode `detect` donne l'instrument de
|
||||
mesure ; le nettoyage est une décision d'exploitation qui viendra avec les chiffres.
|
||||
- **Pas de génération de règles depuis les CVE.** C'est le chantier suivant, qui s'appuiera sur ce
|
||||
mode. Rappel de son cadrage : source = templates **Nuclei** (MIT, vérifié — les CVE eux-mêmes ne
|
||||
contiennent aucun exploit, vérifié sur le KEV : champs `cveID/cwes/product/requiredAction`, aucune
|
||||
URL) ; filtre = `KEV ∧ paquet installé ∧ module exposé` (l'inventaire `secubox-profiles` donne
|
||||
`exposure`, 16 modules publics sur 187) ; expiration à la correction du paquet.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
- **Défaut** : une catégorie sans `mode` bloque (non-régression des 17 existantes — le test le plus
|
||||
important du lot).
|
||||
- `mode: "block"` explicite bloque.
|
||||
- `mode: "detect"` : le match est compté et journalisé avec `action: "detect"`, **et la requête
|
||||
passe** (assertion sur le code de retour, pas seulement sur le log).
|
||||
- `mode: "detect"` **ne produit aucun ban** : pas d'appel CrowdSec, pas de décision nft.
|
||||
- `enabled: false` + `mode: "detect"` → catégorie non évaluée (le `enabled` prime).
|
||||
- `mode` avec une valeur inconnue (`"monitor"`, `""`, `null`) → **rejet bruyant au chargement**, pas
|
||||
un repli silencieux sur `detect` : un mode mal orthographié qui désarmerait le WAF en silence est
|
||||
précisément le défaut à éviter.
|
||||
- Le rechargement à chaud prend en compte un changement de `mode` sans redémarrage.
|
||||
|
||||
Chaque test doit pouvoir échouer : vérifier par mutation (retirer le comportement, constater
|
||||
l'échec, restaurer).
|
||||
|
||||
---
|
||||
|
||||
## Découpage
|
||||
|
||||
| Phase | Contenu | Risque |
|
||||
|---|---|---|
|
||||
| 1 | Champ `mode` + parsing + défaut `block` + rejet des valeurs inconnues | faible |
|
||||
| 2 | Le chemin `detect` : compter, journaliser `action`, laisser passer, aucun ban | moyen — c'est le cœur |
|
||||
| 3 | Déploiement gk2 : rebuild arm64, `mv` sur `/usr/sbin/sbxwaf`, `systemctl restart secubox-waf-ng` | moyen |
|
||||
|
||||
⚠️ Contraintes de déploiement établies : `cp` échoue (« text file busy ») → `mv` ; **jamais**
|
||||
`kill -HUP` → `systemctl restart secubox-waf-ng`. sbxwaf est en frontal de tous les vhosts publics :
|
||||
une régression coupe tout.
|
||||
Loading…
Reference in New Issue
Block a user