用 variant-analysis 做變體分析:找到一個 Bug,再把同類搜乾淨

前言

安全審計裏有一件很常見的事:你在某個文件裏確認了一個漏洞,修完之後還是不放心——同樣的寫法會不會在別的模塊裏再出現一次?漏洞很少只活在被你點開的那一行。同一套開發習慣、一次複製粘貼、一次只改了一處的修復,都會讓同一個根因在倉庫裏留下好幾處變體。

手工做這件事通常停在原來那個文件,或者隨手寫一條 grep。字面量能對上的能找到,換了變量名、換了 API、換了語言的就漏掉。Trail of Bits 把這套安全工程師日常在做的「變體分析」寫成了 Agent Skill:variant-analysis。當前插件版本寫在元數據裏是 2.0.1,作者是 Axel Mierczuk,許可證是 CC BY-SA 4.0。它不負責幫你從零挖漏洞,專門解決「已經找到一個,倉庫裏還有沒有同類」這個問題。

variant-analysis 是什麼

variant-analysis 是 Trail of Bits 技能市場(trailofbits/skills)裏的一個插件,目錄在 plugins/variant-analysis/。它給 Agent 一套可重複的方法:先抽出根因,再寫成只命中已知實例的模式,然後一次只放寬一個條件去搜全庫,最後做分診並寫出報告。

官方對它的定位很窄,適合這些情況:

  • 已經確認了一個漏洞或邏輯錯誤,要在整倉裏找相似實例
  • 要把已知實例泛化成 CodeQL / Semgrep 規則,覆蓋同一類模式
  • 初次發現問題之後,做一次有方法的代碼審計,而不是再掃一遍「感覺可疑」的代碼
  • 分析同一個根因在不同代碼路徑上會以什麼樣子出現

官方也寫了不該用它的場景:還沒有已知 Bug、只是做一般代碼評審、要寫修復建議、或者連代碼還沒看懂。前面兩種分別該用同市場裏的 audit-context-building 或領域審計 Skill;寫修復建議用 issue-writer

Skill 本體是通用的 SKILL.md 格式,Claude Code、Codex、Cursor 這類支持 Agent Skills 的工具都可以加載。插件另外帶了一個工作流 /variant-analysis:variants,這是 Claude Code 插件命令,用來在大倉庫裏並行掃多個擴展軸。

核心方法:五步,而不是一條超級正則

插件把一次變體搜查拆成五步,每一步有對應的策略文檔,放在 skills/variant-analysis/references/ 裏。Agent 走到哪一步,就讀哪一份。

1、先理解原來那處問題。 要抽出的是「爲什麼錯」,不是「這行代碼在幹什麼」。官方建議先問四個問題:危險操作是什麼(eval()、拼 SQL、鑑權判斷)、什麼數據讓它危險、缺了哪一層保護、什麼上下文讓它成立。然後寫成一句根因陳述:

This vulnerability exists because [UNTRUSTED DATA] reaches [DANGEROUS OPERATION] without [REQUIRED PROTECTION].

例如:「用戶輸入到達 eval() 時沒有經過淨化」。沒有數據流的邏輯 Bug 就改寫被破壞的不變量,比如「未登錄調用者必須返回 False,但兩邊 ID 都是 null 時返回了 True」。這句話本身就是後面的搜索模式。同時列出變體可能藏在哪些方向:語義相近的標識符、同一種錯誤的其他寫法、空值與邊界情況。

2、先寫一條只命中已知實例的模式。 用 ripgrep 對原代碼做精確匹配,確認它打中、而且只打中那一處。一條匹配數爲零的模式說明你對 Bug 的理解不對,後面所有搜索都是在錯誤代碼上校準。

3–4、一次只泛化一個元素。 從精確匹配往模式家族上爬,每改一處就重新跑、把新增命中全部看完。假陽性超過大約一半就停,回退,換另一條抽象路徑。不要一次把變量名、函數名、參數位置全換成通配符——噪聲來了,你不知道是哪一步引入的。

5、分診。 判斷候選是不是真的同類問題,並帶上嚴重程度。只看片段不夠,要讀周圍函數、調用方、相關值的類型,專門去找「爲什麼它其實是安全的」:前面的守衛、淨化、參數化 API、類型約束。今天沒有調用方也不等於否定:無保護的代碼仍然是發現,只是嚴重程度可以標低。

