mistermobilka-spec/adrot

GitHub: mistermobilka-spec/adrot

纯 PowerShell 编写的 Active Directory 只读安全扫描器,无需 RSAT 和管理员权限,对域环境进行 17 项安全卫生检查并输出评分和修复建议。

Stars: 0 | Forks: 0

# ADRot **找出你 Active Directory 中的腐朽之处。一个脚本,无需安装,只读。** [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/mistermobilka-spec/adrot/actions/workflows/ci.yml) [![许可证: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![PowerShell 7+](https://img.shields.io/badge/PowerShell-7%2B-5391FE.svg)](https://github.com/PowerShell/PowerShell) [![无需 RSAT](https://img.shields.io/badge/RSAT-not%20required-success.svg)](#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 它。
标签:Active Directory, AI合规, Checkov, GraphQL安全矩阵, IPv6, Libemu, Plaso, PowerShell, 关系图谱, 基线检查, 多人体追踪, 安全扫描器, 模拟器, 请求拦截, 身份认证安全