UTMStack Lab — Guide et Procédures de déploiement

UTMStack v11.2.8 Community Edition — Procédures de déploiement pour lab PME Suisse. Installation VMware, Suricata, CrowdSec, SOAR, OPNsense.

View the Project on GitHub doit4everyone/utmstack-lab

SOAR & Automatisation

← Retour à l’index

⚠️ Version v0.1 — Les tests Red Team / Kali seront ajoutés en V4.


Architecture

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

Étape 1 — Clé SSH pour le compte SYSTEM

L’agent UTMStack tourne sous NT AUTHORITY\SYSTEM. La clé SSH doit être accessible depuis ce contexte.

Génération de la paire de clés

Sur gest-srv en PowerShell admin :

ssh-keygen -t ed25519 -f C:\UTMStack\ssh\soar_key -N ""

Dépôt de la clé dans le profil SYSTEM

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

Autorisation de la clé sur OPNsense

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

Test depuis SYSTEM

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

Étape 2 — Script soar_ban.sh sur OPNsense

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 cscli sont loggués directement par soar_ban.sh. Le script crowdsec-to-syslog.py traite uniquement les alertes CrowdSec natives — aucun doublon.

Persistance aux reboots et mises à jour OPNsense

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

Test

/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

Étape 3 — Script soar_ban.bat sur gest-srv

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.


Étape 4 — Flows SOAR dans UTMStack

Création des règles

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)

⚠️ Bug UTMStack v11.2.8 — Cache backend

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 dans soar_ban.sh.


Étape 5 — Compatibilité Microsoft Defender for Endpoint

Si MDE est actif sur l’agent Windows, les règles ASR bloquent silencieusement l’exécution des commandes SOAR.

Symptômes :

Cause : 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 :


Vérification end-to-end

Log 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)

Étape 6 — Auto-fermeture des alertes après traitement SOAR

Contexte

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

Script de fermeture

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-10m garantit que le SOAR a eu le temps de s’exécuter avant la fermeture.

Cron — toutes les 5 minutes

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": 0 est normal quand le SOC AI a déjà fermé les alertes — le cron est un filet de sécurité, pas le mécanisme principal.


Réduction des faux positifs — Règles built-in

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 :

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