五步之後要寫成報告,失敗過的模式也要留下,並給出一條能進 CI 的規則,防止修好一處、同類再長回來。報告模板在 resources/variant-report-template.md

工具怎麼選

Skill 不綁定單一掃描器。官方給的選擇很直接:

  • 快速摸底用 ripgrep:零配置,適合偵察
  • 簡單結構模式用 Semgrep:語法容易寫,不需要能編譯的工程,殘缺代碼也能跑
  • 要跟蹤值有沒有從源到匯,用 Semgrep taintCodeQL
  • 跨函數、要做過程間分析,用 CodeQL
  • 代碼編譯不過時,繼續用 Semgrep,不要一上來就上 CodeQL

推薦順序是:ripgrep 偵察 → Semgrep 迭代模式 → CodeQL 做深層數據流。官方把「我只用 CodeQL」寫成反模式:前面那些快的一輪,是在告訴你 CodeQL 該瞄準哪裏。

倉庫裏還帶了現成模板。CodeQL 在 resources/codeql/,Semgrep 在 resources/semgrep/,都覆蓋 Python、JavaScript、Java、Go、C++。

抽象階梯:從原句爬到安全屬性

模式有不同抽象層級。官方用一段 SQL 拼接作爲校準例子。

原始代碼:

query = "SELECT * FROM users WHERE id=" + request.args.get('id')

Level 0 是字面匹配,用來證明你理解的就是這一處:

rg 'SELECT \* FROM users WHERE id=" \+ request\.args\.get'

這一層匹配數應當是 1,假陽性爲 0。它不是搜索,是校準點。

Level 1 把變量名換成元變量,用來找複製粘貼變體:

pattern: $QUERY = "SELECT * FROM users WHERE id=" + $INPUT

Level 2 放寬結構,例如「任意字符串拼接,並且用在 cursor.execute 裏」,命中會到十幾、幾十條,假陽性開始出現。

Level 3 抽象到安全屬性本身,用污點模式:源是 request.args.get / request.form.get,匯是 cursor.execute。覆蓋最全,假陽性也最高,必須分診。

目標不同,停在哪一層也不同:驗證某次修復用 Level 0,找複製粘貼用 Level 1,審一個組件用 Level 2,做整倉安全評估才爬到 Level 3。變體搜查裏,假陽性超過大約 50% 就該停;如果規則要進 CI 攔截提交,官方給的可接受假陽性更嚴,大約要低於 5%。

搜查範圍必須是整個倉庫根目錄,不要只搜發現原 Bug 的那個模塊。官方把「只在 api/handlers/ 裏搜、漏掉 utils/auth.py」寫成最常見的失敗原因。

安裝與啓用

Trail of Bits 把這套東西同時做成了 Claude Code 插件和一份可移植的 Skill。安裝方式按工具分開看。

1、Claude Code(官方市場)

先加市場,再裝插件:

/plugin marketplace add trailofbits/skills

然後可以用菜單選,或者直接裝這個插件:

/plugin install trailofbits/skills/plugins/variant-analysis

裝上之後可以使用工作流命令 /variant-analysis:variants。參數按 JSON 對象傳,不要寫成一段散文。bug 必填,建議帶上 文件:行號root 默認當前目錄;lang 是主語言,用來選工具;out 是報告路徑,默認 variant-analysis-report.md

{"bug": "api/auth.py:42 把布爾值和 token 字符串做相等比較", "root": ".", "lang": "python", "out": "variant-analysis-report.md"}

插件 README 寫過:主語言源文件大約不到 40 個(排除 vendored 和測試夾具)時,工作流會收窄、只掃一輪,因爲這個規模下並行展開沒有收益。代碼庫大、根因表現又多時,它會按擴展軸起並行子代理,循環到不再發現新東西爲止。

2、Codex

官方 README 寫明 Codex 可以直接加載 Claude 插件市場:

codex plugin marketplace add trailofbits/skills
codex plugin list
codex plugin add variant-analysis@trailofbits

3、Cursor 以及其他支持 SKILL.md 的工具

把完整 Skill 目錄放到工具會掃描的位置,不要只拷一份 SKILL.mdreferences/resources/ 是五步策略和規則模板,缺了 Agent 只能靠主文件裏的摘要工作。Cursor 會從項目級 .cursor/skills/.agents/skills/,以及用戶級 ~/.cursor/skills/~/.agents/skills/ 加載;爲了兼容,它也會讀 .claude/skills/.codex/skills/

