何时使用
已经定位到一个漏洞/bug,需要在整个代码库(甚至多仓库)找出同根因的其他实例时使用。典型场景:
- 修复一处漏洞后,怀疑别处有相同写法的拷贝粘贴变体。
- 为某个安全模式构建/打磨 CodeQL、Semgrep 查询或 ripgrep 正则。
- 发现初始问题后做系统化横扫,分析一个根因在不同代码路径上的多种表现。
不该用:
- 初次发现漏洞(还没有已知模式可抽象)→ 先做审计/上下文构建,本技能从「已有一个样本」起步。
- 没有具体模式、只想泛泛过一遍代码 → 用
code-reviewer。 - 写修复建议/补丁 → 本技能只负责「找到 WHERE」,不产出 fix。
- 只关心第三方依赖 CVE/许可证 → 用
dependency-auditor。
步骤 / 指令
核心是「根因优先 + 每次只放宽一个元素 + 边放宽边量化误报」。
吃透原始漏洞(根因,不是症状)。用一句话写出根因陈述:
「此漏洞存在,是因为〔不可信数据〕到达〔危险操作〕却缺少〔必需防护〕。」
例:「用户输入到达
eval()而未净化」「攻击者可控的 size 到达malloc()而无溢出检查」。这句话就是你的搜索模式起点。先做精确匹配(Level 0)。用字面量命中且只命中那一处原始漏洞:
rg -n "exact_vulnerable_code_here"验证:是否恰好 1 个匹配(即原始位置)?这是后续泛化的基线。
识别可抽象点。逐元素判断保留还是抽象:
- 变量名 → 永远抽象成元变量(
$INPUT、$QUERY)。 - 函数名 → 仅对该 bug 唯一时保留;属于一类问题时可抽象。
- 字面值 → 仅特定值危险(如移位的
2)时保留;任意值都触发则抽象。 - 参数 → 位置无关用
...通配;只有特定位置是 sink 才保留。
- 变量名 → 永远抽象成元变量(
沿抽象阶梯逐级爬升,每次只改一个元素:
- Level 1 变量抽象:
$QUERY = "SELECT * FROM users WHERE id=" + $INPUT(抓拷贝粘贴变体)。 - Level 2 结构抽象:配合
pattern-inside限定上下文(如在某函数内cursor.execute($Q))。 - Level 3 语义/污点抽象(taint):定义
pattern-sources(如request.args.get(...))与pattern-sinks(如cursor.execute(...)),覆盖最广但误报最高。
每改一步:跑 → 审查全部新增匹配 → 分类真/假阳性 → 误报可接受则继续放宽,否则回退换别的抽象。
- Level 1 变量抽象:
何时停手:误报率超过约 50% 就是放过头了,回退。可接受误报率按场景:CI 阻断 <5%、开发者告警 <20%、安全审计三分类 <50%、研究探索 <80%。
降误报的常用过滤:
rg "pattern" --glob '!**/test*' --glob '!**/*_test.*' # 排除测试代码pattern-not: dangerous_func(sanitize($X)) # 已净化 pattern-not: dangerous_func("...") # 字面量,非用户可控 pattern-not-inside: | if False: ... # 死代码分类与定级。每个匹配记录:位置(文件/行/函数)、置信度(高/中/低)、可利用性(是否可达?输入是否可控?)、优先级(按影响 × 可利用性)。
示例
抽象阶梯(同一 SQL 注入根因,命中数随级别上升):
# 原始漏洞
query = "SELECT * FROM users WHERE id=" + request.args.get('id')
# Level 0:精确匹配,命中=1,误报=0
rg 'SELECT \* FROM users WHERE id=" \+ request\.args\.get'
# Level 3:污点模式,命中 50~100+,需大量三分类
mode: taint
pattern-sources:
- pattern: request.args.get(...)
- pattern: request.form.get(...)
pattern-sinks:
- pattern: cursor.execute(...)
工具选型:
- 快速浅表搜索 → ripgrep(零配置、最快,适合侦察找热点)。
- 简单模式匹配 / 不可构建的代码 → Semgrep(语法简单、无需编译)。
- 数据流追踪 → Semgrep taint 或 CodeQL。
- 跨函数过程间分析(最精确)→ CodeQL。
- 大规模多仓库战役:侦察(ripgrep 找热点) → 深挖(Semgrep/CodeQL) → 降误报 → 固化为 CI 规则。
追踪文档(边查边记,价值等同于模式本身):
## 变体分析:[原始 Bug ID]
### 根因
[漏洞模式陈述]
### 尝试过的模式
| 模式 | 级别 | 命中 | 真阳 | 假阳 | 备注 |
|------|------|------|------|------|------|
| 精确 | 0 | 1 | 1 | 0 | 基线 |
### 已确认变体
| 位置 | 严重度 | 状态 | 备注 |
注意事项
四个最容易漏掉真漏洞的坑:
- 搜索范围太窄:只搜原始 bug 所在模块。例:bug 在
api/handlers/,只搜该目录,漏掉utils/auth.py里的变体。→ 永远对整个代码库根目录搜索。 - 模式太具体:只搜原始的那个属性/函数。例:bug 用
isAuthenticated,只搜这一个词,漏掉isActive/isAdmin/isVerified。→ 枚举该类问题所有语义相关的属性/函数。 - 只盯一种漏洞类:只看根因的一种表现。例:根因是「条件为假时却 return allow」,要同时查:空值相等绕过(
null == null为真)、文档与代码语义相反(函数名/docstring 说 deny 实则 allow)、条件取反走错分支。→ 搜索前先列全所有可能表现。 - 漏测边界用例:只用「正常」场景测模式。→ 必须测:未认证用户、null/undefined、空集合、边界值(如
userId=null撞上resourceOwnerId=null的鉴权绕过)。
变体大量聚集是有原因的——开发者习惯一致、拷贝粘贴扩散、复杂 API 一致误用、框架惯用法、修复不彻底。理解「为什么有变体」能预测「去哪找」。
互见
code-reviewer:无已知模式的常规代码审查 / 找 bug。dependency-auditor:第三方依赖 CVE 与供应链审计。
本条采编自 trailofbits/skills(variant-analysis),许可证 CC-BY-SA-4.0。