rsh1k/condor-scan
GitHub: rsh1k/condor-scan
一款以攻击路径为核心分析模型的 GCP IAM 权限提升扫描器,能够推演权限链、评估外部暴露面、计算修复优先级并映射检测盲点。
Stars: 0 | Forks: 0
# condor-scan
**一款以攻击路径而非零散发现为思路的 GCP 权限提升扫描器。**
condor-scan 读取你的 Google Cloud IAM 导出文件,不仅能告诉你*谁拥有危险的权限*,还能告诉你*谁能将这些权限串联起来获得完全控制权*、*其中哪些权限可以被外部攻击者触及*、*最高优先级的修复点是什么*,以及*你是否甚至能在日志中看到该攻击*。
它最初只是为了扫描一种特定的、未被充分重视的提升类别——基于标签的 IAM Conditions 滥用——后来在此基础上发展出了一个分析层,能够回答安全团队在实际漏洞分诊中真正会争论的那些问题。
## 为什么会有这个项目
市面上并不缺乏 GCP 安全扫描器。Prowler、ScoutSuite、Forseti 以及商业 CSPM/CIEM 平台都能胜任枚举 IAM 错误配置的工作。但在使用它们一段时间后,你会发现两个反复出现的痛点:
**它们对权限进行建模,而不是对路径。** 大多数工具会将“主体 X 拥有 `iam.serviceAccounts.actAs`”作为一个孤立的事实告诉你。它们很少会告诉你,X 可以 `actAs` 一个本身就是项目 Owner 的服务账号,而这才是真正要命的。真实的 GCP 提升几乎从来不是单步完成的;它是一条链——一个泄露的密钥指向一个默认的 service account,该 service account 可以 `actAs` 一个高权限的 service account,而后者持有一个组织级别的绑定。链条中的每一环单独看都毫不起眼。
**它们完全忽略了基于标签的条件提升。** IAM Conditions 允许你仅在 CEL 表达式成立时才授予角色——例如,仅在标记为 `env=prod` 的资源上。这是一个很棒的功能。但问题在于:如果一个低权限用户可以*自己附加该标签*,他们就可以随时满足条件并获取该角色。Mitiga 在 2026 年将此记录为“Tag Your Way In”,而 Google 的立场是这是客户端的配置错误——这意味着检测它是你的工作,而且几乎没有工具会这么做,因为几乎所有工具都只对静态绑定进行建模。
condor-scan 是围绕对链条的建模而构建的,并且它将标签条件提升视为该链条中的一等公民边。
## 它实际计算的内容
### 1. 提升引擎(能力闭包)
核心是一个小型的、确定性的引擎,用于计算**能力闭包**。对于每个主体,它从该主体持有的权限和身份开始,然后重复应用提升规则,直到无法到达任何新内容为止:
- **CONDOR-SETIAMPOLICY** — 持有 `*.setIamPolicy` 允许你将自己绑定为 Owner。一招毙命。
- **CONDOR-ROLEUPDATE** — 对你持有的自定义角色拥有 `iam.roles.update` 权限,允许你向其添加任何权限。
- **CONDOR-IMPERSONATE** — service account 模拟:通过 Token Creator(生成 access token)、通过密钥创建,或者通过 `actAs` 结合部署权限(部署*以*该 SA *身份*运行的代码)。当你到达一个 SA 时,你继承了它能做的一切,并且闭包会从那里继续进行。
- **CONDOR-TAGCONDITION** — 上面描述的基于标签的条件路径。
由于闭包只会*增加*到可达能力集合中,且权限的集合是有限的,因此它总是会终止。每条触发的规则都会记录一个步骤,因此输出的是完整的链条,而不仅仅是一个结论。
这很有用,但并不新颖——这是严肃工具的基本要求。有趣的是它之上的内容。
### 2. 感知暴露的来源分析 —— *外部攻击者能触及此点吗?*
一个能够提升至 Owner 的主体是一个问题。一个能够提升至 Owner *且可从互联网访问*的主体,则是一起安全事件。这两者理应拥有截然不同的优先级,而几乎没有扫描器能区分它们。
condor-scan 对**不受信任的来源**进行了建模:公共 IAM 成员 `allUsers` 和 `allAuthenticatedUsers`(当它们实际被绑定到某些内容时),以及你在导出文件中声明为暴露在互联网上的任何身份——例如,附加到公共 Cloud Run 服务的 service account。然后,它*从*这些来源运行闭包,并标记源自不受信任身份或可被其到达的每一个提升发现。
实际的好处在于:当报告显示“有 2 条外部暴露的路径通往 Tier Zero”时,无论原始严重程度如何排序,这两条路径都会排到队列的最前面。这正是将初始访问与权限提升联系起来的纽带,它将理论上的错误配置转变为一条你可以像攻击者一样进行推理的路径。
### 3. 关键节点分析 —— *我该先修复什么?*
这是我最满意的部分,也是该工具在庞大资产规模下能体现其价值的原因。
提升发现并不是孤立的。在一个拥有数百个主体的组织中进行扫描,你会发现许多截然不同的提升链都汇聚在*相同*的极少数 IAM 授权上——一个过于宽泛的群组绑定,一个所有人都可以模拟的共享 service account。如果你盲目地按照严重程度排序的列表进行修复,你做了大量的工作,但结构性收益却微乎其微。
condor-scan 将每条链中的每一个步骤归因于启用它的、特定的、可修复的授权(即在资源上*将角色绑定给成员*,可能是条件性的)。然后,它将“阻断每一条通往 Tier Zero 的路径”视为这些授权上的一个**最小集合覆盖问题**,并通过贪心算法对其进行求解:反复挑选能够解决最多尚未解决的提升主体的授权。
最小集合覆盖是 NP-hard 问题,因此这是标准的贪心近似(具有经典的 `ln(n)` 界限)。我故意不假装它是最优的。它给你的是正确的*工作顺序*:一份优先级的修复计划,其中第一个项目被证明是能够瓦解最多攻击路径的单一更改。在捆绑的示例中,首当其冲的关键节点是一个群组绑定——移除它——就能立即消除两个主体的提升,优先级排在五个单主体修复之前。
这里有一个在工具中和这里都需要坦诚说明的警告:移除链上的一个授权可能会留下另一条备用的子路径,因此你应该在修复后重新扫描。这种排名仍然是正确的工作顺序;只是它不能保证一次移除就能完全消除某个主体的威胁。
### 4. 检测可见性映射 —— *我们真的能看到它吗?*
一条不产生日志的路径比产生日志的路径更危险,因为它会使检测和事件响应双双失效。我从未见过有哪种错误配置扫描器会告诉你这一点,而这确实是一个非常重要的维度。
每个提升原语都被映射到其对应的 MITRE ATT&CK (Cloud) 技术,更重要的是,映射到它是否**默认**产生 Cloud Audit Log 条目。
这里关键的区别是 GCP 特有的:
- **Admin Activity** 日志始终开启,免费,且无法禁用。它们涵盖了更改配置的调用:`SetIamPolicy`、`CreateServiceAccountKey`、角色更新、资源部署、`CreateTagBinding`。通过这些进行的提升是可见的。
- **Data Access** 日志*默认关闭*(BigQuery 除外),计费,且必须显式启用。它们涵盖了大多数读取操作——包括 IAM Service Account Credentials API 上的 `GenerateAccessToken`、`GenerateIdToken` 和 `SignJwt`。
因此,**service account token 模拟在开箱即用状态下通常是不可见的**,而密钥创建则会被记录。并且**基于标签的条件提升以一种更隐蔽的方式不可见**:条件绑定已经存在,因此通过附加标签来满足它只会发出一个 `CreateTagBinding` 事件——永远不会发出 IAM 策略更改事件。大多数 SIEM 检测内容都以策略更改为关键点,因此它根本不会触发。condor-scan 将这些静默路径明确标记为“检测盲点”,以便 SOC 确切知道在哪里开启 Data Access 日志记录或编写标签绑定检测规则。
### 5. 时间 / JIT 感知 —— *它现在可被利用吗?*
IAM Conditions 通常是受时间限制的。一项紧急破窗流程会授予四个小时的 Owner 权限;一名承包商的访问权限在合作结束后过期;即时(JIT)访问工具会按需分发短暂的有效期提升。所有这些都通过 CEL 针对 `request.time` 表示。
忽略时间的扫描器会在两件事上犯错。它将**已过期**的授权报告为实时提升——这是一个误报,会浪费响应人员的时间去追逐不再存在的访问权限。而且它无法标记出对于实时监控最重要的情况:**现在处于活动状态但很短暂**的授权。每日的 CSPM 扫描可能会完全错过一个两小时的紧急破窗时间窗口。
condor-scan 会从每个条件中解析出 `request.time` 边界,并根据评估时刻对每个条件性授权进行分类:
- **已过期**的授权会从闭包中丢弃——它们不再是路径,因此不再是发现。(这也悄悄修复了该工具之前存在的一个真正的正确性错误:一个时间窗口已过的基于标签的条件授权,以前会被报告为可利用的。)
- **未来**的授权不计入实时提升,而是作为*计划中/休眠的*潜在风险单独列出——在它生效前进行审查。
- **活动**授权会正常分析,并且如果时间窗口很短(低于可配置的阈值,默认为 24 小时),该发现将被标记为 **JIT** 及其过期时间。此外还有一个专门的规则用于处理提升角色的活动、受时间限制的*直接*授权——即字面意义上的紧急破窗情况——如果不这样,引擎根本不会将其显示出来。
由于评估时刻是可注入的(`--as-of`),你也可以提出前瞻性的问题:*下周一 09:00 什么是可利用的?* 同一份导出文件,在两个不同时刻进行评估,会给出两个不同的答案——这正是我们所期望的。
## 安装
condor-scan 发布在 [PyPI](https://pypi.org/project/condor-scan/) 上:
```
pip install condor-scan
```
这会安装 `condor-scan` 命令。如果要在代码检出基础上进行开发,并配合测试和 lint 工具:
```
pip install -e ".[dev]"
```
分析核心具有**零运行时依赖**——它是纯粹的标准库。对于安全工具来说,这是一个深思熟虑的选择:依赖树中的第三方包越少,你运行它时所面临的供应链攻击面就越小。实时的 Cloud Asset Inventory 摄入是唯一可选的附加功能(`pip install ".[cloud]"`),因为它会拉取 Google 客户端库。
需要 Python 3.10+。
## 用法
### 扫描提升链
```
condor-scan scan export.json # human-readable table
condor-scan scan export.json --format json # machine-readable, ATT&CK-enriched
condor-scan scan export.json --format sarif # SARIF 2.1.0 for CI dashboards
condor-scan scan export.json --fail-on critical # non-zero exit to gate CI
```
### 运行攻击路径安全态势报告
这是分诊/高管视图——将暴露情况、修复计划和盲点集中在一处展示:
```
condor-scan posture export.json
condor-scan posture export.json --format json
condor-scan posture export.json --fail-on-exposed # CI gate on internet-reachable Tier Zero
# Temporal 问题:在选定的时间点评估时间约束条件。
condor-scan posture export.json --as-of 2026-12-01T09:00:00Z
condor-scan posture export.json --jit-threshold-hours 4 # what counts as "short-lived"
condor-scan scan export.json --as-of 2026-12-01T09:00:00Z # scan also honours --as-of
```
针对捆绑的 `examples/sample_export.json` 的示例输出:
```
condor-scan - attack-path posture report
================================================
Principals analyzed .............. 10
Escalation findings .............. 8
Can reach Tier Zero .............. 8
Externally exposed -> Tier Zero .. 2
Remediation budget (choke points) 7
Detection blind spots ............ 4
EXTERNALLY EXPOSED PATHS TO TIER ZERO (fix first):
! serviceAccount:frontend@demo-prod.iam.gserviceaccount.com
! serviceAccount:privileged@demo-prod.iam.gserviceaccount.com
PRIORITISED REMEDIATION PLAN (greedy choke-point cover):
1. remove/scope: roles/owner -> group:platform@example.com on .../projects/demo-prod [condition: only-prod-tagged]
-> eliminates escalation for 2 principal(s): user:alice@example.com, user:grace@example.com
...
DETECTION BLIND SPOTS (escalation with no default audit signal):
~ [CONDOR-TAGCONDITION] user:alice@example.com: attach tag to satisfy condition 'only-prod-tagged' ...
~ [CONDOR-IMPERSONATE] user:bob@example.com: impersonate 'privileged@...' via 'iam.serviceAccounts.getAccessToken'
```
### 生成预防性策略
检测标签条件提升的相同逻辑,可以在部署时(Terraform 验证、CI 门禁)作为 OPA / Policy Library 约束*预防性地*运行,而不是仅仅作为事后扫描:
```
condor-scan gen-constraints --out-dir ./policy
```
这会生成一个 Rego 约束模板以及一个兼容 Config Validator / Gatekeeper 生态系统的实例,供 Forseti 的 `policy-library` 使用。它是刻意解耦的:该约束独立存在,可以由 Google 的开源策略工具采用,而完全不需要依赖本项目。
## 获取输入
condor-scan 读取类似于 Cloud Asset Inventory 导出格式的 JSON:
```
gcloud asset search-all-iam-policies --scope=organizations/ORG_ID --format=json
```
你需要将其转换为文档化的模式(`roles`、`iam_policies`、`tag_bindings`、`group_members`、`exposed_principals`)。请参阅 `examples/sample_export.json` 获取完整的、通过示例注释的文件。当你想跳过手动导出时,`loaders.py` 中的 `load_from_cloud_asset_inventory()` 桩代码勾勒了直接客户端集成的轮廓。
`exposed_principals` 列表是你输入网络现实情况的方式——你的 CSPM、负载均衡器配置或 Cloud Run/Functions 清单知道哪些 service account 位于公共端点之后;在此列出它们,暴露分析就会变得有意义。
## 架构
```
loaders.py parse CAI/JSON export ─────────► model.py typed domain objects
│
analysis.py index: per-principal permissions, impersonation maps,
grant provenance, exposure sources
│
rules.py capability-closure engine ────────► findings.py Finding + JSON/SARIF/table
(attributes each step to a remediable grant)
│
graph.py attack-path intelligence: exposure, choke-point
set-cover, detection blind spots ──► PostureReport
│
techniques.py MITRE ATT&CK + audit-log visibility model
temporal.py request.time window parsing + JIT/expiry classification
constraints.py preventive OPA/Rego policy generator
cli.py argparse front-end (scan / posture / gen-constraints)
knowledge.py curated escalation primitives + predefined-role subsets
cel.py conservative tag-condition (CEL) parser
```
这种拆分是刻意的:索引是纯粹的且可缓存的,引擎是纯粹的图遍历,而智能层纯粹是对引擎输出的分析。每一层都可以独立进行单元测试,这就是为什么测试套件能够做到例如将贪心集合覆盖与闭包以及 CEL 解析器隔离开来的原因。
## 局限性(请阅读这些——夸大其覆盖范围的安全工具是危险的)
- **CEL 解析器在设计上是保守的。** 它能识别 `resource.matchTag(...)` 和 `resource.matchTagId(...)` 断言。其他条件类型(时间、IP、请求属性)被视为*不可*通过标签满足——这是低误报的默认设置。它不是一个完整的 CEL 评估器。
- **标签附加能力被广泛建模。** 持有 `resourcemanager.tagValueBindings.create` 被视为“可以附加标签”。真实的 GCP 额外要求在特定标签值上拥有 `tagValues.use` 权限,因此这可能会过度报告;在采取行动之前,请针对特定标签的 IAM 验证标签条件发现。
- **角色到权限的映射是一个精选的与提升相关的子集**而不是 GCP IAM 的完整镜像(后者拥有数以万计的权限)。自定义角色从导出文件中原样读取。保持映射的精选是保持低误报率和输出可审计的关键。
- **关键节点覆盖是贪心近似**,并且移除一个授权可能会留下备选路径——请在修复后重新扫描。排名是正确的工作顺序,而不是唯一性证明。
- **暴露情况仅与你提供的 `exposed_principals` 质量相当。** `allUsers`/`allAuthenticatedUsers` 会被自动检测;其他所有内容(公共 Cloud Run/Functions SA 等)都需要你来提供。
- **资源层级继承**(组织 → 文件夹 → 项目)是按每个策略资源建模的,而不是作为完整的继承绑定传播进行建模。
- **时间解析仅限于 `request.time` 与 `timestamp('...')` 的对比。** 其他时间表达式(持续时间、`request.time.getHours()`、重复窗口)在该侧被视为无界限——这是保守的默认设置。评估使用的是单一时刻;它不会对重复计划进行推理。
特意写下这些是为了正确解释发现结果,而不是盲目信任。
## 开发
```
pip install -e ".[dev]"
make check # the full gate: ruff + mypy --strict + pytest
make test # pytest with coverage
make demo # run the scanner against the bundled example
```
本项目秉持以下标准:**ruff** 检查无误,所有模块的 **mypy `--strict`** 检查无误,以及一个 **pytest** 测试套件(72 个测试,约 96% 的覆盖率),该套件通过正反两个用例测试了每一条提升路径,以及集合覆盖算法、暴露逻辑、盲点检测、时间/JIT 分类和端到端的 CLI。
## 发布(维护者)
发布版本使用 [`build`](https://pypi.org/project/build/) 构建并使用 [`twine`](https://pypi.org/project/twine/) 上传:
```
python -m pip install --upgrade build twine
rm -rf dist build *.egg-info
python -m build # produces dist/*.whl and dist/*.tar.gz
twine check dist/* # validate metadata renders on PyPI
twine upload dist/* # authenticate with a PyPI API token
```
为每个发布版本在 `pyproject.toml` 中更新 `version`(并在 `src/condor_scan/__init__.py` 中更新 `__version__`);PyPI 拒绝覆盖现有版本。请考虑先针对 TestPyPI 进行测试,或者配置 GitHub Actions Trusted Publishing,以确保 token 永远不会离开 CI 环境。
## 许可证
Apache-2.0。详见 `LICENSE`。
标签:CSPM, GCP, IAM, TinkerPop, 协议分析, 图数据库, 攻击路径分析, 权限提升, 逆向工具