mistermobilka-spec/adrot
GitHub: mistermobilka-spec/adrot
纯 PowerShell 编写的 Active Directory 只读安全扫描器,无需 RSAT 和管理员权限,对域环境进行 17 项安全卫生检查并输出评分和修复建议。
Stars: 0 | Forks: 0
# ADRot
**找出你 Active Directory 中的腐朽之处。一个脚本,无需安装,只读。**
[](https://github.com/mistermobilka-spec/adrot/actions/workflows/ci.yml)
[](LICENSE)
[](https://github.com/PowerShell/PowerShell)
[](#why-no-rsat)
ADRot 会扫描 Active Directory,查找每个域中不断累积的安全腐朽问题——
非约束委派、可被 Kerberoasting 的服务账户、`PASSWD_NOTREQD`、
永不过期的密码、管理员组的无序扩张、以及自 2019 年以来就无人轮换过的 `krbtgt` 密码——
并为你提供一个 0–100 的评分,以及优先级明确、切实可行的修复建议。
它**绝不会向目录写入数据**。每一次 LDAP 操作都是搜索。ADRot 中没有任何代码路径
会执行添加、修改或删除操作。
## 问题所在
每个域都会腐化。没有人会注意到,因为这些腐朽在 ADUC 控制台中是不可见的:
一个在 2014 年为某台早已不存在的打印机勾选的复选框、一个被某人赋予了 SPN 且密码仅有十二位字符的服务账户、
以及 Domain Admins 里的四十个人,因为移除其中任何一个人都感觉有风险。
大家最常借用的工具是 [PingCastle](https://github.com/netwrix/pingcastle)——
非常出色,但现在归 Netwrix 所有。它是一个基于 Non-Profit OSL 协议的 .NET 二进制文件,其调度、多域和趋势分析功能都被置于付费层级之后,
而且**审计他人的组织需要商业许可证**——这对每个 MSP 和顾问来说都是一项长期的固定税。
ADRot 采用 MIT 协议,纯 PowerShell 编写,且只做一件事:告诉你哪里出了问题、为什么重要,以及如何修复。
## 运行效果
```
$ Invoke-ADRotScan
ADRot — Active Directory hygiene report
rot.example.com · captured 2026-07-01T00:00:00Z
Score 0/100 grade F
17 findings — 2 critical, 7 high, 7 medium, 1 low
Scanned 10 users, 4 computers, 2 groups against 17 rules
────────────────────────────────────────────────────────────────────────────
CRITICAL AD-001 Accounts store passwords with reversible encryption
1 affected
· legacy.radius — Reversible encryption enabled
CRITICAL AD-012 Unconstrained Kerberos delegation is enabled
1 affected
· PRINT01$ — TRUSTED_FOR_DELEGATION set (unconstrained)
HIGH AD-004 User accounts carry a servicePrincipalName (Kerberoastable)
1 affected
· svc-sql — SPN: MSSQLSvc/sql01.rot.example.com:1433
HIGH AD-009 Tier-0 administrative groups are oversized
1 affected
· Domain Admins — 6 members (threshold 5)
HIGH AD-015 The krbtgt password has not been rotated recently
1 affected
· krbtgt — Password set 900 days ago (threshold 180)
MEDIUM AD-016 Any authenticated user can join computers to the domain
1 affected
· ms-DS-MachineAccountQuota — Set to 10; every authenticated user
may create that many computer accounts
────────────────────────────────────────────────────────────────────────────
Run with -HtmlPath to produce the full report with remediation guidance.
```
`-HtmlPath` 会生成一个自包含的 HTML 报告——单文件,无 CDN、无 Web 字体,
没有任何远程依赖——其中包含原理说明、修复步骤以及所有受影响的对象。
你可以用内置的测试数据亲自试运行:
```
Invoke-ADRotScan -SnapshotPath ./tests/fixtures/dirty-domain.json -HtmlPath ./report.html
```
## 快速开始
**扫描你已加入的域:**
```
git clone https://github.com/mistermobilka-spec/adrot.git
cd adrot
Import-Module ./src/ADRot/ADRot.psd1
Invoke-ADRotScan
```
就是这样。无需安装、无需 RSAT、无 agent、无需管理员权限——普通的域用户就能读取 ADRot 读取的所有内容。
**生成可共享的报告:**
```
Invoke-ADRotScan -HtmlPath ./adrot-report.html
```
**审计一个你能访问但无法安装任何东西的域**——在现场捕获,稍后在任何地方进行分析:
```
Export-ADRotSnapshot -Path ./client.json -Server dc01.client.local
Invoke-ADRotScan -SnapshotPath ./client.json -HtmlPath ./client-report.html
```
**据此拦截 pipeline。** `-FailOn` 会引发终止性错误,因此将其包裹起来即可获取特定的退出代码:
```
try { Invoke-ADRotScan -FailOn High -Quiet; exit 0 }
catch { Write-Host $_.Exception.Message; exit 2 }
```
**或者在 Docker 中运行**(离线快照分析)。目前尚未发布镜像——请在本地构建:
```
docker build -f docker/Dockerfile -t adrot .
docker run --rm -v "$PWD:/data" adrot -SnapshotPath /data/snapshot.json -HtmlPath /data/report.html
```
容器会将阈值违规映射为退出代码 2。
**在将其指向生产环境之前,先看看它检查了什么:**
```
Get-ADRotRule | Format-Table Id, Severity, Category, Title
```
## 规则说明
17 项检查,每项都附带原理说明、修复指导和权威参考。
| ID | 严重性 | 检查项 |
|---|---|---|
| AD-001 | 🔴 严重 | 账户存储了使用可逆加密的密码 |
| AD-012 | 🔴 严重 | 已启用非约束 Kerberos 委派 |
| AD-002 | 🟠 高 | 账户免除了密码要求 (`PASSWD_NOTREQD`) |
| AD-003 | 🟠 高 | 可被 AS-REP Roasting 攻击的账户 (`DONT_REQ_PREAUTH`) |
| AD-004 | 🟠 高 | 可被 Kerberoasting 的用户(用户账户上配置了 SPN) |
| AD-009 | 🟠 高 | Tier-0 管理员组规模过大 |
| AD-010 | 🟠 高 | Tier-0 管理员密码已过期 |
| AD-013 | 🟠 高 | 计算机运行着已停止支持的操作系统 |
| AD-015 | 🟠 高 | `krbtgt` 密码近期未被轮换 |
| AD-005 | 🟡 中 | 密码永不过期 (`DONT_EXPIRE_PASSWORD`) |
| AD-006 | 🟡 中 | 已启用的用户账户处于休眠状态 |
| AD-007 | 🟡 中 | 已启用的用户账户从未登录过 |
| AD-011 | 🟡 中 | 孤立的 `adminCount=1` 账户保留了强化的 ACL |
| AD-014 | 🟡 中 | 计算机账户已失效 |
| AD-016 | 🟡 中 | `ms-DS-MachineAccountQuota` 允许任何用户加入计算机 |
| AD-017 | 🟡 中 | 域密码策略弱于推荐标准 |
| AD-008 | 🔵 低 | 密码在较长一段时间内未更改 |
特权组是通过**众所周知的 RID 而非显示名称**来匹配的,因此
`Domänen-Admins` 和 `Администраторы домена` 都能被正确检测到。
## 架构
```
flowchart LR
subgraph Sources
A[Live LDAP
S.DS.Protocols] B[JSON snapshot
offline / air-gapped] end A --> N[Normalised snapshot] B --> N N --> E[Rule engine
17 pure functions] E --> F[Findings] F --> S[Score 0-100
+ grade] F --> H[Self-contained HTML] F --> J[JSON for automation] F --> C[Terminal summary] S --> X[Exit code
for CI] ``` **数据获取**与**评估**之间的解耦是整个设计的核心。规则是 `(snapshot, config)` 的纯函数, 因此全部 17 项规则都可以在完全没有 Active Directory 环境的情况下通过测试数据(fixtures)进行单元测试——而且同一个引擎既可以分析实时域、 分析来自客户现场的快照,也可以分析两年前的 JSON 文件。 基于时间周期的规则是根据快照自身的 `capturedAt` 时间戳来计算的,而不是当前系统时间。 明年重新分析一份归档的快照,得出的结论会与它当初被捕获时完全一致。 ### 为什么不需要 RSAT ADRot 直接与随 .NET 附带的 `System.DirectoryServices.Protocols` 通信,而不是使用 RSAT 的 `ActiveDirectory` 模块。这意味着它可以在被锁定的堡垒机、容器中运行, 或者运行在 Linux CI runner 上——任何能运行 PowerShell 7 的地方都可以。普通的 LDAP 绑定支持签名和加密, 因为未签名的绑定是可被中继的,而一个用于审计域安全的工具本身不应该成为薄弱环节。 ## 配置 配置文件、环境变量或参数——后者的优先级更高。复制 [`adrot.config.example.json`](adrot.config.example.json) 或 [`.env.example`](.env.example) 即可开始使用。 | 设置项 | 环境变量 | 默认值 | 功能说明 | |---|---|---|---| | `server` | `ADROT_SERVER` | 自动发现 | DC 主机名或域名 DNS 名称 | | `port` | `ADROT_PORT` | `389` | LDAP 端口(如果使用 SSL 则隐含为 `636`) | | `useSsl` | `ADROT_USE_SSL` | `false` | 使用 LDAPS | | `authType` | `ADROT_AUTH_TYPE` | `Negotiate` | `Negotiate` 或 `Anonymous` —— **绝不接受密码** | | `searchBase` | `ADROT_SEARCH_BASE` | 默认命名上下文 | 将扫描范围限定在某个 OU | | `filters.user` | — | `(&(objectCategory=person)(objectClass=user))` | 覆盖默认的用户搜索条件 | | `filters.computer` | — | `(objectCategory=computer)` | 覆盖默认的计算机搜索条件 | | `filters.group` | — | `(objectCategory=group)` | 覆盖默认的组搜索条件 | | `thresholds.staleDays` | `ADROT_STALE_DAYS` | `90` | 休眠时间阈值 | | `thresholds.krbtgtMaxAgeDays` | `ADROT_KRBTGT_MAX_AGE_DAYS` | `180` | krbtgt 轮换周期阈值 | | `thresholds.maxPrivilegedMembers` | `ADROT_MAX_PRIVILEGED_MEMBERS` | `5` | Tier-0 组规模限制 | | `thresholds.minPasswordLength` | `ADROT_MIN_PASSWORD_LENGTH` | `14` | 可接受的最小密码长度 | | `thresholds.maxPasswordAgeDays` | — | `365` | 密码有效期阈值 | | `legacyOperatingSystems` | — | 2000 → Server 2012 | 被视为已停止支持的 OS 前缀 | | `disabledRules` | — | `[]` | 要跳过的规则 ID,例如 `["AD-007"]` | | `failOn` | `ADROT_FAIL_ON` | `None` | 达到或超过此严重程度时以退出代码 2 退出 | | `logLevel` | `ADROT_LOG_LEVEL` | `Info` | `Debug`, `Info`, `Warn`, `Error` | **出于设计考量,没有密码设置项。** ADRot 以运行者的 Windows 身份进行验证。 它从不接受、存储或传输任何凭证。 **退出代码**适用于**容器入口点**以及上述的 `try/catch` 包装器: `0` 正常 · `1` 扫描无法运行 · `2` 发现的问题违反了 `-FailOn` 阈值。 在没有包装器的情况下直接在 `pwsh -c` 中调用该 cmdlet 时,如果违反阈值则会返回退出码 `1`, 因为这是 PowerShell 报告任何未处理的终止性错误的默认方式。 ## 测试 ``` # 安装两个 dev dependencies Install-Module Pester -MinimumVersion 6.0.0 -Scope CurrentUser -Force Install-Module PSScriptAnalyzer -Scope CurrentUser -Force # Lint + 125 个单元测试 — 无需 Docker 和 Active Directory Invoke-ScriptAnalyzer -Path ./src -Recurse -Settings ./PSScriptAnalyzerSettings.psd1 Invoke-Pester ./tests/unit ``` 集成测试需要 Docker: ``` docker compose -f docker/docker-compose.yml up -d --build --wait pwsh -c "Invoke-Pester ./tests/integration" docker compose -f docker/docker-compose.yml down -v ``` 如果你的 PATH 中有 `make`,那么可以使用 [`Makefile`](Makefile) 封装上述所有命令(`make check`, `make ldap-up`, `make test-integration`, `make report`)。 **125 个单元测试**涵盖了针对干净测试数据(必须产生*零*个问题)和故意布置腐朽问题的测试数据(必须触发*每一个*规则——证明没有规则会静默失效)的每一项规则, 此外还涵盖了评分、配置优先级、快照标准化以及针对 HTML 注入的防御。 **25 个集成测试**针对 Docker 中的真实 LDAP 服务器运行——这是一个 Debian + slapd 测试夹具,带有一个手写的 AD 型 schema,并填充了 604 个用户, 以便真正对分页搜索进行压力测试,而不是让其刚好适应在单个页面内。它还证明了离线工作流是无损的:导出快照并重新扫描它,产生的结果与实时扫描同一目录在字节上完全一致。该套件发现了两个单元测试无法发现 的 bug:二进制的 `objectSid` 值被静默解码为乱码,以及服务器端的数量限制表现为无用的原始 .NET 异常。 有一点需要明确它**没有**证明什么:测试夹具使用的是 OpenLDAP,而不是 Active Directory。真实的 `objectCategory` 过词语义、RootDSE `defaultNamingContext` 以及 Kerberos 绑定仍然需要一个真实的域环境来进行测试。 ## 路线图 - [ ] 趋势模式 —— 对比两个快照并展示哪些情况好转或恶化 - [ ] 在 AD-017 中支持细粒度密码策略 (PSO) - [ ] GPO 卫生规则(未链接的 GPO、偏好设置中的密码) - [ ] 基于资源的约束委派和 `msDS-AllowedToActOnBehalfOfOtherIdentity` - [ ] 证书服务配置错误 (ESC1–ESC8) - [ ] 发布到 PowerShell Gallery - [ ] 输出 SARIF 以适配代码扫描仪表板 规则即是数据 —— 只需使用 [`New-ADRotRule`](src/ADRot/Private/Engine/Invoke-ADRotRuleSet.ps1),加上一个纯 `Test` 脚本块和一个测试用例即可。 非常欢迎大家贡献新的规则。 ## 注意事项 ADRot 是一款日常卫生扫描工具,它不是渗透测试,也不是合规认证。它 仅读取 LDAP:它不检查 GPO 内容、ACL、SYSVOL、证书模板或磁盘上的任何内容。完美的 ADRot 评分仅仅意味着它检查的那十七个项目是正常的——仅此 而已。 快照文件包含目录元数据(账户名、DN、组成员身份、操作系统版本、时间戳)。它不包含密码或哈希,因为 ADRot 从不请求这些属性。请像对待任何 AD 资产清单导出数据一样对待它。 ## 许可证 MIT —— 详见 [LICENSE](LICENSE)。你可以将其用于商业目的,用它来审计你的客户,或者 fork 它。
S.DS.Protocols] B[JSON snapshot
offline / air-gapped] end A --> N[Normalised snapshot] B --> N N --> E[Rule engine
17 pure functions] E --> F[Findings] F --> S[Score 0-100
+ grade] F --> H[Self-contained HTML] F --> J[JSON for automation] F --> C[Terminal summary] S --> X[Exit code
for CI] ``` **数据获取**与**评估**之间的解耦是整个设计的核心。规则是 `(snapshot, config)` 的纯函数, 因此全部 17 项规则都可以在完全没有 Active Directory 环境的情况下通过测试数据(fixtures)进行单元测试——而且同一个引擎既可以分析实时域、 分析来自客户现场的快照,也可以分析两年前的 JSON 文件。 基于时间周期的规则是根据快照自身的 `capturedAt` 时间戳来计算的,而不是当前系统时间。 明年重新分析一份归档的快照,得出的结论会与它当初被捕获时完全一致。 ### 为什么不需要 RSAT ADRot 直接与随 .NET 附带的 `System.DirectoryServices.Protocols` 通信,而不是使用 RSAT 的 `ActiveDirectory` 模块。这意味着它可以在被锁定的堡垒机、容器中运行, 或者运行在 Linux CI runner 上——任何能运行 PowerShell 7 的地方都可以。普通的 LDAP 绑定支持签名和加密, 因为未签名的绑定是可被中继的,而一个用于审计域安全的工具本身不应该成为薄弱环节。 ## 配置 配置文件、环境变量或参数——后者的优先级更高。复制 [`adrot.config.example.json`](adrot.config.example.json) 或 [`.env.example`](.env.example) 即可开始使用。 | 设置项 | 环境变量 | 默认值 | 功能说明 | |---|---|---|---| | `server` | `ADROT_SERVER` | 自动发现 | DC 主机名或域名 DNS 名称 | | `port` | `ADROT_PORT` | `389` | LDAP 端口(如果使用 SSL 则隐含为 `636`) | | `useSsl` | `ADROT_USE_SSL` | `false` | 使用 LDAPS | | `authType` | `ADROT_AUTH_TYPE` | `Negotiate` | `Negotiate` 或 `Anonymous` —— **绝不接受密码** | | `searchBase` | `ADROT_SEARCH_BASE` | 默认命名上下文 | 将扫描范围限定在某个 OU | | `filters.user` | — | `(&(objectCategory=person)(objectClass=user))` | 覆盖默认的用户搜索条件 | | `filters.computer` | — | `(objectCategory=computer)` | 覆盖默认的计算机搜索条件 | | `filters.group` | — | `(objectCategory=group)` | 覆盖默认的组搜索条件 | | `thresholds.staleDays` | `ADROT_STALE_DAYS` | `90` | 休眠时间阈值 | | `thresholds.krbtgtMaxAgeDays` | `ADROT_KRBTGT_MAX_AGE_DAYS` | `180` | krbtgt 轮换周期阈值 | | `thresholds.maxPrivilegedMembers` | `ADROT_MAX_PRIVILEGED_MEMBERS` | `5` | Tier-0 组规模限制 | | `thresholds.minPasswordLength` | `ADROT_MIN_PASSWORD_LENGTH` | `14` | 可接受的最小密码长度 | | `thresholds.maxPasswordAgeDays` | — | `365` | 密码有效期阈值 | | `legacyOperatingSystems` | — | 2000 → Server 2012 | 被视为已停止支持的 OS 前缀 | | `disabledRules` | — | `[]` | 要跳过的规则 ID,例如 `["AD-007"]` | | `failOn` | `ADROT_FAIL_ON` | `None` | 达到或超过此严重程度时以退出代码 2 退出 | | `logLevel` | `ADROT_LOG_LEVEL` | `Info` | `Debug`, `Info`, `Warn`, `Error` | **出于设计考量,没有密码设置项。** ADRot 以运行者的 Windows 身份进行验证。 它从不接受、存储或传输任何凭证。 **退出代码**适用于**容器入口点**以及上述的 `try/catch` 包装器: `0` 正常 · `1` 扫描无法运行 · `2` 发现的问题违反了 `-FailOn` 阈值。 在没有包装器的情况下直接在 `pwsh -c` 中调用该 cmdlet 时,如果违反阈值则会返回退出码 `1`, 因为这是 PowerShell 报告任何未处理的终止性错误的默认方式。 ## 测试 ``` # 安装两个 dev dependencies Install-Module Pester -MinimumVersion 6.0.0 -Scope CurrentUser -Force Install-Module PSScriptAnalyzer -Scope CurrentUser -Force # Lint + 125 个单元测试 — 无需 Docker 和 Active Directory Invoke-ScriptAnalyzer -Path ./src -Recurse -Settings ./PSScriptAnalyzerSettings.psd1 Invoke-Pester ./tests/unit ``` 集成测试需要 Docker: ``` docker compose -f docker/docker-compose.yml up -d --build --wait pwsh -c "Invoke-Pester ./tests/integration" docker compose -f docker/docker-compose.yml down -v ``` 如果你的 PATH 中有 `make`,那么可以使用 [`Makefile`](Makefile) 封装上述所有命令(`make check`, `make ldap-up`, `make test-integration`, `make report`)。 **125 个单元测试**涵盖了针对干净测试数据(必须产生*零*个问题)和故意布置腐朽问题的测试数据(必须触发*每一个*规则——证明没有规则会静默失效)的每一项规则, 此外还涵盖了评分、配置优先级、快照标准化以及针对 HTML 注入的防御。 **25 个集成测试**针对 Docker 中的真实 LDAP 服务器运行——这是一个 Debian + slapd 测试夹具,带有一个手写的 AD 型 schema,并填充了 604 个用户, 以便真正对分页搜索进行压力测试,而不是让其刚好适应在单个页面内。它还证明了离线工作流是无损的:导出快照并重新扫描它,产生的结果与实时扫描同一目录在字节上完全一致。该套件发现了两个单元测试无法发现 的 bug:二进制的 `objectSid` 值被静默解码为乱码,以及服务器端的数量限制表现为无用的原始 .NET 异常。 有一点需要明确它**没有**证明什么:测试夹具使用的是 OpenLDAP,而不是 Active Directory。真实的 `objectCategory` 过词语义、RootDSE `defaultNamingContext` 以及 Kerberos 绑定仍然需要一个真实的域环境来进行测试。 ## 路线图 - [ ] 趋势模式 —— 对比两个快照并展示哪些情况好转或恶化 - [ ] 在 AD-017 中支持细粒度密码策略 (PSO) - [ ] GPO 卫生规则(未链接的 GPO、偏好设置中的密码) - [ ] 基于资源的约束委派和 `msDS-AllowedToActOnBehalfOfOtherIdentity` - [ ] 证书服务配置错误 (ESC1–ESC8) - [ ] 发布到 PowerShell Gallery - [ ] 输出 SARIF 以适配代码扫描仪表板 规则即是数据 —— 只需使用 [`New-ADRotRule`](src/ADRot/Private/Engine/Invoke-ADRotRuleSet.ps1),加上一个纯 `Test` 脚本块和一个测试用例即可。 非常欢迎大家贡献新的规则。 ## 注意事项 ADRot 是一款日常卫生扫描工具,它不是渗透测试,也不是合规认证。它 仅读取 LDAP:它不检查 GPO 内容、ACL、SYSVOL、证书模板或磁盘上的任何内容。完美的 ADRot 评分仅仅意味着它检查的那十七个项目是正常的——仅此 而已。 快照文件包含目录元数据(账户名、DN、组成员身份、操作系统版本、时间戳)。它不包含密码或哈希,因为 ADRot 从不请求这些属性。请像对待任何 AD 资产清单导出数据一样对待它。 ## 许可证 MIT —— 详见 [LICENSE](LICENSE)。你可以将其用于商业目的,用它来审计你的客户,或者 fork 它。
标签:Active Directory, AI合规, Checkov, GraphQL安全矩阵, IPv6, Libemu, Plaso, PowerShell, 关系图谱, 基线检查, 多人体追踪, 安全扫描器, 模拟器, 请求拦截, 身份认证安全