UTMStack v11.2.8 Community Edition — Procédures de déploiement pour lab PME Suisse. Installation VMware, Suricata, CrowdSec, SOAR, OPNsense.
⚠️ Version v0.1 — Les tests Red Team / Kali seront ajoutés en V4.
UTMStack détecte une alerte
↓
Flow SOAR déclenche soar_ban.bat (agent gest-srv)
↓
SSH → OPNsense (10.100.1.254)
↓
soar_ban.sh — GeoIP lookup + déduplication + cscli ban
↓
CrowdSec banne l'IP 24h
↓
logger syslog → CROWDSEC_BAN avec country/ASN → UTMStack
L’agent UTMStack tourne sous NT AUTHORITY\SYSTEM. La clé SSH doit être accessible depuis ce contexte.
Sur gest-srv en PowerShell admin :
ssh-keygen -t ed25519 -f C:\UTMStack\ssh\soar_key -N ""
mkdir C:\Windows\System32\config\systemprofile\.ssh -ErrorAction SilentlyContinue
schtasks /create /tn "CopySOARKey" /tr "cmd /c copy /y C:\UTMStack\ssh\soar_key C:\Windows\System32\config\systemprofile\.ssh\soar_key" /sc once /st 00:00 /ru SYSTEM /f
schtasks /run /tn "CopySOARKey"
ping -n 8 127.0.0.1 > nul
schtasks /delete /tn "CopySOARKey" /f
# Afficher la clé publique (depuis gest-srv)
type C:\UTMStack\ssh\soar_key.pub
# Sur OPNsense
echo "ssh-ed25519 AAAA... utmstack-soar" > /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
⚠️ Persistance — Configurer la clé via System → Access → Users → root → Authorized Keys dans l’interface GUI OPNsense pour survivre aux reboots.
schtasks /create /tn "SOARTest" /tr "cmd /c C:\Windows\System32\OpenSSH\ssh.exe -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -o BatchMode=yes -o ConnectTimeout=10 -i C:\Windows\System32\config\systemprofile\.ssh\soar_key root@10.100.1.254 echo ssh_ok >> C:\UTMStack\soar_debug.log 2>&1" /sc once /st 00:00 /ru SYSTEM /f
schtasks /run /tn "SOARTest"
ping -n 8 127.0.0.1 > nul
type C:\UTMStack\soar_debug.log
Ce script gère la déduplication et l’enrichissement GeoIP via ipapi.co (1000 req/jour gratuit).
nano /usr/local/bin/soar_ban.sh
#!/bin/sh
IP=$1
# GeoIP lookup (ipapi.co - 1000 req/jour gratuit)
COUNTRY=$(fetch -qo - "https://ipapi.co/$IP/country_code/" 2>/dev/null | tr -d '\n\r')
ASN=$(fetch -qo - "https://ipapi.co/$IP/asn/" 2>/dev/null | tr -d '\n\r')
[ -z "$COUNTRY" ] || [ "$COUNTRY" = "Undefined" ] && COUNTRY="Unknown"
[ -z "$ASN" ] || [ "$ASN" = "Undefined" ] && ASN="AS0"
if ! /usr/local/bin/cscli decisions list --ip "$IP" 2>/dev/null | grep -q "ban"; then
/usr/local/bin/cscli decisions add --ip "$IP" --duration 24h --reason utmstack
logger -p local5.alert -t crowdsec \
"CROWDSEC_BAN {\"event_type\":\"ban\",\"ip\":\"$IP\",\"reason\":\"utmstack\",\"country\":\"$COUNTRY\",\"as\":\"$ASN\",\"type\":\"ban\"}"
echo "Decision successfully added for $IP ($COUNTRY / $ASN)"
else
echo "IP $IP already banned, skipping"
fi
chmod +x /usr/local/bin/soar_ban.sh
cp /usr/local/bin/soar_ban.sh /conf/soar_ban.sh
ℹ️ Les IPs privées (192.168.x.x) retournent “Undefined” depuis ipapi.co — comportement normal.
ℹ️ Les bans SOAR via
csclisont loggués directement par soar_ban.sh. Le scriptcrowdsec-to-syslog.pytraite uniquement les alertes CrowdSec natives — aucun doublon.
nano /usr/local/etc/rc.syshook.d/start/98-soar-ban
#!/bin/sh
cp /conf/soar_ban.sh /usr/local/bin/soar_ban.sh
chmod +x /usr/local/bin/soar_ban.sh
chmod +x /usr/local/etc/rc.syshook.d/start/98-soar-ban
/usr/local/bin/soar_ban.sh 45.205.1.71
# → Decision successfully added for 45.205.1.71 (ES / AS215925)
/usr/local/bin/soar_ban.sh 45.205.1.71
# → IP 45.205.1.71 already banned, skipping
Set-Content "C:\Program Files\UTMStack\UTMStack Agent\soar_ban.bat" -Encoding ASCII -Value '@echo off
if "%1"=="" exit /b 1
set LOGFILE=C:\Program Files\UTMStack\UTMStack Agent\logs\soar.log
for %%F in ("%LOGFILE%") do if %%~zF gtr 1048576 del "%LOGFILE%"
echo [%DATE% %TIME%] SOAR triggered - IP: %1 >> "%LOGFILE%"
C:\Windows\System32\OpenSSH\ssh.exe -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -o BatchMode=yes -o ConnectTimeout=30 -i C:\Windows\System32\config\systemprofile\.ssh\soar_key root@10.100.1.254 "/usr/local/bin/soar_ban.sh %1" >> "%LOGFILE%" 2>&1
echo [%DATE% %TIME%] Exit: %ERRORLEVEL% >> "%LOGFILE%"'
Fonctionnalités :
⚠️ Encodage — Utiliser impérativement
-Encoding ASCII. Un encodage UTF-16 (défaut PowerShell) rend le BAT silencieusement non fonctionnel.
Dans UTMStack → SOAR → Automation Rules → New Rule :
Flow 5000 — Known Malicious IP :
| Champ | Valeur |
|---|---|
| Nom | CrowdSec Ban Known Malicious IP |
| Condition | name IS Known Malicious IP Detected |
| Agent | gest-srv |
| Commande | soar_ban.bat $(adversary.ip) |
Flow 5001 — Suricata Anomaly :
| Champ | Valeur |
|---|---|
| Nom | CrowdSec Ban Suricata Anomaly |
| Condition | name IS Suricata Network Anomaly Detected |
| Agent | gest-srv |
| Commande | soar_ban.bat $(adversary.ip) |
Après toute modification d’un flow, le backend conserve l’ancienne commande en mémoire. L’UI affiche également l’ancienne règle après sauvegarde — les modifications via l’interface ne sont pas fiables.
Procédure obligatoire après modification :
docker exec -it $(docker ps -q -f name=utmstack_postgres) psql -U postgres -d utmstack -c \
"DELETE FROM utm_alert_response_rule_execution WHERE execution_status = 'PENDING';"
docker service update --force utmstack_backend
Modification directe en base (recommandée) :
docker exec -it $(docker ps -q -f name=utmstack_postgres) psql -U postgres -d utmstack -c \
"UPDATE utm_alert_response_rule SET rule_cmd = 'soar_ban.bat \$(adversary.ip)' WHERE id IN (5000, 5001);"
ℹ️ Limitation — UTMStack v11.2.8 n’expose pas
$(origin.geolocation.countryCode)comme variable SOAR au niveau de l’alerte de corrélation, même si ce champ est présent dans les événements sous-jacents. L’enrichissement GeoIP est donc géré directement danssoar_ban.sh.
Si MDE est actif sur l’agent Windows, les règles ASR bloquent silencieusement l’exécution des commandes SOAR.
Symptômes :
soar.log videCause : La règle ASR d1e49aac-8f56-4280-b9ba-993a6d77406c (Block process creations originating from PSExec and WMI commands) bloque les processus enfants créés par le service agent UTMStack. MDE voit le pattern service inconnu → cmd.exe → ssh.exe → connexion externe comme un comportement C2 malware.
Lab — Offboarder MDE :
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\ASR\Rules" -Force
Production — Indicator of Allow :
utmstack_agent_service_windows_amd64.exe → Action : AllowLog soar.log (gest-srv) :
[25.05.2026 16:45:53,87] SOAR triggered - IP: 20.163.15.91
Warning: Permanently added '10.100.1.254' (ED25519) to the list of known hosts.
level=info msg="Decision successfully added"
Decision successfully added for 20.163.15.91 (US / AS8075)
[25.05.2026 16:45:53,87] Exit: 0
Décisions CrowdSec (OPNsense) :
cscli decisions list
→ Ip:20.163.15.91 | utmstack | ban | US | AS8075 (Microsoft)
Les alertes UTMStack sont stockées dans les index OpenSearch v11-alert-YYYY-MM-DD avec les statuts :
| Valeur | Label | Signification |
|---|---|---|
2 |
Open | Alerte non traitée |
5 |
Completed | Alerte traitée / fermée |
Le SOAR traite les alertes Suricata Network Anomaly Detected et Known Malicious IP Detected automatiquement via CrowdSec. Le SOC AI les ferme ensuite via l’option Change alert status after analysis. Un cron sert de filet de sécurité quand le quota API SOC AI est épuisé.
cat > /usr/local/bin/utmstack-close-alerts.sh << 'EOF'
#!/bin/bash
docker exec $(docker ps -q -f name=utmstack_node1) curl -sk \
-u 'admin:<password>' \
-X POST "https://localhost:9200/v11-alert-*/_update_by_query" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"must": [
{"terms": {"name.keyword": ["Suricata Network Anomaly Detected", "Known Malicious IP Detected"]}},
{"term": {"status": 2}},
{"range": {"@timestamp": {"lt": "now-10m"}}}
]
}
},
"script": {
"source": "ctx._source.status = 5; ctx._source.statusLabel = \"Completed\"; ctx._source.statusObservation = \"Auto-closed by SOAR cron after CrowdSec ban\"",
"lang": "painless"
}
}'
EOF
chmod +x /usr/local/bin/utmstack-close-alerts.sh
ℹ️ Le filtre
now-10mgarantit que le SOAR a eu le temps de s’exécuter avant la fermeture.
crontab -e
*/5 * * * * /usr/local/bin/utmstack-close-alerts.sh >> /var/log/utmstack-close-alerts.log 2>&1
Vérification :
tail /var/log/utmstack-close-alerts.log
# Résultat attendu : {"updated": X, "failures": []}
ℹ️
"updated": 0est normal quand le SOC AI a déjà fermé les alertes — le cron est un filet de sécurité, pas le mécanisme principal.
Les règles de corrélation built-in (icône 🚫 dans l’UI) ne sont pas modifiables via l’interface. Modification directe en base :
docker exec -it $(docker ps -q -f name=utmstack_postgres) psql -U postgres -d utmstack
-- Exemple : exclure les scripts MDE de la règle PowerShell Empire Detection
-- Couvre Azure AD Connect (AADConnector.psm1) ET le Log4j scanner MDE (DataCollection)
UPDATE utm_correlation_rules
SET rule_definition_def = rule_definition_def ||
E'\n&& !contains("log.data.Path", "Windows Defender Advanced Threat Protection")'
WHERE rule_name = 'PowerShell Empire Detection'
AND rule_definition_def NOT LIKE '%Windows Defender Advanced Threat Protection%';
Scripts couverts par cette exclusion :
...\Extensions\AADConnector.psm1 — Azure AD Connect (gest-srv)...\DataCollection\*.ps1 — MDE Log4j scanner / NdrScanner (DC01-MAIN-SITE)Restart backend après modification :
docker service update --force utmstack_backend
ℹ️ Testé sur UTMStack v11.2.8, agent Windows v11.1.4, OPNsense 26.1, CrowdSec 1.6.x, ipapi.co free tier
| ← Dashboards UTMStack | → SOC AI |