WiLL75G/sigma-rule-validation-create-local-user
GitHub: WiLL75G/sigma-rule-validation-create-local-user
通过严格的控制组实验和 Event ID 4104 遥测证据,验证并证明了 SigmaHQ 检测 PowerShell 本地用户创建的规则存在可被 ADSI/WinNT provider 绕过的覆盖缺陷。
Stars: 1 | Forks: 0
# Sigma 规则验证:PowerShell 创建本地用户
一个匹配单个字符串 `New-LocalUser` 的 SigmaHQ 规则,漏掉了一种能产生相同结果的账户创建方法。本次验证在已确认日志处于活动状态的配置下,基于捕获的 Event ID 4104 证据证明了该缺陷的存在,不仅使用了有效的控制组,还刻意记录了在测试过程中发现并纠正的一个方法论错误。
## 概览
| 字段 | 详情 |
| --- | --- |
| 工作类型 | Detection-engineering 验证,上游贡献 |
| 受测规则 | `posh_ps_create_local_user.yml` (SigmaHQ) |
| 发现 | 覆盖缺陷,通过 ADSI/WinNT 创建账户的行为规避了该规则 |
| ATT&CK | T1136.001, Create Account: Local Account |
| 遥测数据 | Windows Event ID 4104, Script Block Logging |
| 控制组 | `New-LocalUser` 确认被检测到,验证了该方法 |
| 环境 | Windows 11, PowerShell 5.1, Administrator |
| 上游 | 作为 SigmaHQ issue #6057 提交 |
## 这是什么
对 SigmaHQ 规则 `posh_ps_create_local_user.yml` 的 Detection-engineering 验证,展示了通过 ADSI/WinNT provider 创建本地账户从而规避检测的覆盖缺陷。以下所有声明均由在已验证处于活动状态的日志配置下捕获的 Event ID 4104 证据支持。
这是上游贡献作品集中的一项条目:在向 SigmaHQ 提交任何内容之前,规则都会在家庭实验室中针对真实遥测数据进行验证。此处使用的相同 JAMES-VM Windows endpoint 和 Event ID 4104 script block logging 是 `detection-engineering-labs` PowerShell 调查和 `sentinel-soc-lab-setup` Day 6 狩猎的遥测基础。这些实验室与这项工作并不分离,它们正是使此类贡献具有可信度的关键。
## 目标
Sigma 规则 [`posh_ps_create_local_user.yml`](https://github.com/SigmaHQ/sigma/blob/master/rules/windows/powershell/powershell_script/posh_ps_create_local_user.yml) 通过匹配单个字符串来检测在 PowerShell 中创建本地用户的行为:
```
detection:
selection:
ScriptBlockText|contains: 'New-LocalUser'
condition: selection
```
本次验证旨在回答一个问题:该规则是能检测到所有在 PowerShell 中创建本地用户的常见方法,还是仅能检测到 `New-LocalUser` cmdlet?
假设:不调用 `New-LocalUser` cmdlet 的账户创建方法将不包含被匹配的字符串,因此将规避该规则。
## 环境
| 组件 | 详情 |
| --- | --- |
| 操作系统 | Windows 11 |
| PowerShell | 5.1 (Windows PowerShell) |
| 日志 | Script Block Logging (Event ID 4104),通过策略启用 |
| 权限 | Administrator 会话 |
Script Block Logging 是规则自身 `definition` 字段中明确说明的要求,因此在任何测试之前都已启用并经过验证。在没有确认其所需日志源处于活动状态的情况下测试检测,是产生错误“规避”声明的原因;本次验证预先消除了这种疑虑。
## 方法论
每次测试都遵循相同的严谨步骤:
1. 捕获基线日志状态。
2. 启用 Script Block Logging 并验证其是否处于活动状态。
3. 运行已知会被检测到的命令作为控制组 (`New-LocalUser`),并确认其包含匹配字符串并被记录。
4. 运行每种候选的规避方法。
5. 确认每种方法都创建了一个真实的、已启用的用户账户。
6. 直接读取捕获的 Event 4104 `ScriptBlockText`,验证是否存在 `New-LocalUser` 字符串。
7. 移除所有测试账户(清理)。
## 步骤 1 — 基线日志状态(已禁用)
Script Block Logging 的注册表项不存在,确认在开始时日志是关闭的。这建立了一个干净的“之前”状态。

## 步骤 2 — 启用 Script Block Logging
创建了策略注册表项并将 `EnableScriptBlockLogging` 设置为 `1`,随后进行了验证。


## 步骤 3 — 基线(控制组):New-LocalUser cmdlet
```
New-LocalUser -Name "test_baseline" -NoPassword
```
账户已创建并启用。相应的 Event ID 4104 捕获到了该命令,且其 `ScriptBlockText` 包含字符串 `New-LocalUser`。规则按预期触发,验证了测试方法有效。
**结果:已检测到(控制组表现正常)。**
控制组正是让后续规避声明可信的关键。如果没有证据表明规则在应该触发时确实触发了,那么“未检测到”的结果可能仅仅意味着日志功能坏了。这确认了它是有效的,因此漏报才是真正的漏报。


## 步骤 4 — 规避 1:ADSI / WinNT Provider
```
$user = [ADSI]"WinNT://$env:COMPUTERNAME"; $newuser = $user.Create("User","test_adsi"); $newuser.SetInfo()
```
账户 `test_adsi` 已创建,并且 `Get-LocalUser` 确认它和基线账户都存在且已启用。


### 方法论说明(重要)
验证规避的第一次尝试,计算了通过对 `New-LocalUser` 进行子字符串搜索返回的 Event 4104 条目。这种方法是有缺陷的:每个搜索命令本身都包含字符串 `New-LocalUser`(在 `Where-Object` 过滤器内),并且 Script Block Logging 也会记录该搜索命令。结果导致了自我污染——搜索在一定程度上检测到了它自己,而不是用户创建命令。下面的截图展示了受污染的结果,重复的搜索将其自身的条目计入了总数。


该方法被纠正为直接读取特定用户创建事件的完整 `ScriptBlockText` 内容,而不是计算事件数量。这种方法不受污染问题的影响,因为它检查的是字面意义上的已记录命令。作为中间检查,对 `WinNT` provider 字符串的搜索证实了 ADSI 命令确实被记录了下来:

这个缺陷在此被刻意记录下来而没有被隐藏,因为发现并纠正它本身就是验证的一部分。一个把搜索匹配到自身的结果计算在内的做法不能作为任何事物的证据;认识到原因,并转向阅读字面意义上的已记录命令,才是声明与证明之间的区别。
### 纠正后的验证
读取 ADSI 事件的字面捕获 `ScriptBlockText`:
```
$user = [ADSI]"WinNT://$env:COMPUTERNAME"; $newuser = $user.Create("User","test_adsi"); $newuser.SetInfo()
```
命令被完全记录下来(Event ID 4104, 5:36:54 PM),但不包含任何 `New-LocalUser` 字符串的实例。规则未匹配。
**结果:未检测到,确认发生规避。**

## 步骤 5 — 规避 2:net user
```
net user test_netuser /add
```
账户 `test_netuser` 已成功创建。读取捕获的 `ScriptBlockText`:
```
net user test_netuser /add
```
命令被记录下来(Event ID 4104, 5:46:51 PM),同样不包含任何 `New-LocalUser` 的实例。规则未匹配。
**结果:未检测到,确认发生规避(见局限性)。**


## 步骤 6 — 清理
所有三个测试账户均已被移除,并验证了移除操作(结果为空)。

## 发现
规则 `posh_ps_create_local_user.yml` 仅检测 `New-LocalUser` cmdlet。通过 ADSI/WinNT provider 创建本地账户产生了功能上相同的结果(一个真实的、已启用的本地用户),但被完全记录时却缺少匹配的字符串,因此规避了检测。
这一点是通过在已验证处于活动状态的日志配置下,读取每个命令的字面捕获 `ScriptBlockText`,并配合有效的控制组来验证测试方法而得到确认的。缺陷不在于该活动未被记录(它已被完整记录),而在于该规则的单字符串匹配并没有去寻找它。
## 局限性
- `net user` 方法虽然规避了这个 script-block 规则,但在许多环境中,可能会通过对 `net.exe` 的进程创建日志(Event ID 4688)检测到。因此,它是一个较弱的示例。ADSI/WinNT 方法是更纯粹的缺陷,因为它完全在 PowerShell 进程内执行,没有单独的二进制文件可以被捕获。
- 测试是在单一的 Windows 11 / PowerShell 5.1 配置上进行的。未针对 PowerShell 7.x 或更旧的 Windows 版本验证行为。
- 本次验证不评估规则集中其他地方的同级规则是否已经覆盖了 ADSI 模式。
坦率地陈述这些是贡献的一部分。夸大其词的缺陷报告会浪费维护者的时间;而明确指出自身边界的报告则能让他们确定修复的范围。
## 建议
考虑扩大检测范围以覆盖 ADSI/WinNT 账户创建模式,例如添加一个在 `ScriptBlockText` 中同时匹配 `[ADSI]` 和 `Create("User"`(或 `Create('User'`)的选择项。如果维护者认为这在其范围内,后续的 pull request 可以添加此覆盖。
## 仓库结构
```
sigma-rule-validation-create-local-user/
├── README.md
└── screenshots/
├── 01_scriptblock_logging_disabled_baseline.png
├── 02_scriptblock_logging_key_created.png
├── 03_scriptblock_logging_enabled_confirmed.png
├── 04_baseline_newlocaluser_created.png
├── 05_baseline_4104_newlocaluser_logged.png
├── 06_evasion_adsi_user_created.png
├── 07_both_test_users_exist.png
├── 08_contaminated_search_newlocaluser.png
├── 09_adsi_winnt_event_logged.png
├── 10_contamination_demonstrated.png
├── 11_adsi_message_content_no_newlocaluser.png
├── 12_evasion_netuser_created.png
├── 13_netuser_message_content_no_newlocaluser.png
└── 14_test_users_removed_cleanup.png
```
## 参考
- 受测规则:https://github.com/SigmaHQ/sigma/blob/master/rules/windows/powershell/powershell_script/posh_ps_create_local_user.yml
- Sigma 规范(修饰符;默认字符串匹配不区分大小写):https://github.com/SigmaHQ/sigma-specification/blob/main/specification/sigma-appendix-modifiers.md
- Microsoft ADSI WinNT provider 文档:https://learn.microsoft.com/en-us/windows/win32/adsi/the-winnt-provider
*由 [WiLL75G](https://github.com/WiLL75G) 验证并记录。*
标签:AI合规, PowerShell监控, Sigma规则, Windows事件日志, 目标导入