Architecture des Journaux d'Événements Windows : Pourquoi Votre SIEM Manque Probablement 30% des Événements et Comment le Vérifier
Un analyste signale une alerte suspecte de mouvement latéral. Vous consultez la chronologie de l'enquête. Il y a un écart de 47 minutes dans les événements de création de processus d'un serveur critique exactement dans la fenêtre où l'attaquant s'est déplacé. L'EDR ne montre rien. Le SIEM ne montre rien. L'analyse forensique post-incident sur la machine locale révèle 6 800 événements qui n'ont jamais quitté le terminal. Le journal d'événements de sécurité s'est réécrit sur lui-même. L'abonnement WEF avait un bug de filtre. Le serveur WEC était sous charge. Personne ne l'a remarqué parce que personne ne mesurait. Ce scénario n'est pas hypothétique c'est la cause racine la plus fréquente des lacunes de détection trouvées lors des revues post-incident, et il est presque entièrement évitable.
Pourquoi Cela Importe Plus Que N'importe Quelle Règle de Détection Que Vous Écrirez
Les équipes de sécurité investissent d'énormes efforts pour écrire des règles de détection, ajuster Sigma et élargir la couverture MITRE ATT&CK. Ces efforts sont inutiles si les événements sous-jacents n'atteignent jamais votre SIEM.
L'hypothèse intégrée dans pratiquement chaque tableau de bord SIEM est que le pipeline de collecte d'événements fonctionne. Cette hypothèse n'est presque jamais testée, et quand elle échoue, elle échoue silencieusement. Il n'y a pas d'alerte pour "nous avons arrêté de recevoir des événements de création de processus de cet hôte." Il n'y a pas de tuile de tableau de bord qui devient rouge quand votre serveur WEC commence à perdre des événements sous charge. Il n'y a pas de notification automatique quand un conflit GPO annule silencieusement votre stratégie d'audit avancée aux valeurs par défaut.
Le résultat est ce que les ingénieurs en sécurité appellent parfois la couverture théâtrale vous avez les règles, vous avez les tableaux de bord, vous avez la carte de chaleur ATT&CK éclairée, mais en dessous se trouve une infrastructure de collecte avec de vraies lacunes qu'un attaquant qui comprend les mécanismes internes Windows ne déclenchera jamais d'alerte.
Cet article part des premiers principes comment la journalisation des événements Windows fonctionne réellement en interne à travers les modes de défaillance spécifiques qui causent la perte d'événements, et se termine avec des outils concrets et des scripts que vous pouvez exécuter cette semaine pour mesurer votre fidélité de collecte réelle.
Partie 1 L'Architecture : De l'Événement Noyau à l'Enregistrement SIEM
Comprendre où les événements peuvent être perdus nécessite de comprendre le pipeline complet. La plupart des praticiens connaissent le modèle de haut niveau. Peu connaissent les mécanismes internes où les choses se cassent réellement.
1.1 Event Tracing for Windows (ETW) : La Fondation du Noyau
Chaque événement Windows prend naissance dans Event Tracing for Windows (ETW) le sous-système noyau de bas niveau qui sert de colonne vertébrale à toute la télémétrie Windows. ETW n'est pas la même chose que le Journal d'Événements Windows. C'est le mécanisme de transport sous-jacent.
Dix points de défaillance distincts sur quatre couches. Un événement peut être perdu à n'importe lequel d'entre eux, sans notification à l'analyste de l'autre côté.
1.2 Le Tampon Circulaire ETW Là Où les Événements Naissent et sont Perdus en Premier
ETW fonctionne en utilisant des tampons circulaires en mémoire des régions de mémoire circulaires dans lesquelles les fournisseurs écrivent des événements. Les consommateurs (y compris le service Journal d'Événements Windows) lisent depuis ces tampons. Quand un tampon se remplit plus vite que les consommateurs peuvent le vider, les nouveaux événements écrasent les anciens en mémoire avant d'être écrits sur disque.
Ce n'est pas la même chose que l'écrasement du journal (qui se produit sur disque). Le débordement du tampon circulaire ETW est une perte silencieuse en mémoire qui ne laisse aucune trace des événements perdus pas même un écart dans la séquence EventRecordID.
Les paramètres du tampon ETW sont configurables mais presque jamais ajustés :
:: Afficher la configuration actuelle de la session ETW pour une session spécifique
logman query "EventLog-Security" -ets
:: Exemple de sortie :
:: Name: EventLog-Security
:: Status: Running
:: Root Path: %systemdrive%\PerfLogs\Admin
:: Segment: Off
:: Schedules: On
:: Segment Max Size: 100 MB
::
:: Name: EventLog-Security
:: Type: Trace
:: Append: Off
:: Circular: Off
:: Overwrite: Off
:: Buffer Size: 64 ← 64Ko par tampon
:: Buffers Lost: 0 ← Surveiller ce nombre
:: Buffers Written: 15432
:: Buffer Flush Timer: 1
:: Clock Type: System
:: File Mode: Real-time
Le compteur Buffers Lost est la métrique clé. S'il est non nul, des événements sont perdus dans ETW avant même que le service Journal d'Événements ne les voie. Vérifiez ceci sur les contrôleurs de domaine et les serveurs à haute activité :
# Vérifier les pertes de tampon ETW pour toutes les sessions actives liées à la sécurité
Get-WinEvent -ListLog Security | Select-Object LogName, RecordCount, IsEnabled
# Plus détaillé : vérifier les statistiques de session ETW via les Compteurs de Performance
$cheminCompteurs = @(
'\Security System-Wide Statistics\Audit Failures',
'\Security System-Wide Statistics\System Events'
)
Get-Counter -Counter $cheminCompteurs -SampleInterval 1 -MaxSamples 5
1.3 Le Fichier EVTX : Structure et Comment Fonctionnent les Écrasements
Les journaux d'événements Windows sont stockés sous forme de fichiers .evtx (XML Event Log) dans C:\Windows\System32\winevt\logs\. Le format utilise une structure binaire par blocs :
Quand le journal s'enroule, les EventRecordIDs continuent d'incrémenter ils ne se remettent pas à zéro. Cela signifie que vous pouvez détecter les écarts d'écrasement en recherchant des discontinuités dans la séquence EventRecordID. Un saut de EventRecordID 482 441 à 489 209 signifie que 6 768 événements ont été écrasés et sont perdus.
# Détecter les écarts EventRecordID indiquant un écrasement du journal
# Exécuter sur un hôte distant ou localement
$evenements = Get-WinEvent -LogName Security -MaxEvents 100 |
Select-Object RecordId, TimeCreated, Id |
Sort-Object RecordId
for ($i = 1; $i -lt $evenements.Count; $i++) {
$ecart = $evenements[$i].RecordId - $evenements[$i-1].RecordId
if ($ecart -gt 1) {
Write-Output "ÉCART DÉTECTÉ : RecordId a sauté de $($evenements[$i-1].RecordId) à $($evenements[$i].RecordId)"
Write-Output " Événements manquants : $($ecart - 1)"
Write-Output " Moment de l'écart : $($evenements[$i-1].TimeCreated) → $($evenements[$i].TimeCreated)"
}
}
Partie 2 Stratégie d'Audit : La Mauvaise Configuration Silencieuse
Avant qu'un seul événement ne se déplace, il doit d'abord être généré. La stratégie d'audit contrôle ce que le Security Reference Monitor (le composant noyau qui applique la stratégie de sécurité) enregistre réellement. C'est là que la majorité des lacunes de couverture défensive prennent naissance pas dans le pipeline de collecte, mais dans la stratégie qui contrôle si les événements sont générés.
2.1 Stratégie d'Audit Héritée vs. Avancée Le Conflit Qui Désactive Silencieusement Votre Journalisation
Windows dispose de deux systèmes de stratégie d'audit qui peuvent entrer en conflit :
| Système | Emplacement | Granularité | Sous-catégories |
|---|---|---|---|
| Stratégie d'Audit Héritée | secpol.msc → Stratégies locales → Stratégie d'audit | 9 catégories de niveau supérieur | Aucune |
| Stratégie d'Audit Avancée | secpol.msc → Configuration de la stratégie d'audit avancée | 10 catégories, 58 sous-catégories | Contrôle total |
Le comportement critique, fréquemment méconnu : si les deux sont configurées, la stratégie héritée gagne par défaut et remplace silencieusement les sous-catégories de la stratégie avancée.
Exemple du conflit :
La correction un paramètre GPO manquant dans la plupart des organisations :
Chemin GPO : Configuration ordinateur → Paramètres Windows → Paramètres de sécurité →
Stratégies locales → Options de sécurité
Paramètre : "Audit: Forcer les paramètres de sous-catégorie de stratégie d'audit
(Windows Vista ou ultérieur) à substituer les paramètres de catégorie
de stratégie d'audit"
Valeur : ACTIVÉ
Sans ce paramètre activé, toute stratégie d'audit héritée dans la hiérarchie GPO annule silencieusement vos sous-catégories de stratégie avancée.
2.2 Lire Votre Stratégie d'Audit Effective Réelle (Pas Ce Que Vous Avez Configuré)
L'éditeur GPO montre ce que vous avez configuré. auditpol.exe montre ce qui est réellement en vigueur sur une machine donnée. Ces deux éléments sont souvent différents.
:: Afficher la stratégie d'audit effective complète toutes les 58 sous-catégories
:: Exécuter sur un DC, serveur critique ou poste de travail à vérifier
auditpol /get /category:*
:: Exemple de sortie (montrant les zones de lacunes courantes) :
:: System audit policy
:: Category/Subcategory Setting
::
:: Account Logon
:: Credential Validation No Auditing ← PROBLÈME : connexions non journalisées
:: Kerberos Authentication Service Success ← OK
:: Kerberos Service Ticket Operations Success ← Événements d'échec manquants
:: Other Account Logon Events No Auditing ← PROBLÈME
::
:: Logon/Logoff
:: Logon Success and Failure
:: Logoff Success
:: Account Lockout Success
:: Special Logon No Auditing ← PROBLÈME : connexions admin manquées
:: Other Logon/Logoff Events No Auditing ← PROBLÈME
::
:: Object Access
:: File System No Auditing ← Peut être intentionnel (trop bruyant)
:: Registry No Auditing
:: SAM No Auditing ← PROBLÈME sur les DCs
:: Certification Services No Auditing ← Attaques ADCS invisibles
:: Detailed File Share No Auditing
:: File Share No Auditing ← Mouvement latéral via partages
::
:: Privilege Use
:: Sensitive Privilege Use No Auditing ← PROBLÈME : SeDebugPrivilege, etc.
:: Non Sensitive Privilege Use No Auditing ← Généralement intentionnel (bruyant)
Audit scripté sur votre parc :
# Collecter la stratégie d'audit de plusieurs machines distantes et comparer à la référence
$hôtesCibles = @("DC01", "DC02", "SERVER01", "WSADMIN01")
$résultats = @()
foreach ($hôte in $hôtesCibles) {
try {
$sortie = Invoke-Command -ComputerName $hôte -ScriptBlock {
$brut = auditpol /get /category:* /r # Format CSV
$brut | ConvertFrom-Csv
} -ErrorAction Stop
foreach ($ligne in $sortie) {
$résultats += [PSCustomObject]@{
NomOrdinateur = $hôte
Catégorie = $ligne.'Category/Subcategory'
Paramètre = $ligne.'Inclusion Setting'
}
}
} catch {
Write-Warning "Échec de la requête sur $hôte : $_"
}
}
# Trouver les hôtes où "Credential Validation" n'est PAS audité
$résultats | Where-Object {
$_.Catégorie -like "*Credential Validation*" -and
$_.Paramètre -eq "No Auditing"
} | Select-Object NomOrdinateur, Catégorie, Paramètre
# Exporter la comparaison complète
$résultats | Export-Csv "stratégie_audit_parc.csv" -NoTypeInformation
2.3 Les Sous-catégories Qui Doivent Être Activées (Et Pourquoi)
Le tableau suivant associe les sous-catégories les plus critiques pour la détection aux techniques d'attaque spécifiques qu'elles couvrent. Il s'agit de la référence minimale pour un environnement capable de détecter :
| Sous-catégorie | IDs d'Événements | Couvre | État par Défaut |
|---|---|---|---|
| Credential Validation | 4776, 4768, 4771 | Auth NTLM, TGT Kerberos, échec de pré-auth | ❌ Désactivé sur beaucoup de systèmes |
| Kerberos Service Ticket Operations | 4769 | Kerberoasting, silver ticket | ⚠ Succès uniquement (manque les échecs) |
| Process Creation | 4688 | Toutes les exécutions de processus | ❌ Désactivé par défaut |
| Process Termination | 4689 | Reconstruction de chronologie | ❌ Désactivé par défaut |
| DPAPI Activity | 4693, 4694 | Déchiffrement d'identifiants par malware | ❌ Désactivé par défaut |
| Special Logon | 4672 | Connexion équivalente admin (SeDebug, etc.) | ❌ Désactivé sur beaucoup de systèmes |
| Sensitive Privilege Use | 4673, 4674 | Preuves d'élévation de privilèges | ❌ Désactivé par défaut |
| Security Group Management | 4728, 4732, 4756 | Changements de membres de groupes | ✅ Activé sur les DCs |
| Directory Service Access | 4661, 4662 | DCSync, accès aux objets AD | ⚠ Souvent désactivé (volume élevé) |
| Directory Service Changes | 5136, 5137, 5141 | Création/modification d'objets AD | ⚠ Parfois désactivé |
| Audit Policy Change | 4719 | Quelqu'un modifiant la stratégie d'audit | ⚠ Souvent désactivé |
| Filtering Platform Connection | 5156, 5158 | Connexions réseau par processus | ❌ Désactivé extrêmement bruyant |
| Other Object Access | 4698, 4700, 4702 | Création de tâches planifiées | ❌ Désactivé sur beaucoup de systèmes |
Critique : activer la création de processus (4688) avec la journalisation de la ligne de commande
L'événement 4688 journalise la création de processus, mais sans un paramètre de registre supplémentaire, la ligne de commande n'est PAS incluse rendant l'événement largement inutile pour détecter l'abus de LOLBin, les attaques PowerShell, ou tout ce qui repose sur des arguments de ligne de commande :
# Activer la journalisation de la ligne de commande dans les événements de création de processus (4688)
# Ceci doit être défini SÉPARÉMENT de la sous-catégorie de la stratégie d'audit
$cheminRegistre = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System\Audit"
if (-not (Test-Path $cheminRegistre)) {
New-Item -Path $cheminRegistre -Force | Out-Null
}
Set-ItemProperty -Path $cheminRegistre `
-Name "ProcessCreationIncludeCmdLine_Enabled" `
-Value 1 -Type DWord
# Vérifier que le paramètre a été appliqué :
Get-ItemProperty -Path $cheminRegistre -Name "ProcessCreationIncludeCmdLine_Enabled"
Sans cette valeur de registre, vous verrez des événements 4688 avec CommandLine: - une ligne de commande vide. Chaque règle que vous écrivez pour détecter powershell -enc, certutil -urlcache ou wmic ne se déclenchera silencieusement jamais.
Partie 3 Taille des Journaux : La Cause la Plus Fréquente des Écrasements
Les tailles de journaux par défaut pour les canaux de sécurité Windows sont ridiculement insuffisantes pour les environnements d'entreprise avec des stratégies d'audit de sécurité actives :
| Canal de Journal | Taille Max Défaut Windows | Événements Par Jour (DC occupé) | Rétention au Défaut |
|---|---|---|---|
| Security | 20 Mo | 500 000–2 000 000+ | < 1 heure |
| System | 20 Mo | 10 000–50 000 | 8–24 heures |
| Application | 20 Mo | 5 000–20 000 | 1–3 jours |
| PowerShell/Operational | 15 Mo | 20 000–200 000 | 1–4 heures |
| Sysmon/Operational | 20 Mo | 200 000–1 000 000+ | Minutes |
Un contrôleur de domaine occupé générant 1 million d'événements de sécurité par jour réécrit son journal de sécurité de 20 Mo environ toutes les 2 minutes.
3.1 Définir des Tailles de Journaux Appropriées
:: Définir le journal de sécurité à 4 Go (recommandé pour les DCs avec des stratégies d'audit actives)
wevtutil sl Security /ms:4294967296
:: Définir le journal opérationnel Sysmon à 2 Go
wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:2147483648
:: Définir le journal opérationnel PowerShell à 1 Go
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824
:: Définir le journal Application à 500 Mo
wevtutil sl Application /ms:524288000
:: Définir le journal System à 500 Mo
wevtutil sl System /ms:524288000
:: Vérifier que le changement a pris effet :
wevtutil gl Security
:: La sortie inclut :
:: maxSize: 4294967296
:: retention: false ← "false" = écraser si nécessaire (paramètre correct)
:: autoBackup: false
Déploiement via GPO (la bonne façon à grande échelle) :
Chemin GPO : Configuration ordinateur → Modèles d'administration →
Composants Windows → Service Journal des événements → Security
Paramètre : "Spécifier la taille maximale du fichier journal (Ko)"
Valeur : 4194304 (= 4 Go pour les DCs)
1048576 (= 1 Go pour les serveurs)
512000 (= 500 Mo pour les postes de travail)
Paramètre : "Contrôler le comportement du journal des événements quand le fichier atteint
sa taille maximale"
Valeur : NON configuré (laisser le comportement d'écrasement par défaut)
[Ne PAS définir "Ne pas remplacer les événements" sauf si vous avez une collecte extrêmement rapide]
3.2 Vérifier l'État Actuel des Journaux sur Votre Parc
# Inventaire des tailles de journaux, pourcentage de remplissage et événement le plus ancien
$hôtes = @("DC01", "DC02", "SERVER01", "SERVER02")
$nomsJournaux = @("Security", "System", "Microsoft-Windows-Sysmon/Operational",
"Microsoft-Windows-PowerShell/Operational")
$rapport = @()
foreach ($ordinateur in $hôtes) {
foreach ($nomJournal in $nomsJournaux) {
try {
$journal = Invoke-Command -ComputerName $ordinateur -ScriptBlock {
param($nj)
$j = Get-WinEvent -ListLog $nj -ErrorAction SilentlyContinue
if ($j) {
[PSCustomObject]@{
NomJournal = $j.LogName
TailleMaxMo = [math]::Round($j.MaximumSizeInBytes / 1MB, 1)
TailleActuelleMo= [math]::Round($j.FileSize / 1MB, 1)
PctRempli = [math]::Round(($j.FileSize / $j.MaximumSizeInBytes) * 100, 1)
NbreEnreg = $j.RecordCount
Activé = $j.IsEnabled
EnregPlusAncien = if ($j.RecordCount -gt 0) {
(Get-WinEvent -LogName $nj -MaxEvents 1 -Oldest -ErrorAction SilentlyContinue).TimeCreated
} else { $null }
}
}
} -ArgumentList $nomJournal -ErrorAction SilentlyContinue
if ($journal) {
$journal | Add-Member -NotePropertyName NomOrdinateur -NotePropertyValue $ordinateur
$rapport += $journal
}
} catch {}
}
}
# Signaler tout journal retenant moins de 24 heures d'événements
$rapport | Where-Object {
$_.EnregPlusAncien -and
((Get-Date) - $_.EnregPlusAncien).TotalHours -lt 24
} | Select-Object NomOrdinateur, NomJournal, TailleMaxMo, PctRempli, EnregPlusAncien |
Format-Table -AutoSize
# Exporter le rapport complet
$rapport | Export-Csv "inventaire_journaux.csv" -NoTypeInformation
Partie 4 Windows Event Forwarding : Le Pipeline Qui Perd Silencieusement des Événements
Pour les organisations utilisant WEF/WEC plutôt que ou en plus d'un agent SIEM, le pipeline de transfert introduit des modes de défaillance supplémentaires qui sont largement invisibles sans surveillance explicite.
4.1 Architecture WEF et le Modèle d'Abonnement
WEF utilise WinRM (port 5985 HTTP / 5986 HTTPS) pour transporter les événements des machines sources vers un serveur Windows Event Collector (WEC). Le flux :
Le mécanisme de signet et comment il échoue :
WEC maintient un signet par machine source par abonnement, suivant le dernier EventRecordID transmis avec succès. Quand une source se reconnecte après être hors ligne, le transfert reprend depuis le signet. Cela semble fiable. Il a deux modes de défaillance critiques :
- Le journal local de la source a écrasé la position du signet. Si la source était hors ligne et que son journal de sécurité s'était réécrit avant la reconnexion, le WEC reprend depuis le signet qui n'existe plus dans le journal. Les événements entre le dernier signet et la position actuelle sont silencieusement perdus. Le WEC ne reçoit aucune notification qu'un écart existe.
- Le signet lui-même est dans le registre WEC et peut être corrompu. Si le serveur WEC plante ou que le registre devient incohérent, les signets se réinitialisent, causant soit des doublons soit des événements manquants.
La documentation officielle de Microsoft le reconnaît explicitement :
"Quand le journal d'événements écrase des événements existants (entraînant une perte de données si l'appareil n'est pas connecté au Collecteur d'Événements), aucune notification n'est envoyée au collecteur WEF que des événements ont été perdus chez le client. Il n'y a pas non plus d'indicateur qu'un écart a été rencontré dans le flux d'événements."
4.2 Les Trois Modes d'Optimisation de Livraison WEF
WEF offre trois modes de livraison qui échangent latence contre fiabilité. La plupart des organisations laissent le mode par défaut, qui est optimisé pour le mauvais scénario :
:: Afficher la configuration actuelle de l'abonnement
wecutil gs "BaselineSubscription"
:: Le champ "DeliveryMaxLatency" contrôle le mode de livraison :
::
:: Normal (par défaut) : Délai de livraison de 15 minutes. Regroupe les événements.
:: Événements mis en tampon sur la source jusqu'à 15 minutes.
:: Lors d'un incident de 4 minutes, vous pouvez voir ZÉRO événement dans le SIEM.
::
:: Minimize Latency : Délai de livraison de 30 secondes.
:: Meilleur pour la détection mais charge WEC plus élevée.
::
:: Minimize Bandwidth : Délai de livraison de 6 heures.
:: Clairement inadapté aux cas d'utilisation sécurité.
:: Définir un abonnement en mode Minimize Latency :
wecutil ss "BaselineSubscription" /cm:MinLatency
:: Ou définir un timing personnalisé (livraison toutes les 30 secondes, battement toutes les 60) :
wecutil ss "BaselineSubscription" /cm:Custom /hi:60000 /dmi:30000
:: Vérifier :
wecutil gs "BaselineSubscription" | findstr -i "latency\|heartbeat\|delivery"
En mode Normal, un incident de 15 minutes peut générer zéro alertes SIEM parce que les événements n'ont pas encore été transférés. Ce n'est pas une préoccupation théorique c'est un comportement documenté qui impacte directement le temps moyen de détection.
4.3 Limites de Capacité du Serveur WEC et Comportement de Perte
Un serveur WEC sur du matériel standard gère environ 3 000 événements par seconde en moyenne sur tous les abonnements. Cela semble beaucoup. Ce ne l'est pas, pour une grande entreprise.
Calcul : 1 000 postes de travail × 150 événements/sec chacun au pic (tempêtes de connexion, mardi des correctifs, réponse aux incidents) = 150 000 événements/sec. Un seul serveur WEC sera saturé à ~2% de cette charge.
Quand le serveur WEC dépasse sa capacité :
Surveiller la santé WEC avec ces compteurs de performance :
# Exécuter sur le serveur WEC
$compteurs = @(
'\Event Tracing for Windows Session(EventLog-ForwardedEvents)\Events Lost',
'\Event Tracing for Windows Session(EventLog-ForwardedEvents)\Events Logged per second',
'\Web Service(_Total)\Current Connections',
'\Web Service(_Total)\Maximum Connections',
'\Processor(_Total)\% Processor Time',
'\Memory\Available MBytes'
)
# Surveillance continue avec des échantillons de 10 secondes
Get-Counter -Counter $compteurs -SampleInterval 10 -MaxSamples 60 |
Select-Object -ExpandProperty CounterSamples |
Select-Object Path, CookedValue, Timestamp |
Format-Table -AutoSize
# Surveiller spécifiquement le compteur Events Lost toute valeur non nulle est critique
Get-Counter '\Event Tracing for Windows Session(EventLog-ForwardedEvents)\Events Lost' `
-SampleInterval 5 -MaxSamples 12 |
Select-Object -ExpandProperty CounterSamples |
Where-Object { $_.CookedValue -gt 0 } |
ForEach-Object { Write-Warning "ÉVÉNEMENTS PERDUS à $($_.Timestamp) : $($_.CookedValue)" }
4.4 Filtres d'Abonnement XPath : Les Lacunes Que Vous Avez Introduites Intentionnellement
Les abonnements WEF utilisent des requêtes XPath pour filtrer les événements à transmettre. Ces requêtes sont puissantes mais sujettes aux erreurs. Une erreur de syntaxe ou une logique incorrecte dans un filtre XPath exclut silencieusement des événements sans message d'erreur.
Exemple d'un filtre XPath cassé qui manque silencieusement des événements :
<!-- CASSÉ : Ce filtre essaie de capturer Event ID 4688 ET 4624
mais le XPath est sémantiquement incorrect ne correspondra à rien -->
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4688)]] AND *[System[(EventID=4624)]]
</Select>
</Query>
<!-- CORRECT : Utiliser des éléments Select séparés ou la syntaxe OR XPath correcte -->
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4688 or EventID=4624)]]
</Select>
</Query>
Valider vos filtres XPath avant déploiement :
# Tester un filtre XPath sur les journaux locaux avant de le mettre dans un abonnement
# Révèle si la syntaxe du filtre est correcte et retourne des événements
$xpath = "*[System[(EventID=4688 or EventID=4624 or EventID=4625)]]"
$nomJournal = "Security"
try {
$evenements = Get-WinEvent -LogName $nomJournal -FilterXPath $xpath -MaxEvents 10 -ErrorAction Stop
Write-Host "Filtre XPath valide. $($evenements.Count) événements récents correspondants."
$evenements | Select-Object TimeCreated, Id, Message | Format-Table -AutoSize
} catch [System.Exception] {
Write-Error "Filtre XPath INVALIDE ou aucun événement correspondant : $_"
}
# Valider également que les IDs d'événements clés SONT présents dans le journal
# (s'ils ne le sont pas, la stratégie d'audit ne les génère pas)
$IDsEvenementsClés = @(4688, 4624, 4625, 4672, 4698, 4719, 4776)
foreach ($id in $IDsEvenementsClés) {
$nombre = (Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=$id]]" `
-MaxEvents 1000 -ErrorAction SilentlyContinue).Count
$statut = if ($nombre -gt 0) { "✓ Présent ($nombre dans les 1000 derniers)" } else { "⚠ ABSENT vérifier la stratégie d'audit" }
Write-Host "ID Événement $id : $statut"
}
Partie 5 La Couche Agent SIEM : Points de Perte Cachés
Les agents SIEM (Splunk Universal Forwarder, Elastic Agent, Microsoft Monitoring Agent, etc.) introduisent leurs propres modes de défaillance. Ceux-ci sont fréquemment négligés parce que l'agent est "en cours d'exécution" et envoie des battements de cœur au SIEM, même en perdant des événements.
5.1 La Condition de Compétition du Signet
Les agents SIEM lisant des fichiers .evtx maintiennent un signet local (marqueur de position) dans le fichier qu'ils lisent. L'agent lit depuis le signet vers l'avant, envoie les événements et met à jour le signet. La condition de compétition :
La correction est double : rendre le journal assez grand pour qu'il ne s'enroule pas pendant le cycle de lecture de l'agent, et s'assurer que l'intervalle de traitement par lots de l'agent est assez court par rapport au taux de génération d'événements. Pour Splunk UF :
# inputs.conf Réglage du Splunk Universal Forwarder pour les journaux Security à haut volume
[WinEventLog://Security]
disabled = 0
start_from = oldest
current_only = 0
checkpointInterval = 5 # Vider le signet toutes les 5 secondes (défaut : 60)
batch_size = 10 # Lire 10 événements par lot (réduire sur les DCs occupés)
renderXml = true # Capturer le XML complet pour l'extraction de champs
blacklist1 = EventCode="4634" # Exclure les événements de déconnexion si le volume est trop élevé
blacklist2 = EventCode="4656" # Exclure les demandes de handles (très bruyant)
[WinEventLog://Microsoft-Windows-Sysmon/Operational]
disabled = 0
start_from = oldest
checkpointInterval = 5
batch_size = 20
renderXml = true
5.2 Perte Induite par le Plafond de Licence (Le Problème Budgétaire Invisible)
De nombreuses plateformes SIEM appliquent des limites d'ingestion quotidiennes basées sur le volume de licence. Quand le plafond quotidien est atteint :
- Splunk : L'indexation s'arrête. Aucun nouvel événement accepté jusqu'à la prochaine fenêtre de licence. Un avertissement apparaît dans l'interface Splunk mais seulement si quelqu'un surveille.
- Microsoft Sentinel : L'ingestion continue mais la tarification par Go signifie des pics de coûts, déclenchant parfois des décisions organisationnelles de plafonner l'ingestion implémentées via des Règles de Collecte de Données qui filtrent silencieusement des événements.
- Elastic : Les limites de licence restreignent l'utilisation des fonctionnalités, mais l'ingestion est moins fréquemment plafonnée.
Vérifier l'utilisation de votre licence Splunk :
| rest /services/licenser/pools
| table title, used_bytes, effective_quota, slave_count
| eval used_GB = round(used_bytes/1073741824, 2)
| eval quota_GB = round(effective_quota/1073741824, 2)
| eval pct_used = round((used_bytes/effective_quota)*100, 1)
| where pct_used > 80
| sort -pct_used
Vérifier les écarts d'indexation dans Splunk (périodes de dépassement de licence) :
index=_internal source=*license_usage.log type=Usage
| timechart span=1h sum(b) as octets_indexés
| eval Go_indexés = round(octets_indexés/1073741824, 2)
| where Go_indexés = 0
Partie 6 Comment Mesurer Réellement Votre Fidélité de Collecte
Tout ce qui précède décrit où les choses vont mal. Cette section vous dit comment mesurer si elles vont mal dans votre environnement, en ce moment.
6.1 Le Test de Continuité EventRecordID
La mesure la plus directe : comparer la séquence EventRecordID vue dans votre SIEM avec ce que la machine source a généré. Tout écart = événements que vous n'avez pas.
# Sur la machine source : obtenir le EventRecordID actuel le plus élevé et le plus ancien conservé
$journalSécurité = Get-WinEvent -LogName Security -MaxEvents 1
$évenementLePlusAncien = Get-WinEvent -LogName Security -MaxEvents 1 -Oldest
$statsSource = [PSCustomObject]@{
DernierRecordId = $journalSécurité.RecordId
PlusAncienRecordId = $évenementLePlusAncien.RecordId
HorodatePlusAncien = $évenementLePlusAncien.TimeCreated
TotalConservé = $journalSécurité.RecordId - $évenementLePlusAncien.RecordId + 1
}
Write-Output "Source dernier RecordId : $($statsSource.DernierRecordId)"
Write-Output "Source plus ancien conservé : $($statsSource.PlusAncienRecordId) à $($statsSource.HorodatePlusAncien)"
Write-Output "Événements conservés localement : $($statsSource.TotalConservé)"
Vérifiez maintenant ce que votre SIEM a pour le même hôte :
index=wineventlog host="DC01" source="WinEventLog:Security"
| stats min(EventRecordID) as plus_ancien_siem,
max(EventRecordID) as plus_récent_siem,
count as total_siem
by host
| eval pct_couverture = round((total_siem / (plus_récent_siem - plus_ancien_siem + 1)) * 100, 2)
Si pct_couverture est substantiellement inférieur à 100%, des événements dans cette plage d'ID manquent dans votre SIEM. Le delta entre le TotalConservé de la source et le total_siem du SIEM sur la même période est votre nombre d'écarts.
6.2 La Méthode de Référence du Volume d'Événements
Une approche plus subtile mais plus évolutive : établir une référence du volume d'événements attendu par hôte par type d'événement, puis alerter sur les déviations.
index=wineventlog source="WinEventLog:Security" EventCode=4688
| timechart span=1h count by host
| foreach [
eval avg_$host$ = mvavg($host$, 168),
eval pct_de_moy_$host$ = round(($host$ / avg_$host$) * 100, 0)
]
Plus pratiquement, l'équivalent KQL (Microsoft Sentinel) :
// Détecter les hôtes signalant significativement moins d'événements que leur moyenne sur 7 jours
// Indicateur de défaillance d'agent, accélération des écrasements ou suppression active
let lookback = 7d;
let fenêtreÉvaluation = 1h;
SecurityEvent
| where TimeGenerated > ago(lookback)
| where EventID == 4688 // Création de processus volume élevé, bon indicateur de référence
| summarize
NombreÉvénements = count()
by Computer, bin(TimeGenerated, fenêtreÉvaluation)
| summarize
MoyenneHoraire = avg(NombreÉvénements),
ÉcartType = stdev(NombreÉvénements),
NombreDernièreHeure = take_anyif(NombreÉvénements, TimeGenerated > ago(fenêtreÉvaluation))
by Computer
| where isnotempty(NombreDernièreHeure)
| extend
SeuilChute = MoyenneHoraire * 0.5, // Alerter si en dessous de 50% de la moyenne
PctDeMoyenne = round((NombreDernièreHeure / MoyenneHoraire) * 100, 1)
| where NombreDernièreHeure < SeuilChute
| where MoyenneHoraire > 10 // Exclure les hôtes avec une référence basse (trop bruyant)
| project Computer, MoyenneHoraire, NombreDernièreHeure, PctDeMoyenne, SeuilChute
| sort by PctDeMoyenne asc
Cette requête s'exécute toutes les heures. Tout hôte signalant moins de 50% de ses événements normaux de création de processus déclenche une alerte. La cause racine peut être : la machine est éteinte (attendu), l'agent a planté (corrigez-le), le journal n'est pas collecté (problème de configuration), ou un attaquant a supprimé la journalisation (répondez immédiatement).
6.3 La Norme Or : Injection Synthétique d'Événements
Le test le plus fiable : injecter des événements connus dans une machine source et vérifier qu'ils apparaissent dans votre SIEM avec les champs corrects dans une fenêtre temporelle attendue. C'est fonctionnellement équivalent à un test canari pour votre pipeline de collecte.
# Sur une machine de test ou de production :
# Injecter un événement synthétique dans le journal Application avec un identifiant unique
# que vous pouvez rechercher dans votre SIEM
$marqueurUnique = "SIEM-FIDELITY-TEST-$(Get-Date -Format 'yyyyMMdd-HHmmss')-$(New-Guid)"
# Écrire un événement synthétique en utilisant la classe .NET EventLog
$sourceÉvénement = "SIEMFidelityTest"
if (-not [System.Diagnostics.EventLog]::SourceExists($sourceÉvénement)) {
[System.Diagnostics.EventLog]::CreateEventSource($sourceÉvénement, "Application")
}
$journal = New-Object System.Diagnostics.EventLog("Application")
$journal.Source = $sourceÉvénement
$journal.WriteEntry($marqueurUnique, [System.Diagnostics.EventLogEntryType]::Information, 9999)
Write-Output "Marqueur injecté : $marqueurUnique"
Write-Output "Recherchez maintenant cette chaîne dans votre SIEM dans les 5 prochaines minutes."
Write-Output "Si absent après 10 minutes, le pipeline de collecte a une lacune."
Vous pouvez envelopper ceci dans une tâche planifiée qui s'exécute toutes les 4 heures, écrit un marqueur unique, et qu'une requête SIEM séparée vérifie l'arrivée du marqueur dans une fenêtre de 15 minutes. Marqueurs manquants = défaillance du pipeline = ticket automatique.
Recherche SIEM pour valider l'arrivée du marqueur (Splunk) :
index=wineventlog OR index=windows EventCode=9999 source="WinEventLog:Application"
| where Message like "%SIEM-FIDELITY-TEST%"
| rex field=Message "SIEM-FIDELITY-TEST-(?<id_marqueur>[^\s]+)"
| eval secondes_latence = now() - strptime(substr(id_marqueur, 1, 15), "%Y%m%d-%H%M%S")
| table _time, host, id_marqueur, secondes_latence
| sort -_time
Si secondes_latence est constamment supérieur à 900 (15 minutes), votre pipeline de collecte est trop lent pour une détection significative des incidents à évolution rapide.
6.4 Vérifier la Santé des Abonnements WEF
# Sur le serveur WEC afficher la santé de tous les abonnements et leurs sources
wecutil es # Lister tous les abonnements
# Pour chaque abonnement, vérifier le statut d'exécution de toutes les sources enrollées
$abonnements = wecutil es
foreach ($abo in $abonnements) {
Write-Host "`n=== Abonnement : $abo ===" -ForegroundColor Cyan
# Obtenir la configuration complète de l'abonnement
wecutil gs "$abo" | Select-String -Pattern "Name|Status|Enabled|Uri"
# Obtenir le statut d'exécution par source
wecutil gr "$abo" | ForEach-Object {
if ($_ -match "Source|LastError|NextRetry|LastHeartbeat") {
if ($_ -match "LastError" -and $_ -notmatch "LastError: 0x0") {
Write-Host $_ -ForegroundColor Red # Erreur non nulle = problème
} else {
Write-Host $_
}
}
}
}
Rechercher les sources avec des valeurs LastError autres que 0x0. Codes d'erreur courants et leur signification :
| Code d'Erreur | Signification | Action |
|---|---|---|
| 0x0 | OK | Aucune action requise |
| 0x80070005 | Accès refusé | Vérifier la configuration WinRM, DACL sur l'abonnement |
| 0x80070776 | Abonnement non trouvé | Ré-appliquer GPO, redémarrer le service WEC |
| 0x803300004 | Connexion refusée | WinRM non en cours d'exécution sur la source, pare-feu bloquant 5985 |
| 0x803300005 | Impossible de se connecter | Échec de résolution DNS, problème réseau |
| 0x8033000f | Plus de points de terminaison | Machine source hors ligne ou inaccessible |
# Trouver toutes les sources WEF dont le dernier battement de cœur remonte à plus de 2 heures
# Ces machines ont des lacunes de couverture potentielles
$deuxHeuresAvant = (Get-Date).AddHours(-2)
wecutil gr "BaselineSubscription" |
Select-String "Source:|LastHeartbeat:" |
ForEach-Object {
$ligne = $_.Line.Trim()
if ($ligne -match "^Source:") {
$sourceActuelle = ($ligne -split "Source: ")[1]
}
if ($ligne -match "LastHeartbeat:") {
$batt = ($ligne -split "LastHeartbeat: ")[1]
if ($batt -ne "N/A") {
$heureBattement = [DateTime]::Parse($batt)
if ($heureBattement -lt $deuxHeuresAvant) {
Write-Warning "OBSOLÈTE : $sourceActuelle dernier battement : $heureBattement"
}
}
}
}
Partie 7 Les Attaquants Exploitant Ces Lacunes : T1562.002
Tout ce qui précède décrit des lacunes accidentelles. Les attaquants sophistiqués les exploitent délibérément. MITRE ATT&CK T1562.002 (Affaiblir les Défenses : Désactiver la Journalisation des Événements Windows) documente les techniques spécifiques.
7.1 Désactiver la Stratégie d'Audit en Plein Milieu d'une Attaque
:: Un attaquant avec des droits admin locaux peut désactiver des sous-catégories d'audit spécifiques
:: pour supprimer la journalisation de ses techniques spécifiques
:: Désactiver la journalisation de création de processus avant d'exécuter des outils
auditpol /set /subcategory:"Process Creation" /success:disable /failure:disable
:: Désactiver la journalisation des événements de connexion pendant le mouvement latéral
auditpol /set /subcategory:"Logon" /success:disable
:: Ceci génère Event ID 4719 (stratégie d'audit modifiée) SI vous le journalisez
:: La plupart des environnements n'alertent pas sur 4719. Vérifiez le vôtre :
auditpol /get /subcategory:"Audit Policy Change"
La défense : alerter sur Event ID 4719 (stratégie d'audit système modifiée). Cet événement est généré chaque fois qu'auditpol modifie la stratégie locale. C'est l'un des indicateurs les plus fiables d'évasion de défense active il a presque aucune utilisation légitime en dehors des changements administratifs planifiés.
// KQL Alerter sur les changements de stratégie d'audit depuis des processus non-tâches planifiées
SecurityEvent
| where EventID == 4719
| where TimeGenerated > ago(24h)
| extend
UtilisateurSujet = tostring(EventData.SubjectUserName),
ConnexionSujet = tostring(EventData.SubjectLogonId),
ChangementsStratégie = tostring(EventData.AuditPolicyChanges)
| where UtilisateurSujet !endswith "$" // Exclure les comptes machine (application GPO)
| project TimeGenerated, Computer, UtilisateurSujet, ChangementsStratégie
| sort by TimeGenerated desc
7.2 Effacement du Journal d'Événements
# L'attaquant efface le journal Security pour détruire des preuves
wevtutil cl Security # Génère Event 1102 (journal d'audit effacé)
# OU
Clear-EventLog -LogName Security # Même résultat
# Remove-EventLog est plus destructeur supprime entièrement le canal
Remove-EventLog -LogName Security
# Ne génère PAS 1102 le canal est supprimé avant que l'événement puisse être écrit
# Génère 104 dans le journal System (erreur du service de journalisation)
Détecter l'effacement du journal :
// Alerter sur Event 1102 (journal Security effacé) événement légitime rare
SecurityEvent
| where EventID == 1102
| project TimeGenerated, Computer,
Compte = tostring(EventData.SubjectUserName),
IdConnexion = tostring(EventData.SubjectLogonId)
| sort by TimeGenerated desc
// Alerter également sur Event 104 (journal System) indiquant la suppression du canal
Event
| where EventLog == "System" and EventID == 104
| project TimeGenerated, Computer, RenderedDescription
7.3 Manipulation du Fournisseur ETW (Avancé)
Un attaquant sophistiqué peut manipuler ETW au niveau noyau, désactivant des fournisseurs spécifiques sans déclencher d'événements d'effacement de journaux :
Technique : Patcher l'enregistrement du fournisseur ETW dans la mémoire du processus cible
pour retourner tôt depuis la fonction d'écriture ETW, supprimant silencieusement tous
les événements de ce fournisseur sans aucun Event ID 1102, 4719 ou 104 apparaissant.
Détection :
- Comparer les volumes d'événements attendus vs. réels (Section 6.2)
- Surveiller Sysmon Event ID 1 (création de processus) avec des signatures d'outils
de patching ETW connus dans le champ CommandLine
- Vérifier les compteurs de perte de tampon de session ETW (Section 1.2)
- L'injection synthétique d'événements détectera cela (Section 6.3)
Il n'y a pas d'événement unique qui se déclenche quand ETW est patché en mémoire. La détection basée sur le volume et l'injection synthétique sont les seules détections fiables.
Partie 8 La Feuille de Route de Durcissement : Corrigez-le Cette Semaine
Priorité 1 (Faites-le Aujourd'hui)
# 1. Vérifier que le drapeau de substitution de la stratégie d'audit est défini sur tous les DCs
# Attendu : "Audit: Force audit policy..." = Activé
Invoke-Command -ComputerName "DC01","DC02","SERVER01" -ScriptBlock {
$paramètre = secedit /export /cfg "$env:TEMP\secpol.cfg" /quiet
Select-String "MACHINE\\System\\CurrentControlSet\\Control\\Lsa\\SCENoApplyLegacyAuditPolicy" `
"$env:TEMP\secpol.cfg"
}
# 2. Vérifier que la création de processus (4688) génère des événements sur au moins un DC
$récents4688 = Get-WinEvent -ComputerName "DC01" -LogName Security `
-FilterXPath "*[System[EventID=4688 and TimeCreated[timediff(@SystemTime) <= 3600000]]]" `
-MaxEvents 5 -ErrorAction SilentlyContinue
if (-not $récents4688) {
Write-Warning "Aucun événement 4688 dans la dernière heure sur DC01 stratégie d'audit mal configurée"
}
# 3. Vérifier que la journalisation de la ligne de commande est activée
$paramètreLigneCommande = Invoke-Command -ComputerName "DC01" -ScriptBlock {
$chemin = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System\Audit"
(Get-ItemProperty -Path $chemin -Name "ProcessCreationIncludeCmdLine_Enabled" -EA SilentlyContinue).ProcessCreationIncludeCmdLine_Enabled
}
if ($paramètreLigneCommande -ne 1) {
Write-Warning "Journalisation ligne de commande NON activée sur DC01 tous les événements 4688 ont CommandLine vide"
}
Priorité 2 (Cette Semaine)
# Redimensionner le journal Security sur tous les DCs à 4 Go
$dcs = (Get-ADDomainController -Filter *).Name
foreach ($dc in $dcs) {
Invoke-Command -ComputerName $dc -ScriptBlock {
wevtutil sl Security /ms:4294967296 # 4 Go
wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:2147483648 # 2 Go
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824 # 1 Go
Write-Output "$env:COMPUTERNAME tailles de journaux mises à jour"
}
}
Priorité 3 (Ce Mois)
Déployez le test d'injection synthétique d'événements comme une tâche planifiée sur 10 hôtes représentatifs (DCs, serveurs critiques, échantillon de postes de travail). Exécutez toutes les 4 heures. Alertez dans le SIEM si un marqueur est absent après 15 minutes. Cela vous donne une validation continue et automatisée de la fidélité de collecte la métrique qui transforme ceci d'un audit ponctuel en contrôle opérationnel continu.
L'Inventaire Complet des Lacunes : Quoi Vérifier et Comment
| Lacune | Méthode de Détection | Outil | Temps pour Vérifier |
|---|---|---|---|
| Stratégie d'audit ne générant pas d'événements | auditpol /get /category:* | auditpol.exe | 5 min par hôte |
| Conflit stratégie héritée/avancée | Vérifier SCENoApplyLegacyAuditPolicy=0 | secedit / registre | 10 min |
| Journalisation ligne de commande désactivée | Vérification du registre | PowerShell | 2 min par hôte |
| Tailles de journaux trop petites | wevtutil gl Security | wevtutil.exe | 2 min par hôte |
| Erreurs de filtre d'abonnement WEF | Tester XPath avec Get-WinEvent -FilterXPath | PowerShell | 15 min |
| Serveur WEC perdant des événements | Compteur ETW Buffers Lost | Get-Counter | 10 min |
| Mode de livraison WEF trop lent | wecutil gs <abonnement> DeliveryMaxLatency | wecutil.exe | 5 min |
| Sources WEF obsolètes | wecutil gr <abonnement> LastHeartbeat | wecutil.exe | 15 min |
| Écarts EventRecordID dans SIEM | Comparer RecordId source vs. requête SIEM | PowerShell + SIEM | 30 min |
| Déviation référence volume | Requête SIEM comparant dernière heure à moyenne 7 jours | SIEM | Continu |
| Journal d'audit effacé (1102) | Règle d'alerte dans SIEM | SIEM | Déployer maintenant |
| Stratégie d'audit manipulée (4719) | Règle d'alerte dans SIEM | SIEM | Déployer maintenant |
| Manipulation ETW | Test d'injection synthétique | PowerShell planifié | Déployer hebdomadaire |
Références
- Microsoft Learn : "Use Windows Event Forwarding to help with intrusion detection"
- Palantir : dépôt GitHub windows-event-forwarding architecture WEF en production
- Elastic : "The Essentials of Central Log Collection with WEF/WEC"
- MITRE ATT&CK T1562.002 : Affaiblir les Défenses Désactiver la Journalisation des Événements Windows
- MITRE ATT&CK T1070.001 : Suppression d'Indicateurs Effacement des Journaux d'Événements Windows
- Microsoft Learn : Documentation Event ID 1102 et 4719
- NSA/CISA : "Windows Event Logging and Forwarding" (NSA-CSI-18-130)
- Malware Archaeology : Windows Logging Cheat Sheet v2019
- Roberto Rodriguez (Cyb3rWard0g) : ThreatHunter-Playbook Recherche ETW
Toutes les commandes de cet article sont des utilitaires d'administration Windows standard et des commandes PowerShell intégrées. Elles opèrent sur des journaux auxquels vous avez un accès administratif. Il s'agit d'un guide d'opérations défensives.