項目內目錄結構類似:

.cursor/skills/variant-analysis/
  SKILL.md
  references/
  resources/

officialskills.sh 給出的安裝命令是:

npx skills add https://github.com/trailofbits/skills --skill variant-analysis

裝好後,在對話裏直接問「還有沒有同類」就會觸發;也可以用 /variant-analysis 顯式調用。/variant-analysis:variants 這條帶命名空間的工作流是 Claude Code 插件命令,Cursor 裏一般不會出現同名斜槓命令。

典型用法

官方給過一個鑑權判斷的例子,流程可以直接照着跑。

假設在 api/auth.py:42 看到:

if user.isAuthenticated == request.token:
    return allow_access()

根因不是「這行寫得難看」,而是:布爾值和字符串比較恆爲假,邏輯被寫反了。

先做 Level 0 校準:

rg "user.isAuthenticated == request.token"

確認只有原處一命中之後,把對象換成元變量:

pattern: $USER.isAuthenticated == $INPUT

如果代碼庫裏鑑權屬性不止這一個,再沿「語義相近的標識符」往外擴,把 isActiveisVerified 等實際存在的名字加進去,而不是憑空列一份詞典。每擴一次都要重新跑、把新命中看完。

對話裏不必先寫規則。Skill 的觸發描述裏寫了,像「are there others like this?」「is this the same bug?」這種問法就會用它。你可以這樣說:

剛纔在 api/auth.py:42 確認 user.isAuthenticated == request.token 這個判斷是錯的。
請按 variant-analysis 做一次變體搜查:先寫根因陳述,再用精確模式校準,
然後一次只放寬一個條件搜整個倉庫,最後按模板寫出報告。

大倉庫、在 Claude Code 裏已經裝了插件時,改用工作流,讓它按擴展軸並行掃。

報告至少要留下這些內容:根因陳述、試過的模式(層級、工具、命中數、真陽性、假陽性)、已確認變體(位置、嚴重程度、狀態)、以及按原因歸組的假陽性。最後用命中最多變體的那條模式,整理成可以放進 CI 的 Semgrep 或 CodeQL 規則。

適用場景與注意事項

適合已經有一個錨點的人:安全工程師修完一處漏洞要掃迴歸,開發者剛合入 CVE 補丁想看 fork 出來的路徑有沒有漏網,或者要把已知 XSS / 注入寫成倉庫級規則。它不替代第一次審計,也不替代修復方案的撰寫。

官方把搜查失敗的原因寫得很具體,這幾條值得對着自查:

  1. 範圍太窄:只搜原 Bug 所在模塊。變體出現在別的目錄纔是常態。
  2. 模式太死:只搜原來那個屬性名。isAuthenticated 出問題,isActive / isAdmin / isVerified 往往是同一類檢查。
  3. 只追一種漏洞形態:根因是「條件爲假時卻放行」,它還可能表現爲空值相等繞過、文檔與實現相反、條件寫反。
  4. 只測快樂路徑:沒拿 null、空集合、未登錄用戶去壓。兩邊都是 None== 爲真,是授權繞過裏很常見的一種。
  5. 泛化太快:一次改多個元素,噪聲無法歸因。

分診時還有兩個官方強調的邊角。一是文檔和實現相反:函數名或 docstring 寫着 DENY,返回值卻在授權成功時給了 True,這種函數的每個調用點都可能是發現。二是不要把「當前沒有調用方」直接判成誤報,標低嚴重程度即可。

相關 Skill 也在同一個市場裏:做深層過程間分析可以配合 codeql,簡單模式配合 semgrep,處理掃描結果配合 sarif-parsing。代碼還沒看懂時,先用 audit-context-building

小結

variant-analysis 做的事情很單一:你已經有一個 Bug,它幫你按根因把整倉同類找出來,並把搜查過程寫成可複查的報告和 CI 規則。方法本身並不新,安全工程師一直在做;寫成 Skill 之後,開發者也可以在修完一處問題後,讓 Agent 按同一套步驟把漏網的複製粘貼和平行實現掃一遍。

官方地址:

  • Skill:https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis/skills/variant-analysis
  • 插件說明:https://github.com/trailofbits/skills/tree/main/plugins/variant-analysis
  • 技能市場:https://github.com/trailofbits/skills
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