验证环境:Windows 测试节点
DESKTOP-HT6EO5E,Windows 10 Pro for Workstations 19045,Windows PowerShell 5.1.19041.6456。采集命令以-ExecutionPolicy Bypass启动单次只读 PowerShell 进程,因此输出中的Process=Bypass只代表本次进程级覆盖,不代表机器或域策略。本文只讨论防御、审计和检测工程,不提供攻击执行步骤。
核心观点
PowerShell 事件 4104 的价值不是“看到一条脚本日志就等于完成检测”,而是把脚本内容、执行上下文和主机策略状态放进同一条证据链。它能证明某段脚本块被 PowerShell 引擎记录过;它不能单独证明脚本一定完成执行、一定被 Defender 扫描、一定覆盖所有 PowerShell 版本和所有执行入口。
对安全运营来说,正确姿势是把 4104 当作证据链中的“脚本文本层”,再用执行策略、AMSI/Defender 状态、进程创建、模块日志、转录日志和终端侧策略去补足时间线。否则 SIEM 里会出现两个常见误判:一是把 4104 当成万能审计;二是因为日志噪声太大而完全不用它。
背景与问题定义
PowerShell 的 Windows 日志体系包含 engine/provider/cmdlet 等事件。Script Block Logging 会在 Microsoft-Windows-PowerShell/Operational 中记录脚本块创建事件,其中 4104 是检测工程最常用的事件之一。Microsoft 同时在 PowerShell 安全文档中说明,Windows PowerShell 5.1 起会把脚本块交给 AMSI,PowerShell 7.3 以后扩展了送检内容范围。这些能力给了蓝队很好的可见性,但它们不是同一个控制点。
问题在于,企业落地时经常把三个层次混在一起:
| 层次 | 常见对象 | 能回答的问题 | 不能回答的问题 |
|---|---|---|---|
| 脚本文本可见性 | 4104 Script Block Logging | 记录到了什么脚本块文本 | 脚本是否完整执行、是否绕过其他入口 |
| 安全扫描与阻断 | AMSI、Defender、EDR | 是否把动态脚本内容交给反恶意软件能力 | 日志策略是否开启、事件是否集中采集 |
| 企业治理 | GPO/MDM、SIEM、SOAR、WDAC/App Control | 是否能形成强制基线和审计闭环 | 单台机器临时状态是否代表全域状态 |
实测基线:一台 Windows 节点暴露出来的边界
下面是本次在授权 Windows 测试环境上采集的只读输出。命令读取系统、PowerShell、日志通道和最近 4104 事件,没有修改策略。
=== System ===
WindowsProductName : Windows 10 Pro for Workstations
WindowsVersion : 2009
OsBuildNumber : 19045
OsArchitecture : 64-bit
=== PowerShell ===
PSVersion : 5.1.19041.6456
PSEdition : Desktop
=== ExecutionPolicy ===
Scope ExecutionPolicy
----- ---------------
MachinePolicy Undefined
UserPolicy Undefined
Process Bypass
CurrentUser RemoteSigned
LocalMachine Undefined
=== ScriptBlockLoggingPolicy ===
(no explicit HKLM policy value returned)
=== ModuleLoggingPolicy ===
(no explicit HKLM policy value returned)
=== TranscriptionPolicy ===
(no explicit HKLM policy value returned)
=== PowerShell Operational Log ===
LogName : Microsoft-Windows-PowerShell/Operational
IsEnabled : True
RecordCount : 976
MaximumSizeInBytes : 15728640
=== Recent 4104 ===
TimeCreated : 6/9/2026 2:01:48 AM
Id : 4104
ProviderName : Microsoft-Windows-PowerShell
Message : Creating Scriptblock text (1 of 1):
$ErrorActionPreference='SilentlyContinue'
Write-Host '=== System ==='
Get-ComputerInfo | Select-Object WindowsProductName,...
ScriptBlock ID: 5e52b512-4f9e-42be-ba19-b0b60d66858b这组结果能支撑三个具体判断。
Microsoft-Windows-PowerShell/Operational日志通道已启用,并且有4104记录;这说明至少当前 PowerShell 入口产生了脚本块文本可见性。- HKLM 下未读到 ScriptBlockLogging、ModuleLogging、Transcription 的显式企业策略值;
Process=Bypass来自本次只读采集进程的启动参数,不是机器持久策略,所以不能把这台机器的行为外推成“域内强制基线已开启”。 - 事件内容记录了本次采集命令本身,证明
4104可以把多行脚本块、管道和部分参数纳入证据;但这并不证明所有脚本入口、所有 PowerShell 版本、所有主机进程都被同等覆盖。
环境边界也要写清楚:这是一台授权测试节点,不代表企业域控 GPO、Intune/MDM、EDR 代理或生产 SIEM 的真实状态。本文使用它验证事件字段与证据链写法,不能把“本机看到了 4104”扩展成“生产环境已经合规”。
证据链架构
这张图的关键不是“多收日志”,而是避免把 4104 放在孤岛里。4104 适合回答脚本文本层的问题;进程创建适合回答谁启动了 PowerShell;AMSI/Defender 适合回答扫描与处置;策略注册表和 GPO 适合回答基线是否被强制。
工程化设计:从事件到可运营检测
1. 先定义采集基线,而不是先写规则
最低限度的主机侧基线应包含:
| 控制项 | 验证命令/对象 | 合格信号 | 失败模式 |
|---|---|---|---|
| Operational 日志通道 | Get-WinEvent -ListLog Microsoft-Windows-PowerShell/Operational | IsEnabled=True,容量足够 | 通道关闭、容量太小导致覆盖 |
| Script Block Logging 策略 | HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging | GPO/MDM 明确配置 | 只依赖默认行为,无法证明强制基线 |
| Module Logging | ...\ModuleLogging\ModuleNames | 关键模块被纳入 | 只有脚本文本,没有模块调用上下文 |
| Transcription | ...\Transcription | 输出目录、权限和保留策略明确 | 转录文件泄露敏感输出或无法集中采集 |
| AMSI/Defender/EDR | Defender/EDR 健康状态 | 实时保护与脚本扫描链路可观测 | 日志有了,但扫描/处置缺位 |
| SIEM 归一化 | 字段映射 | host,user,process,script_block_id,message_hash 可查询 | 只存原文,无法关联或去重 |
2. 再写检测逻辑,并保留“证据等级”
示例规则不应该直接等价于“出现某字符串就是入侵”。更稳妥的写法是把证据分级:
| 证据等级 | 条件 | 处置建议 |
|---|---|---|
| 低 | 单条 4104 命中可疑关键字,但无进程/用户异常 | 进入低优先队列,做哈希去重和上下文补全 |
| 中 | 4104 命中 + 非交互父进程/计划任务/异常路径 | 创建告警,拉取父子进程、登录会话和文件落点 |
| 高 | 4104 命中 + AMSI/EDR 告警 + 横向移动或凭据访问信号 | 进入事件响应流程,隔离主机前保全日志 |
| 审计缺口 | 高风险资产没有 4104 或日志容量不足 | 不是安全告警,而是基线缺陷工单 |
这能避免规则把安全运营拖进两个极端:要么噪声淹没,要么因为误报而关闭采集。
最小可复现实验
下面实验只验证可见性,不包含攻击步骤。它适合在测试机或实验 OU 中执行,生产环境应通过变更流程启用策略。
# 1. 查看当前日志通道
Get-WinEvent -ListLog 'Microsoft-Windows-PowerShell/Operational' |
Select-Object LogName, IsEnabled, RecordCount, MaximumSizeInBytes
# 2. 执行一段无害脚本块,制造可追溯事件
$marker = 'ps4104-lab-' + [guid]::NewGuid().ToString()
Write-Output $marker | Out-Null
# 3. 回查最近 4104,并确认 marker 是否出现
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104
} -MaxEvents 20 |
Where-Object { $_.Message -like "*$marker*" } |
Select-Object TimeCreated, Id, ProviderName, Message预期结果:如果日志通道和脚本块记录生效,最近的 4104 中应能看到 marker 或包含 marker 的脚本文本。若没有看到,排查顺序不是“规则写错了”,而是:日志通道是否开启、策略是否强制、是否使用了不同 PowerShell 版本或宿主、事件是否被覆盖、采集代理是否过滤。
失败模式与排错路径
| 现象 | 首查对象 | 常见原因 | 修复方向 |
|---|---|---|---|
| 本机能看到 4104,SIEM 看不到 | 采集代理、WEF 订阅、索引映射 | 只采 Security/System,未采 Operational | 增加 PowerShell Operational 订阅和字段解析 |
| 4104 太多,规则不可用 | 去重字段、脚本哈希、资产分组 | 构建/运维脚本噪声未建白名单 | 按资产角色和脚本签名做基线 |
| 只有脚本文本,没有父进程 | 4688/EDR telemetry | Windows 审计策略或 EDR 字段缺失 | 补齐进程创建、命令行和用户登录事件 |
| 合规检查无法证明策略开启 | GPO/MDM 状态、注册表策略值 | 依赖默认行为,没有集中策略 | 建立配置基线与漂移检测 |
| 敏感信息进入日志 | Transcription/4104 脱敏策略 | 脚本输出或参数包含密钥 | 日志访问分级、密钥扫描、最小化记录范围 |
风险与安全边界
4104 的边界要明确写进检测规范:
- 它不是强制执行控制,不能替代 WDAC/App Control、最小权限、脚本签名和 JEA。
- 它不是完整取证系统,日志容量、覆盖周期和采集延迟会影响证据完整性。
- 它不应被当作单点阻断信号,必须和 AMSI/Defender/EDR、进程事件和身份上下文关联。
- 它可能记录敏感脚本文本,日志平台本身要纳入数据安全和访问控制。
- 它在不同 PowerShell 版本、宿主和企业策略下表现可能不同,生产规则必须先做资产分层验证。
结论
真正有用的 PowerShell 检测,不是把 4104 塞进 SIEM 就结束,而是让它成为一条可解释、可复核、可回归测试的证据链。资产范围要明确,策略来源要可追溯,事件关联要能串联用户、主机、父进程和 AMSI 告警,噪声治理要有基线。对蓝队来说,这比追逐单条规则的命中率更重要。



