为什么要审计 PowerShell
PowerShell 是 Windows 上少见的「系统自带且能力完备」的执行环境:既是运维自动化工具,也是无文件攻击与横向移动的常用载体。默认情况下它只留下很少痕迹,因此需要显式开启脚本块日志(记录实际执行的脚本内容)、模块日志与转录(记录会话输入输出)。开启后可以通过事件回溯「谁在什么时间执行了什么代码」,这既是安全审计要求,也是事件溯源的关键数据。
基本信息
| 日志位置 | 事件查看器 → 应用程序和服务日志 → Microsoft → Windows → PowerShell → Operational |
|---|---|
| 传统日志 | Windows PowerShell 经典日志(含引擎启动/停止事件) |
| 开启方式 | 组策略:计算机配置 → 管理模板 → Windows 组件 → Windows PowerShell;或写入对应注册表项 |
| 关键事件 | 400 引擎启动、403 引擎停止、4103 模块日志、4104 脚本块日志 |
| 转录位置 | 由策略指定输出目录,默认在用户文档目录,建议统一到受控目录 |
| 性能代价 | 脚本块日志会记录脚本原文,高频脚本场景下日志量明显上升 |
开启与查询
开启与查询
# 开启脚本块日志(等效于组策略设置)
New-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -Force | Out-Null
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' `
-Name EnableScriptBlockLogging -Value 1 -Type DWord
# 查询脚本块日志(事件 ID 4104)
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -MaxEvents 200 |
Where-Object Id -eq 4104 |
Select-Object -First 10 TimeCreated, Id, @{n='Message';e={$_.Message.Substring(0,[Math]::Min(120,$_.Message.Length))}}
# 按时间段定位一次可疑执行
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104;
StartTime=(Get-Date).AddDays(-1)} | Where-Object Message -match 'Invoke-Expression|DownloadString'常用事件 ID
| ID | 含义 | 用途 |
|---|---|---|
| 400 | 引擎状态变为可用(会话启动) | 把一次会话的时间范围框出来 |
| 403 | 引擎状态变为停止 | 与 400 配对计算会话时长 |
| 4103 | 模块日志,记录管道执行 | 覆盖面广但噪声大,适合定向开启 |
| 4104 | 脚本块日志,记录脚本或代码片段内容 | 无文件攻击与混淆代码的主要线索 |
| 4105/4106 | 脚本块开始/结束(需单独开启) | 需要精确执行时长时使用 |
| 600 | 提供程序生命周期 | 判断日志记录能力是否被停用 |
落地建议
- 分级开启:服务器与域管终端开脚本块日志与转录,普通办公终端按风险决定是否开模块日志
- 集中采集:Operational 日志容量有限,本地不采集会被后续事件覆盖,必须接入 SIEM 或日志平台
- 权限收敛:脚本块日志可能记录含口令、连接串的明文脚本,日志读取权限要按最小必要授予
- 配合取证:4104 记录代码内容但不记录执行输出,重要场景同时开转录以还原命令结果
- 策略统一:域环境用组策略下发,避免逐台手工改注册表造成覆盖不一致
- 版本差异:较新版本 PowerShell 的日志字段更完整,老系统上部分事件可能缺失,试点后再全网推开
脚本内容里常含凭据与内部地址,日志一旦外泄等同于泄露运维口令;访问控制、脱敏与留存期应纳入日志管理制度,不能因为「是安全日志」就放开读取。