前五天測的都是「產出品質」型的 skill——寫得好不好、抓得準不準、記得住記不住,錯了頂多重來一次。今天測的是 Trail of Bits——一間在資安圈子裡份量很重的研究公司,主業是幫別人的程式碼、區塊鏈協議、加密系統做稽核,平常的工作就是產出公開或半公開的稽核報告,找出真實系統裡的漏洞。他們把自己稽核工作裡會用到的自動化方法包成一組 Claude Code 的 skill,公開放在 github.com/trailofbits/skills。這種背景的公司願意把內部方法公開,本身是一個值得測的訊號:如果連他們包的東西都測不出什麼名堂,大概率不是他們的問題,是這條路本身有限制;如果測出真的有用或有缺口的東西,這個結論的份量也比隨便一個不知名作者的套件重。這是這系列第一次測資安類的 skill,跟前五天測過的東西比起來,這類工具的失敗方式不一樣:它不是「答案普通」,是「你以為安全,其實不安全」,中間那個落差平常沒人會提醒你,只有真的出事那天才會發現。如果一份掃描報告給你錯誤的安全感,那比它什麼都不說更危險,因為你會停止懷疑、停止繼續找其他方法把關。這種東西值不值得信任,比前五天測過的任何一個都更需要老實查證。
static-analysis,Trail of Bits 官方出品,這次測的是裡面的 semgrep 子技能(同系列還有 semgrep-rule-creator、property-based-testing 等,今天沒測)github.com/trailofbits/skills,公開倉庫;底層引擎是開源的 Semgrep,不是 Trail of Bits 自己寫的分析引擎p/security-audit、p/secrets)+ 語言框架規則(p/python、p/flask)+ Trail of Bits 自己維護的專屬規則庫 + 額外拉進其他資安公司的規則(這次實際看到 elttam、Apiiro——elttam 是澳洲一間做滲透測試與程式碼審查的資安顧問公司,Apiiro 是做應用程式安全風險管理的公司,都不是無名小廠)check_id,全部規則集掃完後會合併成一份 SARIF 報告、自動去重,可以直接接 CISemgrep 這類工具不是真的理解你的程式在做什麼,是拿一大套規則庫去比對程式碼的語法結構(有分析語法樹,不是單純字串比對),看有沒有符合已知危險模式的寫法。規則庫按語言、框架分類,每條規則都有一個 check_id,對應到一份說明「這個模式為什麼危險、怎麼修」。這種設計可重複、可自動化——同一段程式碼,不管誰在什麼時候跑,只要規則庫版本一樣,結果就一樣;但也因此它只認得規則庫裡寫過的模式,任何沒收錄的問題類型,不管多明顯,它都看不見。Trail of Bits 這個 skill 在這個基礎上疊了一層:自動判斷專案用什麼語言、該套哪些規則集,還拉進自己跟其他資安公司的規則庫——等於把「自己挑規則集」這件事自動化了,但疊再多層規則庫,語法比對這個方法本身的能力邊界不會變。
再往下一層講,Semgrep 抓到 SQL injection 這類問題,靠的是一種叫「污染追蹤」(taint tracking)的技術:先標出程式裡哪些資料是「來源」(source)——通常是使用者輸入,像網址參數、表單欄位;再標出哪些操作是「終點」(sink)——像直接執行 SQL、直接寫進檔案系統指令;只要有一條路徑能從來源一路不經過任何清洗(sanitize)就走到終點,就判定為危險。這個機制的好處是不會只看單一行程式碼,能追一小段資料流;壞處是它的「路徑」只能在單一檔案裡(沒有付費升級的跨檔功能),而且它認得的「終點」一樣是照規則庫寫死的清單——執行 SQL 是終點,但「把資料回傳給沒有驗證身份的呼叫者」,這種行為在它的世界裡不算是需要警戒的終點,因為規則庫裡沒人告訴它這也該算。
兩個檔案都是很短的 Flask 小應用,只有一個 /user 路由:一個直接用字串拼接組 SQL 查詢,是教科書等級的 SQL injection;另一個功能一樣,但改用參數化查詢,把這個洞補起來了。選 SQL injection 當主題,是因為它是這類工具最擅長、也最經典的偵測目標,如果連這種都測不出差異,後面不用再往下測。第二個案例刻意不做成「完全沒問題的乾淨程式碼」,而是「修好了一個已知的洞,但可能還藏著別的問題」——現實世界很少有真正完美無瑕的程式碼,比較常見的狀況是修好一個漏洞後大家鬆一口氣,卻沒注意到旁邊還有別的問題。兩個案例都各跑一次完整的官方 skill 流程,另外派一組不用任何工具、單純讀程式碼的盲測基線當對照。
有 SQL injection 的那個案例,官方 skill 跑完五組規則集,合併去重後留下 1 筆發現,指向那行字串拼接組出來的 SQL 查詢,判定為 ERROR 等級,建議改用參數化查詢。盲測基線也抓到同一個問題,講法一樣具體。這輪兩邊都做對了。
SQL injection 之所以是這類工具最擅長的題目,是因為它的危險模式非常固定——使用者輸入透過字串串接直接進到執行 SQL 的函式,這條路徑幾十年來就是那幾種寫法,規則庫很容易寫得又準又全。這也是為什麼它明明是老掉牙的問題,規則庫抓得又快又準,卻還是三不五時出現在真實系統裡:不是工具抓不到,是很多程式碼根本沒被這類工具掃過,或者掃過但沒人去看報告、去修。工具擅不擅長是一回事,有沒有真的被用起來、有沒有人跟進處理,是另一回事,這點在讀今天這篇的時候也值得放在心上。
真正值得記的細節是:p/security-audit——這個名字聽起來最像「全面資安檢查」的規則集——這次是 0 命中,真正抓到問題的是語言框架專屬的 p/python 跟 p/flask。這代表挑規則集本身就有學問,「聽起來最全面」不等於「涵蓋最廣」,框架專屬的規則集反而常常是抓到真正問題的那一組。這個結果被驗證了兩次(先手動重建一次,再跑正式官方流程一次),兩次一致,不是抽樣誤差。
另外規則本身標的分類是 CWE-704,跟這個問題實際對應的 CWE-89(SQL Injection)對不太上——工具的 metadata 本身也不是百分之百精確,值得養成核對的習慣,不要照單全收。
這裡順便解釋一下規則集命名背後的邏輯,知道這個之後你自己選規則集會更有底。Semgrep 生態裡的規則集分成幾種層次:像 p/security-audit、p/secrets 這種是官方維護的「泛用」規則集,涵蓋很廣但不特別深入任何一種框架的細節;p/python、p/flask 這類是照語言或框架分的「專項」規則集,規模比較小,但因為只需要照顧一種生態,寫得比較深入、貼近該框架真實常見的用法;再上一層則是像 Trail of Bits 這樣的資安公司自己維護的「專屬」規則庫,通常是他們在真實稽核案子裡踩過的坑、抓過的漏洞類型,反過來寫成規則。三者不是誰取代誰的關係,是覆蓋範圍不同、互補的關係——今天的結果剛好示範了「泛用規則集不一定涵蓋所有框架的細節,專項規則集反而可能更準」這件事,不能單純假設「規則越多、規則集越大」就等於「掃得越完整」。
第二個案例(已經修好 SQL injection)跑完,七組掃描全部成功,唯一的發現是一則 INFO 等級的路由入口標記,不是漏洞。沒有誤判成有問題,這點是對的。
但同一個檔案,不用任何工具的盲測基線多抓到一件事:
「完全沒有身份驗證/授權(Broken Access Control)——
/user?username=xxx任何人都能直接查任何一個使用者的 email,沒有登入檢查、沒有『只能查自己』的限制。這是這支程式最大的風險:等於一個公開的使用者資料查詢端點。」
它還往下推了一層,指出這會導致使用者列舉(拿帳號字典去掃、靠回應有無差異確認哪些帳號存在)跟 PII 外洩的複合風險。
官方 skill 完全沒有提到這件事,即使動用了 Trail of Bits 自己的規則庫加上兩家第三方資安公司的規則,七組掃描沒有一組碰到這個問題。 這不是案例設計得特別刁鑽才踩到的例外——語法比對這類工具的規則庫是照著已知的程式碼層級漏洞模式寫的,「這支 API 沒有檢查呼叫者是誰」不是一個語法模式,是一個要理解「這支程式在做什麼、誰應該能呼叫它」才看得出來的商業邏輯問題,靜態分析工具沒有這個上下文,自然抓不到。連 skill 作者自己都清楚這條界線,文件裡明講某些情境要改用別的工具,代表這個方法的能力邊界從一開始就是已知的,不是這次測試才發現的意外。
這裡有一個諷刺的地方,值得多講一句:業界公認最權威的資安風險排行榜 OWASP Top 10,最新一版把「權限控管缺失」(Broken Access Control)排在第一位,注入類攻擊(包含 SQL injection)反而排到第三——也就是說,今天靜態分析工具抓得又快又準的那個問題,在真實世界的統計上,還不是最常見、危害最大的那一類;而它完全看不到的那類問題,反而是統計上排名最高的風險。這不代表 SQL injection 不重要,而是提醒你:一套工具能不能把你最該擔心的問題排在最前面,跟它掃描起來準不準、報告做得多漂亮,是兩回事。
把這件事放進更大的脈絡看會更清楚。業界通常把程式碼安全檢查分成幾種不同的方法,各自照顧不同的層面:SAST(Static Application Security Testing,今天測的這類)在不執行程式的情況下比對原始碼;DAST(Dynamic Application Security Testing)實際把程式跑起來、像攻擊者一樣送真實請求去戳,能抓到 SAST 看不到的執行期問題,包含一部分權限控管缺失;人工滲透測試與架構審查則是唯一真正會問「這個系統的權限模型設計得合不合理」這種問題的方法。Trail of Bits 自己身為一間資安顧問公司,正職做的其實是後面這兩種——今天測的 skill 只是他們把 SAST 這一塊自動化、公開釋出的部分,不是他們完整服務的全貌。理解這個定位,才不會誤以為裝了一個資安 skill,就等於把資安顧問公司的完整服務都請回家了。
這三種方法的成本結構也不一樣,值得放在一起比較:SAST 最便宜、最快、可以無限次重複跑,適合接進日常的開發流程當第一道防線;DAST 需要一個真的能跑起來的環境,成本高一截,通常排在上線前的固定檢查點;人工審查最貴、最慢,但也是唯一能問出「這個系統的權限模型合不合理」這種問題的方法,通常只在關鍵系統或大改版時才會動用。把 SAST 掃描工具當成免費、隨時可用的第一道濾網是合理的用法,但如果因為裝了這道濾網就跳過後面兩層,等於是把整個安全策略押在它看得到的那個範圍裡,這才是今天最想提醒的事。
也有一個小地方值得記:這次拉進的第三方規則庫裡,有些規則是針對 Java、JavaScript 這類其他語言寫的,掃這個純 Python 專案時直接編譯失敗被跳過。官方輸出裡有標記,但不會主動跳出來提醒你「這部分沒真的測到」,代表「拉了幾家知名資安公司的規則庫」不等於「每一條規則都真的對你的專案有效」,語言對不上的規則只是安靜地被跳過。
如果你以後也想拿靜態分析工具幫程式碼把關,今天這個反差值得記住:它擅長的是「這段語法本身有沒有已知的危險模式」,不擅長「這支程式的存取邏輯合不合理」。 這兩類問題不是同一個工具能一次顧到的,就算背後掛了好幾家知名資安公司的規則庫也一樣。換個角度講,跑完這類工具看到「0 個發現」,比較安全的解讀是「已知的程式碼層級危險模式都沒踩到」,不是「這支程式很安全」——這兩句話聽起來很像,差別卻很大,今天的案例二就是活生生的例子:0 個漏洞發現,但這支 API 讓任何人都能查到別人的 email,兩件事同時成立,完全不矛盾。
具體一點,可以問自己兩層完全不同的問題:第一層是「這段寫法本身安不安全」——字串有沒有正確跳脫、有沒有用參數化查詢——這一層丟給這類工具去顧,效果穩定又划算;第二層是「這個功能存在的意義是什麼、誰應該摸得到它」——這一層工具幫不上忙,得靠人自己想清楚,或找真的懂系統業務邏輯的人看過一遍。混著問,兩層都顧不好。
幾個更實務的提醒:
check_id 或分類標籤,順手核對一下。 標錯分類的情況是存在的,工具的自我描述不是絕對可信的。這系列每天測的都是不同的 skill,但回頭看,一直反覆出現同一個結構:某個東西講得很篤定、很完整,但它篤定的範圍是有邊界的,邊界外面的事它完全不知道自己不知道。Day 4 是模型對訓練資料截止後的新寫法一無所知;Day 5 是通用建議不知道有現成模式可以直接沿用;今天是連掛了好幾家知名資安公司規則庫的官方掃描工具,也完全看不到一個商業邏輯層級的權限漏洞。三次的盲區位置都不一樣,但形狀是一樣的:工具或模型不會主動告訴你它看不到什麼,只會安靜地在它看得到的範圍內做到最好。回頭想想前五天測過的每一個 skill,其實都可以問同一句話:它講得很有自信的那個範圍之外,還有什麼是它看不到、但你需要知道的?資安這個領域把這件事講得特別清楚,因為它的盲區有真實代價——一份程式碼審查建議漏掉一個問題,頂多是少了一個優化機會;一份資安掃描報告漏掉一個問題,代價是系統真的被打穿。今天的教訓沒有比前幾天更特別,只是後果比較重。
今天真正的收穫不是「Trail of Bits 這套工具能不能用」——它在自己擅長的範圍內表現得很扎實,抓到的 SQL injection 判斷正確、也沒有對乾淨的程式碼誤判,這點無庸置疑——是任何一種自動化檢查工具都有它看得到跟看不到的範圍,靠單一工具把關永遠會有一塊盲區,這塊盲區不會自己舉手說「這裡我漏看了」,只有拿別的方法去對照,才會知道它在哪裡。如果你只跑過這類工具、報告顯示乾淨,千萬別把這個結果當成「這支系統很安全」的證明——它只證明了「這支系統沒有踩到這套工具認得的那些模式」,這中間的距離,可能就是下一個真實漏洞藏身的地方。
明天想換一個完全不同的方向測,這系列接下來還是每篇一個獨立的技能卡,累積起來變成一份你自己就能查的 skill 選用清單。