
Présentation
En Mastère, nous menons à trois un projet d'étude : concevoir une plateforme SOC open source prête à l'emploi pour PME et ETI. Dans l'équipe, je porte l'intégration et l'industrialisation. La maquette tourne dans mon homelab, sur des serveurs Dell sous Proxmox. Une zone de labo isolée, que l'équipe attaque par des VPN dédiés, réunit un serveur web vulnérable (DVWA), des postes Linux et Windows et un futur contrôleur de domaine. Une VM porte la stack SOCFortress : 39 conteneurs en 6 projets Docker Compose (Wazuh, Graylog, Grafana, Velociraptor, TheHive, Cortex, MISP, OpenCTI, Shuffle, Suricata, Zeek, CoPilot). J'ai travaillé avec Claude Code comme agent d'exécution : je fixais le périmètre et tranchais, l'agent écrivait et appliquait les scripts, chaque intervention datée dans le journal du dépôt. J'ai installé moi-même les agents et Sysmon sur les postes Windows.
Vue technique du SOC dans Grafana : alertes Wazuh par gravité, alertes Suricata, détections Graylog et tactiques MITRE ATT&CK sur 24 heures, avec les données réelles de la maquette.
Une stack qui se relève seule
J'ai d'abord déplacé la stack d'une VM de l'école vers le homelab : images tirées par empreinte sha256 (versions exactes), 47 volumes recopiés. En set -e, le start-all.sh d'origine s'arrêtait sur un misp-core encore « unhealthy » et laissait MISP, OpenCTI, Shuffle et les sondes éteints : je l'ai réécrit avec trois essais par projet et des attentes explicites. Le manager Wazuh boucle quand son conteneur redémarre, mais un conteneur neuf démarre : siem-demarrer le recrée au boot, et le timer siem-surveiller relance toutes les 5 minutes un projet incomplet. Après une coupure brutale, la stack est revenue seule en 4 minutes environ.
Un pare-feu qui n'écrit pas son nom
Faute de paquet d'agent Wazuh pour pfSense, ses journaux arrivent en syslog BSD, sans nom d'hôte. Wazuh prenait filterlog[pid]: pour l'hôte et le décodeur officiel ne s'appliquait jamais. Le format RFC 5424 n'étant pas lu par cette version de Wazuh, j'ai corrigé à la réception : une copie locale des décodeurs remplace l'original, exclu par <decoder_exclude>, avec un second parent qui reconnaît ce préfixe.
<decoder name="pf">
<program_name>filterlog</program_name>
</decoder>
<decoder name="pf">
<prematch>^filterlog[\d+]: </prematch>
</decoder>
<decoder name="pf-fields">
<parent>pf</parent>
<regex offset="after_parent">^\S*,\S*,\S*,(\S*),\S*,\S*,(\S*),</regex>
<order>id,action</order>
</decoder>Une alerte 87702 (18 blocages d'une même source en 45 secondes) part alors vers Shuffle puis TheHive. La règle locale 100700 ignore les sources extérieures au homelab, pour que les scans d'internet n'inondent pas TheHive. Dans Graylog, un pipeline corrige la source et l'heure : sans année ni fuseau, l'horodatage BSD était rangé deux heures dans le futur. Après une recréation du manager, une entrée conntrack UDP non redirigée par Docker faisait refuser le syslog : siem-syslog-conntrack la supprime.
Graylog : connexions d'internet bloquées par pfSense en 24 heures, géolocalisées et réparties par pays à partir des journaux syslog du pare-feu.
Un miroir de port pour les sondes
Suricata et Zeek ne voyaient que le trafic du SIEM. Sur l'hôte Proxmox, j'ai ajouté un pont de capture sans uplink et une seconde carte au SIEM, en mode espion, sans adresse IP. Le script siem-span.sh pose sur les cartes des VM du labo une qdisc clsact et des filtres matchall dont l'action mirred recopie le trafic vers cette carte ; un timer le réapplique chaque minute. Zeek voit maintenant le trafic entre machines du labo, qui ne passe jamais par le pare-feu : 162 connexions vers 153 ports pendant un balayage. Une fois le miroir étendu au WAN du pfSense, environ 40 alertes Suricata venues d'internet sont arrivées dans TheHive en 15 minutes : j'ai restreint la définition d'événement Graylog aux alertes qui touchent le réseau interne. Les autres zones restent hors du miroir : le SIEM est dans la zone des attaquants.
Onglet Zeek du dashboard réseau : connexions vues sur le miroir de port, par service, et indice de balayage qui classe les sources par nombre de ports distincts (ici des scanners d'internet, adresses masquées).
Ce qui se passe après l'intrusion
Sur les Linux du labo, auditd surveille les fichiers d'identité, la configuration SSH, cron, les unités systemd, les modules noyau et des outils comme nc ou nmap. Wazuh classe auditd au niveau 0 par défaut ; mes règles locales 100710 à 100716 relèvent ces événements aux niveaux 8 à 12 et les relient à MITRE ATT&CK. Journaliser toutes les exécutions (-S execve) produisait environ 1 000 événements par minute et en faisait perdre : je suis revenu à des surveillances par chemin. Le rechargement des règles émettait des CONFIG_CHANGE portant la clé surveillée, d'où l'exigence du champ audit.exe, présent seulement sur une vraie action. Enfin, homelab_graylog.py crée de façon idempotente un pipeline d'enrichissement commun (zone, gravité, tactique MITRE, GeoIP) et trois dashboards, puis exécute leurs 41 widgets pour repérer les erreurs.
Wazuh sur sept jours : la règle locale 100712 relève au niveau 10 des écritures dans /root/.ssh, à côté d'un exécutable créé par PowerShell sur le poste Windows (noms de machines masqués).
Ce que la campagne de test a montré
J'ai validé l'ensemble par des attaques standard sur mes propres machines, après instantanés : balayage, force brute SSH, injections SQL sur DVWA, persistance par cron, reconnaissance Windows lancée par Velociraptor. Tout a été détecté, et les alertes de niveau 8 et plus sont arrivées dans TheHive. Mais Suricata n'alerte pas sur un balayage « connect » lent avec les règles ET open : seuls Zeek et un événement Graylog le voient. Les attaques web restent sous le seuil de TheHive. La VM n'a pas encore de sauvegarde, et je n'ai pas vérifié le rendu visuel des dashboards. J'en retiens qu'une stack SIEM qui tourne ne voit qu'une partie des attaques : l'essentiel du travail a été de fermer des angles morts et de couper le bruit, puis de vérifier les détections par de vraies attaques.