UTMStack v11.2.8 Community Edition — Procédures de déploiement pour lab PME Suisse. Installation VMware, Suricata, CrowdSec, SOAR, OPNsense.
Par défaut, OPNsense configure HOME_NET avec uniquement le réseau LAN. Si Suricata monitore l’interface WAN (em1), toutes les règles -> $HOME_NET sont aveugles sur le trafic WAN — les scanners, les attaques et les sondes ne sont jamais détectés.
Symptôme : les règles NF-Scanners (Shodan, Censys, BinaryEdge) ne déclenchent aucune alerte malgré un trafic de scan visible dans les logs.
Fix : OPNsense GUI → Services → Intrusion Detection → Administration → Settings → Home Networks → ajouter le réseau WAN :
10.100.1.0/24, 192.168.1.0/24
Vérification :
grep "HOME_NET" /usr/local/etc/suricata/suricata.yaml | head -1
# → HOME_NET: "[10.100.1.0/24,192.168.1.0/24]"
Résultat après correction — en 24h, 139 scans bloqués par les règles NF qui étaient précédemment invisibles :
| Scanner | Hits bloqués | SID |
|---|---|---|
| Stretchoid | 40 | 5034xxx |
| BinaryEdge 1 | 36 | 5034101 |
| BinaryEdge 2 | 36 | 5034104 |
| Visionheight | 17 | 5034xxx |
| ShadowServer | 10 | 5034xxx |
| SYN scans divers | 12 | 9000200 |
| Dshield blocklist | 23 | 2402000 |
ℹ️ Visibilité UTMStack — Les événements
event_type: alertavecaction: blockedremontent dans UTMStack et peuvent déclencher les corrélations. Les événementsevent_type: dropne remontent pas — c’est le comportement normal du pipeline syslog.
OPNsense gère nativement ET Open et Abuse.ch via Services → Intrusion Detection → Administration → Download. Ces sources couvrent l’essentiel. Pour aller plus loin sans compromettre la conformité nLPD, trois sources complémentaires ont été retenues :
| Source | Méthode | Contenu | Règles |
|---|---|---|---|
| ET Open + Abuse.ch | UI OPNsense | Référence absolue | ~30 000 |
| suricata-update | CLI | APT, lateral movement, threat hunting | ~1701 |
| NF Scanners | Script custom | Scanners connus (Shodan, Censys…) en drop |
222 |
| NF Local | Script custom | Threat hunting, C2, malware | 727 |
| NF Suricata | Script custom | JA3 fingerprints botnets/RAT | 6 |
Rejeté — ET Pro Telemetry : partage anonyme des alertes avec Proofpoint (USA). Non conforme nLPD pour un contexte PME suisse.
suricata-update update-sources
suricata-update enable-source ptrules/open # PT Security — APT Windows/AD
suricata-update enable-source stamus/lateral # Lateral movement SMB/DCERPC
suricata-update enable-source tgreen/hunting # Threat hunting général
suricata-update enable-source etnetera/aggressive
suricata-update enable-source abuse.ch/sslbl-ja3
suricata-update disable-source et/open # Déjà géré par OPNsense UI
⚠️
abuse.ch/feodotrackerdésactivé — déjà présent dans OPNsense (abuse.ch.feodotracker.rules).
OPNsense génère suricata.yaml avec cette structure :
default-rule-path: /usr/local/etc/suricata/opnsense.rules
rule-files:
- suricata.rules ← ignoré (écrasé par installed_rules.yaml)
include:
- installed_rules.yaml ← liste effective des fichiers chargés
Solution : sortir suricata-update directement dans opnsense.rules/ et ajouter suricata.rules à installed_rules.yaml.
OPNsense désactive le protocole DNP3 (SCADA) — les distribution rules correspondantes font planter le chargement.
mkdir -p /var/lib/suricata/update
nano /var/lib/suricata/update/disable.conf
Contenu :
re:SURICATA DNP3
suricata-update \
--disable-conf /var/lib/suricata/update/disable.conf \
-o /usr/local/etc/suricata/opnsense.rules \
--no-test
echo " - suricata.rules" >> /usr/local/etc/suricata/installed_rules.yaml
kill -USR2 `pgrep -x suricata | head -1`
⚠️
--no-testobligatoire — en Suricata 8.x, la redéfinition derule-filesparinstalled_rules.yamlgénère un exit code 1 en mode-Tmême si les règles sont valides. Le service fonctionne normalement.
nano /usr/local/bin/update-suricata-rules.sh
#!/bin/sh
/usr/local/bin/suricata-update \
--disable-conf /var/lib/suricata/update/disable.conf \
-o /usr/local/etc/suricata/opnsense.rules \
--no-test
grep -q "suricata\.rules" /usr/local/etc/suricata/installed_rules.yaml || \
echo " - suricata.rules" >> /usr/local/etc/suricata/installed_rules.yaml
kill -USR2 `pgrep -x suricata | head -1`
echo "suricata-update done: `date`"
chmod +x /usr/local/bin/update-suricata-rules.sh
# /etc/cron.d/suricata-update
0 3 * * 1 root /usr/local/bin/update-suricata-rules.sh >> /var/log/suricata-update.log 2>&1
https://networkforensic.dk/SNORT/default.html — règles écrites en format Snort, mises à jour régulièrement.
| Fichier ZIP | Contenu | Conversion |
|---|---|---|
NF-Scanners.zip |
222 règles Snort — 80 scanners connus | flow + alert → drop |
NF-local.zip |
729 règles Snort — threat hunting général | flow uniquement |
NF-Suricata.zip |
6 règles Suricata natives — JA3 botnets/RAT | aucune |
drop pour NF-ScannersEn mode alert + SOAR, le scanner reçoit une réponse avant le ban → il sait que l’IP existe. En mode drop IPS direct, le paquet est bloqué immédiatement sans réponse → invisibilité totale. Suricata en mode IPS sur OPNsense exécute réellement le drop.
ℹ️ Les règles
dropgénèrent quand même un événement danseve.jsonavecalert_action: blocked— UTMStack voit toujours l’alerte.
| Problème | Correction sed |
|---|---|
flow:to_server,established |
→ flow:established,to_server |
flow:from_server,established |
→ flow:established,from_server |
flow:from_client,established |
→ flow:established,to_server |
flow:established, to_server (espace) |
→ sans espace |
;; double point-virgule |
→ ; |
flowbits:noalert |
→ noalert |
dsize:X<>Y content: (semicolon manquant) |
→ dsize:X<>Y; content: |
SIDs désactivés (incompatibles Suricata 8.x) :
5017877 — PCRE invalide (\m non supporté par pcre2)5050006 — sticky buffer incompatiblenano /usr/local/bin/update-nf-rules.sh
#!/bin/sh
TMPDIR="/tmp/nf-rules"
RULES_DIR="/usr/local/etc/suricata/opnsense.rules"
CONF_DIR="/conf"
INSTALLED="/usr/local/etc/suricata/installed_rules.yaml"
mkdir -p "$TMPDIR"
fetch -qo "$TMPDIR/NF-Scanners.zip" "https://networkforensic.dk/SNORT/NF-Scanners.zip"
fetch -qo "$TMPDIR/NF-local.zip" "https://networkforensic.dk/SNORT/NF-local.zip"
fetch -qo "$TMPDIR/NF-Suricata.zip" "https://networkforensic.dk/SNORT/NF-Suricata.zip"
echo "SHA1 NF-Scanners : $(sha1 -q $TMPDIR/NF-Scanners.zip)"
echo "SHA1 NF-local : $(sha1 -q $TMPDIR/NF-local.zip)"
echo "SHA1 NF-Suricata : $(sha1 -q $TMPDIR/NF-Suricata.zip)"
unzip -o "$TMPDIR/NF-Scanners.zip" -d "$TMPDIR"
unzip -o "$TMPDIR/NF-local.zip" -d "$TMPDIR"
unzip -o "$TMPDIR/NF-Suricata.zip" -d "$TMPDIR"
# NF-Scanners : flow Snort→Suricata + alert→drop (scanners connus)
sed -e 's/flow:to_server,established/flow:established,to_server/g' \
-e 's/flow:from_server,established/flow:established,from_server/g' \
-e 's/flow:from_client,established/flow:established,to_server/g' \
-e 's/flow:established, to_server/flow:established,to_server/g' \
-e 's/flow:established, from_server/flow:established,from_server/g' \
-e 's/^alert/drop/' \
"$TMPDIR/NF-Scanners.rules" > "$RULES_DIR/NF-Scanners.rules"
# NF-local : flow + corrections Suricata 8.x (garder alert)
sed -e 's/flow:to_server,established/flow:established,to_server/g' \
-e 's/flow:from_server,established/flow:established,from_server/g' \
-e 's/flow:from_client,established/flow:established,to_server/g' \
-e 's/flow:established, to_server/flow:established,to_server/g' \
-e 's/flow:established, from_server/flow:established,from_server/g' \
-e 's/;;/;/g' \
-e 's/flowbits:noalert/noalert/g' \
-e 's/dsize:\([0-9]*<>[0-9]*\) content:/dsize:\1; content:/g' \
"$TMPDIR/NF-local.rules" | \
grep -v "sid:5017877\|sid:5050006" > "$RULES_DIR/NF-local.rules"
# NF-Suricata : natif, copie directe
cp "$TMPDIR/NF-Suricata.rules" "$RULES_DIR/NF-Suricata.rules"
# Persistance
cp "$RULES_DIR/NF-Scanners.rules" "$CONF_DIR/"
cp "$RULES_DIR/NF-local.rules" "$CONF_DIR/"
cp "$RULES_DIR/NF-Suricata.rules" "$CONF_DIR/"
# Ajouter à installed_rules.yaml si absent
grep -q "NF-Scanners" "$INSTALLED" || echo " - NF-Scanners.rules" >> "$INSTALLED"
grep -q "NF-local" "$INSTALLED" || echo " - NF-local.rules" >> "$INSTALLED"
grep -q "NF-Suricata" "$INSTALLED" || echo " - NF-Suricata.rules" >> "$INSTALLED"
kill -USR2 `pgrep -x suricata | head -1`
echo "Done : $(date)"
echo "NF-Scanners (drop) : $(grep -c '^drop' $RULES_DIR/NF-Scanners.rules) règles"
echo "NF-local (alert) : $(grep -c '^alert' $RULES_DIR/NF-local.rules) règles"
echo "NF-Suricata (drop) : $(grep -c '^drop' $RULES_DIR/NF-Suricata.rules) règles"
rm -rf "$TMPDIR"
chmod +x /usr/local/bin/update-nf-rules.sh
# /etc/cron.d/nf-rules
0 4 * * 1 root /usr/local/bin/update-nf-rules.sh >> /var/log/nf-rules-update.log 2>&1
OPNsense exécute au démarrage tous les scripts présents dans /usr/local/etc/rc.syshook.d/start/ par ordre alphabétique. Ces scripts sont des hooks de boot personnalisés — l’équivalent d’un rc.local sous Linux. Le préfixe numérique (98-) détermine l’ordre d’exécution (après les services système).
Persistance du hook lui-même : /usr/local/etc/ survit aux mises à jour normales OPNsense (c’est une partition de données, pas le système de base). En revanche, une réinstallation complète ou une migration vers une nouvelle VM efface ce répertoire. La stratégie retenue : sauvegarder le hook dans /conf/ (toujours persistant, inclus dans les backups OPNsense XML) et le redéployer manuellement si nécessaire.
nano /usr/local/etc/rc.syshook.d/start/98-soar-ban
Contenu complet actuel du hook :
#!/bin/sh
# Hook de boot OPNsense — restaure configurations SOAR + Suricata
cp /conf/soar_ban.sh /usr/local/bin/soar_ban.sh
chmod +x /usr/local/bin/soar_ban.sh
# Restaurer suricata-update si OPNsense a régénéré installed_rules.yaml
grep -q "suricata\.rules" /usr/local/etc/suricata/installed_rules.yaml || \
echo " - suricata.rules" >> /usr/local/etc/suricata/installed_rules.yaml
# Restaurer les règles NF après mise à jour OPNsense
cp /conf/NF-Scanners.rules /usr/local/etc/suricata/opnsense.rules/
cp /conf/NF-local.rules /usr/local/etc/suricata/opnsense.rules/
cp /conf/NF-Suricata.rules /usr/local/etc/suricata/opnsense.rules/
grep -q "NF-Scanners" /usr/local/etc/suricata/installed_rules.yaml || echo " - NF-Scanners.rules" >> /usr/local/etc/suricata/installed_rules.yaml
grep -q "NF-local" /usr/local/etc/suricata/installed_rules.yaml || echo " - NF-local.rules" >> /usr/local/etc/suricata/installed_rules.yaml
grep -q "NF-Suricata" /usr/local/etc/suricata/installed_rules.yaml || echo " - NF-Suricata.rules" >> /usr/local/etc/suricata/installed_rules.yaml
# Restaurer suricata.yaml si threshold-file absent
grep -q "threshold-file" /usr/local/etc/suricata/suricata.yaml || \
cp /conf/suricata.yaml /usr/local/etc/suricata/suricata.yaml
# Restaurer cron Spamhaus DROP
cp /conf/suricata-drop-fix /etc/cron.d/suricata-drop-fix
exit 0
chmod +x /usr/local/etc/rc.syshook.d/start/98-soar-ban
cp /usr/local/etc/rc.syshook.d/start/98-soar-ban /conf/98-soar-ban
/conf/ est inclus dans les backups XML OPNsense (System → Configuration → Backups). En cas de réinstallation, restaurer le backup XML suffit à récupérer /conf/98-soar-ban, puis redéployer manuellement :
cp /conf/98-soar-ban /usr/local/etc/rc.syshook.d/start/98-soar-ban
chmod +x /usr/local/etc/rc.syshook.d/start/98-soar-ban
sh -n /usr/local/etc/rc.syshook.d/start/98-soar-ban # syntaxe OK si aucune sortie
sh /usr/local/etc/rc.syshook.d/start/98-soar-ban # test d'exécution manuelle
ℹ️ Le contenu complet du hook est centralisé dans la section Partie 3 → Persistance après mises à jour OPNsense — mécanisme syshook ci-dessous.
OPNsense ne configure pas threshold.config par défaut. Le fichier n’est pas référencé dans suricata.yaml généré automatiquement — Suricata l’ignore complètement même s’il existe sur disque.
Sans ce mécanisme, certaines signatures légitimes mais très répétitives (Mirai botnet, wget injection par le même bot, anomalies TCP) génèrent des dizaines d’alertes par heure sans valeur ajoutée.
⚠️
threshold.configse charge uniquement au démarrage de Suricata — unkill -USR2(rechargement des règles) ne le relit pas. Toute modification nécessiteservice suricata restart.
nano /usr/local/etc/suricata/threshold.config
Deux types d’entrées :
suppress — supprime complètement les alertes d’un SID :
suppress gen_id 1, sig_id 2210008
threshold — limite à N alertes par source/heure :
threshold gen_id 1, sig_id 2610808, type threshold, track by_src, count 1, seconds 3600
# Bruit TCP/HTTP infrastructure — supprimés le 2026-06-03
# SURICATA STREAM anomalies (TCP normal avec middleboxes/NAT)
suppress gen_id 1, sig_id 2221010 # HTTP unable to match response
suppress gen_id 1, sig_id 2210008 # STREAM 3way SYN resend
suppress gen_id 1, sig_id 2210022 # STREAM anomalie
suppress gen_id 1, sig_id 2210023 # STREAM anomalie
suppress gen_id 1, sig_id 2210024 # STREAM anomalie
suppress gen_id 1, sig_id 2210025 # STREAM anomalie
suppress gen_id 1, sig_id 2210026 # STREAM anomalie
suppress gen_id 1, sig_id 2210027 # STREAM anomalie
suppress gen_id 1, sig_id 2210028 # STREAM anomalie
suppress gen_id 1, sig_id 2210042 # STREAM ESTABLISHED SYN resend
suppress gen_id 1, sig_id 2210043 # STREAM SYNACK wrong ack
suppress gen_id 1, sig_id 2210044 # STREAM TIMEWAIT anomalie
suppress gen_id 1, sig_id 2210045 # STREAM Packet invalid timestamp
# Thresholds — 1 alerte par IP source par heure
# TGI HUNT wget injection (Mirai botnet en boucle)
threshold gen_id 1, sig_id 2610808, type threshold, track by_src, count 1, seconds 3600
threshold gen_id 1, sig_id 2610850, type threshold, track by_src, count 1, seconds 3600
# Faux positif — OPNsense External IP lookup (ipify.org)
suppress gen_id 1, sig_id 2047703, track by_src, ip 192.168.1.203
⚠️
suppressne fonctionne PAS sur les règlesdrop— testé avec SID 7000005 (NGROK JA3). Le suppress empêche l’alertmais ledropcontinue de fire. Pour les règlesdrop, modifier la source de la règle directement (voir section NF-Suricata ci-dessous).
| Type de règle | suppress/threshold | Modification directe |
|---|---|---|
alert |
✅ fonctionne | pas nécessaire |
drop |
❌ ne fonctionne pas | ✅ modifier la règle |
CrowdSec (écrit en Go) utilise un fingerprint JA3 identique à NGROK (aussi en Go). La règle NF SID 7000005 (drop) bloque les connexions CrowdSec CAPI sortantes d’OPNsense.
Le suppress dans threshold.config ne fonctionne pas sur cette règle drop. Fix : modifier la règle directement dans NF-Suricata.rules pour exclure l’IP WAN d’OPNsense :
# Avant (bloque tout $HOME_NET, y compris OPNsense)
drop tls $HOME_NET 1024: -> $EXTERNAL_NET 443 (msg:"NF - NGROK Tunnels - JA3 match"; ...
# Après (exclut OPNsense WAN)
drop tls [$HOME_NET,!192.168.1.203] 1024: -> $EXTERNAL_NET 443 (msg:"NF - NGROK Tunnels - JA3 match"; ...
Cette modification est automatiquement réappliquée par update-nf-rules.sh après chaque mise à jour des règles NF (voir section Partie 2).
OPNsense ne référence pas automatiquement threshold.config. Il faut ajouter la directive manuellement :
nano /usr/local/etc/suricata/suricata.yaml
Ajouter threshold-file juste après default-rule-path :
default-rule-path: /usr/local/etc/suricata/opnsense.rules
threshold-file: /usr/local/etc/suricata/threshold.config
rule-files:
- suricata.rules
⚠️ Ne pas utiliser sed pour cette modification — la syntaxe
\net les chemins avec/sont problématiques sur FreeBSD/csh.
Vérification :
grep "threshold-file" /usr/local/etc/suricata/suricata.yaml
Puis restart (obligatoire — kill -USR2 ne recharge pas threshold.config) :
service suricata restart
OPNsense régénère suricata.yaml lors des mises à jour système ou d’une réinstallation du package Suricata — la directive threshold-file est alors perdue.
Solution : ajouter le contrôle dans le hook de boot 98-soar-ban
Ajouter dans /usr/local/etc/rc.syshook.d/start/98-soar-ban :
# Vérifier que threshold.config est référencé dans suricata.yaml
grep -q "threshold-file" /usr/local/etc/suricata/suricata.yaml || \
sed -i '' 's|classification-file: /usr/local/etc/suricata/classification.config|classification-file: /usr/local/etc/suricata/classification.config\nthreshold-file: /usr/local/etc/suricata/threshold.config|' \
/usr/local/etc/suricata/suricata.yaml
Ce bloc vérifie la présence de threshold-file à chaque boot et la réinsère si manquante. threshold.config lui-même ne risque pas d’être effacé car il ne fait pas partie des fichiers gérés par OPNsense.
OPNsense régénère suricata.yaml régulièrement et supprime la directive threshold-file. L’approche la plus fiable est de sauvegarder le fichier complet :
# Après avoir configuré threshold-file manuellement
cp /usr/local/etc/suricata/suricata.yaml /conf/suricata.yaml
Dans le hook 98-soar-ban :
# Restaurer suricata.yaml si threshold-file absent
grep -q "threshold-file" /usr/local/etc/suricata/suricata.yaml || \
cp /conf/suricata.yaml /usr/local/etc/suricata/suricata.yaml
⚠️ Si tu modifies des réglages Suricata via l’interface OPNsense, pense à refaire
cp /usr/local/etc/suricata/suricata.yaml /conf/suricata.yamlpour mettre à jour le backup.
ℹ️ Vérification 2026-06-30 — Contrôle de l’état réel, avec une nuance importante entre les deux flux regroupés sous ce titre :
- Spamhaus (règles
ET DROP Spamhaus..., dansdrop.rules) : 0 lignealertrestante ce matin-là. Ce résultat ne prouve pas une livraison native endroppar ET — il confirme que le mécanisme ci-dessous, déployé depuis le 2026-06-10/11 (cronsuricata-drop-fix, 00:30 quotidien), fonctionne correctement : le téléchargement nocturne OPNsense (00:00) régénèredrop.rulesenalert, et le cron de 00:30 reconvertit avant toute vérification matinale. La procédure ci-dessous reste donc nécessaire et active pour Spamhaus.- Dshield (règles
ET DROP Dshield..., sid 2402000+) : vit en réalité dans un fichier séparé,dshield.rules, non couvert par le cronsuricata-drop-fix(qui ne cible quedrop.rules). Vérifié déjà endrop ip [...]directement à la source, sans aucune action de notre part. Pour ce flux précis, la livraison native endroppar ET est confirmée — aucune action requise.
Les règles ET DROP (Spamhaus, Dshield) sont en mode alert par défaut. Les IPs les plus dangereuses d’Internet passent le firewall — elles sont détectées mais pas bloquées.
sed -i '' 's/^alert/drop/' /usr/local/etc/suricata/opnsense.rules/drop.rules
kill -USR2 `pgrep -x suricata`
# Vérifier
grep -c "^drop" /usr/local/etc/suricata/opnsense.rules/drop.rules
grep -c "^alert" /usr/local/etc/suricata/opnsense.rules/drop.rules
Le download nocturne des règles (00:00) régénère drop.rules en mode alert. Un cron à 00:30 corrige automatiquement :
nano /etc/cron.d/suricata-drop-fix
30 0 * * * root sed -i '' 's/^alert/drop/' /usr/local/etc/suricata/opnsense.rules/drop.rules && kill -USR2 `pgrep -x suricata`
cp /etc/cron.d/suricata-drop-fix /conf/suricata-drop-fix
Dans le hook 98-soar-ban, ajouter :
cp /conf/suricata-drop-fix /etc/cron.d/suricata-drop-fix
Contrairement à Spamhaus et Dshield (Partie 4, nativement en drop), le flux ET CINS Active Threat Intelligence (ciarmy.rules — CINS Army, liste de réputation IP) est livré par Emerging Threats en mode alert. Les IP listées comme malveillantes par CINS traversent donc le firewall sans être bloquées :
log.alert.action: allowed
log.alert.metadata.tag: CINS
log.alert.signature: "ET CINS Active Threat Intelligence Poor Reputation IP group X"
Volume mesuré : 299 règles au format alert ip [...] any -> $HOME_NET any dans ciarmy.rules, organisées en groupes (group 1 à group N).
À la différence de la Partie 4 (transformation globale de drop.rules), le sed cible spécifiquement le format alert ip propre à ciarmy.rules, pour éviter tout effet de bord sur d’autres syntaxes de règles potentiellement présentes dans le même fichier :
cp /usr/local/etc/suricata/opnsense.rules/ciarmy.rules /conf/ciarmy.rules.backup-`date +%Y%m%d`
⚠️ Utiliser des backticks, pas
$()— csh (shell par défaut OPNsense) ne supporte pas la substitution de commande au format bash et renvoieIllegal variable name.
nano /usr/local/bin/ciarmy-drop-fix.sh
#!/bin/sh
sed -i '' 's/^alert ip/drop ip/' /usr/local/etc/suricata/opnsense.rules/ciarmy.rules
kill -USR2 `pgrep -x suricata`
chmod +x /usr/local/bin/ciarmy-drop-fix.sh
sh /usr/local/bin/ciarmy-drop-fix.sh
# Vérifier
grep -c "^drop ip" /usr/local/etc/suricata/opnsense.rules/ciarmy.rules # → 299
grep -c "^alert ip" /usr/local/etc/suricata/opnsense.rules/ciarmy.rules # → 0
Confirmation du rechargement (chercher dans le log Suricata courant — latest.log, pas un fichier daté) :
tail -50 /var/log/suricata/latest.log | grep -i "reload complete"
# → [Notice] -- rule reload complete
ciarmy.rules étant un flux ET re-téléchargé périodiquement (révision quasi quotidienne), toute modification manuelle est écrasée au prochain refresh sans automatisation :
nano /etc/cron.d/ciarmy-drop-fix
30 0 * * * root /usr/local/bin/ciarmy-drop-fix.sh
cp /usr/local/bin/ciarmy-drop-fix.sh /conf/ciarmy-drop-fix.sh
cp /etc/cron.d/ciarmy-drop-fix /conf/ciarmy-drop-fix
Dans le hook 98-soar-ban, ajouter :
cp /conf/ciarmy-drop-fix.sh /usr/local/bin/ciarmy-drop-fix.sh && chmod +x /usr/local/bin/ciarmy-drop-fix.sh
cp /conf/ciarmy-drop-fix /etc/cron.d/ciarmy-drop-fix
OPNsense permet de basculer une règle individuelle Alert/Drop via Services → Intrusion Detection → Administration → Rules, mais sans action groupée par fichier source. Avec 299 règles à traiter, le sed reste la seule approche réaliste — l’UI étant réservée à des ajustements ponctuels sur une poignée de règles.
Confirmation côté Log Explorer UTMStack — une IP CINS bloquée par Suricata (action: blocked) déclenche ensuite le flow SOAR Suricata Network Anomaly Detected, qui ban automatiquement l’IP via CrowdSec :
cscli decisions list | grep "<ip>"
# → reason=utmstack, decisions: ban:1
Boucle complète vérifiée de bout en bout : CINS détecté → bloqué par Suricata → remonté UTMStack → SOAR → ban CrowdSec.
Une fois bloqué côté Suricata, le trafic CINS bascule de action: allowed à action: blocked — il devient alors éligible aux exclusions de bruit posées côté règles de corrélation UTMStack, voir annexe — Réduction du bruit, règles de corrélation UTMStack.
Ce lab suit une règle générale (voir l’annexe Réduction du bruit — règles de corrélation UTMStack) : bloquer sur réputation IP confirmée, mais seulement observer sur anomalie comportementale — pour éviter de bloquer par erreur du trafic légitime (Google, Azure, scanners de sécurité…) sur la base d’un simple pattern suspect.
Le cas rencontré ici est une exception délibérée à cette règle, parce qu’il ne présente aucune ambiguïté :
log.http.hostname: 127.0.0.1
log.http.url: /cgi-bin/ViewLog.asp
log.http.http_user_agent: r00ts3c-owned-you
C’est une tentative d’exploitation d’une vulnérabilité connue sur des DVR/NVR en marque blanche (endpoint ViewLog.asp), avec un User-Agent auto-signé par un botnet/scanner automatisé — aucun logiciel légitime n’envoie jamais r00ts3c-owned-you comme User-Agent. Volume observé : 12 tentatives en une journée, depuis 3 IP distinctes (Italie, Hong Kong, Chine), certaines en GET, d’autres en POST — cohérent avec un botnet distribué qui scanne Internet en continu, pas un acteur unique ciblant ce lab spécifiquement.
Avant d’ajouter une règle custom, vérifier qu’aucun SID de la plage utilisée n’est déjà pris ailleurs (ce lab réutilise la tranche 7000000+ depuis NGROK JA3 dans NF-Suricata.rules) :
grep -rhoE "sid:70000[0-9]+" /usr/local/etc/suricata/opnsense.rules/ | sort -u
grep -rhoE "sid:70000[0-9]+" /usr/local/etc/suricata/rules/ | sort -u
ℹ️ Une plage de SID partagée entre plusieurs fichiers de règles (
NF-Suricata.rules,custom.rules, etc.) n’implique pas que chaque fichier contienne une suite continue — les SID 7000001 à 7000006 rencontrés ici vivent dans d’autres fichiers,custom.rulesne contient que le SID retenu ci-dessous.
nano /usr/local/etc/suricata/opnsense.rules/custom.rules
drop http $EXTERNAL_NET any -> $HOME_NET any (msg:"CUSTOM DROP - r00ts3c ViewLog.asp DVR exploit attempt"; flow:established,to_server; http.user_agent; content:"r00ts3c-owned-you"; http.uri; content:"/cgi-bin/ViewLog.asp"; classtype:attempted-admin; sid:7000007; rev:1;)
service suricata restart
Vérification (chercher la confirmation de démarrage complet, pas seulement l’absence d’erreur) :
grep -i "engine started" /var/log/suricata/latest.log | tail -1
custom.rules est réinitialisé chaque nuit à minuitConstaté le 2026-07-08 : la règle, ajoutée et vérifiée la veille au soir (fichier actif + backup /conf/ identiques via diff), avait disparu le lendemain matin — remplacée par des dizaines de répétitions du seul commentaire d’en-tête du fichier, sans plus aucune règle réelle.
ls -la /usr/local/etc/suricata/opnsense.rules/custom.rules
# -rw-r--r-- 1 root wheel 4226 Jul 8 00:00 ...
L’horodatage à 00:00 pile confirme la cause : OPNsense régénère custom.rules à minuit, dans le même cycle que le téléchargement nocturne des règles qui réinitialise déjà drop.rules (Partie 4). Le hook 98-soar-ban ne s’exécute qu’au démarrage du système — il ne protège pas contre ce cycle de régénération qui survient indépendamment de tout reboot. Un uptime au moment de la découverte confirmait que le système n’avait pas redémarré depuis la veille au soir, écartant toute autre explication.
ℹ️ Ce parallèle avec la découverte faite côté UTMStack (les règles de corrélation, elles aussi réinitialisées par un simple redémarrage de conteneur sans reboot complet — voir l’annexe correlation-rules-tuning.md) illustre un principe général valable des deux côtés de l’infrastructure : une protection uniquement au boot ne suffit jamais à elle seule, il faut aussi couvrir les cycles de régénération propres à chaque système.
nano /usr/local/bin/custom-rules-fix.sh
#!/bin/sh
cp /conf/custom.rules /usr/local/etc/suricata/opnsense.rules/custom.rules
kill -USR2 `pgrep -x suricata`
chmod +x /usr/local/bin/custom-rules-fix.sh
nano /etc/cron.d/custom-rules-fix
30 0 * * * root /usr/local/bin/custom-rules-fix.sh
cp /usr/local/bin/custom-rules-fix.sh /conf/custom-rules-fix.sh
cp /etc/cron.d/custom-rules-fix /conf/custom-rules-fix
Dans le hook 98-soar-ban, ajouter :
cp /conf/custom-rules-fix.sh /usr/local/bin/custom-rules-fix.sh && chmod +x /usr/local/bin/custom-rules-fix.sh
cp /conf/custom-rules-fix /etc/cron.d/custom-rules-fix
cp /conf/custom.rules /usr/local/etc/suricata/opnsense.rules/custom.rules
Un premier diff identique entre le fichier actif et le backup peut donner un faux sentiment de sécurité si le backup lui-même a été fait à un mauvais moment (par exemple après que la source ait déjà été vidée par le cycle de minuit). Vérifier les deux dans l’ordre :
grep "7000007" /usr/local/etc/suricata/opnsense.rules/custom.rules
grep "7000007" /conf/custom.rules
diff /usr/local/etc/suricata/opnsense.rules/custom.rules /conf/custom.rules
Un diff silencieux confirme des fichiers strictement identiques — mais seulement si les deux contiennent bien la règle attendue, pas juste une absence commune.
ℹ️ Le fichier accumule à chaque cycle de régénération une nouvelle copie du commentaire d’en-tête sans dédupliquer les précédentes — cosmétique, sans impact sur le fonctionnement de la règle. Pas d’action nécessaire, sauf préférence esthétique.
# Règles NF chargées
grep -c "^drop" /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules
grep -c "^alert" /usr/local/etc/suricata/opnsense.rules/NF-local.rules
grep -c "^drop" /usr/local/etc/suricata/opnsense.rules/NF-Suricata.rules
# threshold-file présent dans suricata.yaml
grep "threshold-file" /usr/local/etc/suricata/suricata.yaml
# Erreurs au chargement Suricata
grep -i "error" /var/log/suricata/suricata_$(date +%Y%m%d).log | grep "NF-"
# Suricata tourne
service suricata status
⚠️ Les règles NF ne sont pas visibles dans l’UI OPNsense (Services → Intrusion Detection → Rules). L’UI ne gère que les règles téléchargées via son propre système (onglet Download). Les règles NF sont chargées directement par Suricata via
installed_rules.yaml— toute gestion passe par la CLI.
# Chercher une règle par SID
grep "sid:5034006" /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules
# Lister les règles par scanner
grep "Censys" /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules
grep "Shodan" /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules
# Désactiver une règle spécifique (commenter)
sed -i '' 's/^drop.*sid:5034006.*/#&/' /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules
# Recharger Suricata après modification
kill -USR2 `pgrep -x suricata | head -1`
OPNsense régénère le répertoire opnsense.rules/ lors de chaque téléchargement de règles via l’UI (Download → Apply). Les fichiers NF sont alors supprimés. Le hook 98-soar-ban ne restaure qu’au boot — pas lors d’un Download en cours d’utilisation.
Solution : cron de surveillance toutes les 30 minutes
# /etc/cron.d/nf-rules-check
*/30 * * * * root test -f /usr/local/etc/suricata/opnsense.rules/NF-Scanners.rules || /usr/local/bin/update-nf-rules.sh >> /var/log/nf-rules-update.log 2>&1
Si NF-Scanners.rules est absent → update-nf-rules.sh est relancé automatiquement.
ℹ️ Testé sur OPNsense 26.1, Suricata 8.0.4 / 8.0.5, suricata-update 1.3.7
| ← SOAR & Automatisation | → SOC AI | Annexe — Réduction du bruit, corrélation UTMStack → |