《安全程式碼審查解析》標題卡,顯示一個程式碼差異面板,其中包含一行原始內容與一行變更內容,下方有三個標示階段:模式(確定性)、通過、推理(模型)。
Guides & Insights

安全程式碼審查詳解:審查者在 Diff 中會注意什麼,以及背後原因

作者

Rowan Sterling

發佈日期

最新模型 · 20查看全部模型 →
基準測試:Artificial Analysis · 每日更新
返回全部文章

安全程式碼審查是刻意閱讀原始碼,目的很明確:找出能讓程式碼做出作者原意之外行為的方式。這個定義聽起來理所當然,直到你把它和大多數團隊實際執行的做法相比——那其實是附帶安全檢查清單的功能審查。OWASP Secure Code Review Cheat Sheet 精確劃清了界線:安全審查與功能審查的不同之處,在於它針對的是輸入驗證、身分驗證、授權、密碼學實作與攻擊向量,而不是正確性。一份運作完美的差異(diff)仍可能讓攻擊者取得進入系統的路徑,而這兩種審查中只有一種會去找出這條路徑。

這個主題之所以現在值得寫成文章、而非2020年,是因為語言模型能夠勝任、低成本且持續地讀懂一份差異(diff)——這改變了審查的成本效益,卻完全沒有改變任何一個被審查的面向。這份檢查清單比模型更早存在,而且始終沒變。變的是你負擔得起多常執行它。

審稿人真正在閱讀中尋找的是什麼

OWASP 速查表將工作組織成六個領域,而這份清單的實用之處在於:每個領域都是一個問題,而不是一個類別。類別會招來敷衍打勾的心態;問題則能產生實質發現。

Card 'What a reviewer is actually looking for' listing six dimensions - input and injection, authn and session, authorization, cryptography, business logic, configuration and secrets - each with the question it answers about a diff.

輸入與注入。 問題不在於程式碼是否有清理機制;真正的問題在於,是否有任何東西會到達解譯器——shell、SQL、樣板引擎、運算式求值器——以及這項要求應該在哪一層被強制執行。一個加入參數化查詢的 diff 是好的;但若把參數化查詢加進一個其他呼叫者會傳入原始字串的函式裡,這樣的 diff 只是把問題搬了位置。

身分驗證與工作階段。呼叫者是誰,程式碼又是如何確認的?反覆出現的錯誤不是密碼檢查失效,而是工作階段在權限變更後仍然存續——權限提升了,權杖卻保留舊的宣告,一小時後這些權限依然存在。

授權。每一次、在每一條路由上,都要做物件層級的檢查。在 diff 中要特別留意的具體情況,是新增了一條沒有繼承其同層路由所擁有之守衛(guard)的路由,因為中介軟體是依路由器(router)分別註冊的,而掛在不同路由器底下的路由就是不會被涵蓋到。

密碼學。基本演算法、運作模式、金鑰長度,以及金鑰存放的位置。有兩個問題能揪出大多數真正的問題:任何能讀取這份程式碼庫的人,是否也能讀到金鑰;以及這個比較是否屬於絕不能洩漏時序資訊的那種比較。

業務邏輯。序列是否能被重新排序、重播,或由用戶端所選的值所驅動。價格、數量、角色或狀態轉換若是來自用戶端,而未在伺服器端重新推導,無論其如何被驗證,都算是一項發現。

設定與機密。diff 帶進了哪些根本不該出現在儲存庫裡的內容,以及預設值在程式實際執行的環境中是否安全。除錯旗標預設為 true,這是一項關於生產環境的發現,而不是關於該旗標本身的發現。

基線檢視與差異檢視的比較

OWASP 區分兩種模式,而將兩者混為一談,往往既浪費心力又帶來虛假信心。基線審查會全面檢視程式碼庫,適合用於新應用程式或重大版本、接手舊有系統、滿足合規要求,或在問題已經發生後進行事故後分析。差異式審查只檢視變更的內容,適合用於提取請求、提交、功能完成與持續驗證。

這個區別很重要,因為兩者產出的成果不同。基線審查以一份待辦清單收尾;差異審查則以一個合併決定收尾。持續執行第一種在成本上難以負擔,而在一個從未做過第一種的程式碼庫上執行第二種,正是團隊累積虛假覆蓋感的方式:每一項變更都通過審查,而既有的 SQL 字串拼接卻原封不動地擺在那裡。

Screenshot of the OWASP Cheat Sheet Series index entry for Secure Code Review, showing the introduction describing manual examination of source code for vulnerabilities automated tools miss, the baseline-versus-diff review split with their use cases, and a table of contents covering methodology, patterns, techniques, checklists and tools.

技巧,以及每一項各自能捕捉到什麼

OWASP 列出四種審查技術,而區分它們的是審查者依據什麼證據,而不是它們有多徹底。 程式碼模式分析會追隨熟悉的形式:危險的函式呼叫、不受信任輸入的反序列化、缺少長度檢查。它很快,而且這正是確定性工具擅長的事。 資料流分析會追蹤一個值從進入點到使用點,這是回答在三個函式之前套用的清理器是否仍適用於此處的唯一方法。 以威脅為基礎的審查從攻擊者的能力出發並向前推演,這就是你如何捕捉兩個各自正確的元件之間的缺口。 業務邏輯審查讀的是工作流程而非程式碼行,追問什麼樣的合法操作序列會產生不合法的狀態。

那四項中,只有第一項適合用掃描器處理;而只有第一項和第二項適合用無引導的模型處理。這並不是反對模型,而是在主張要告訴審查者你想要哪種技術,因為要求找出「安全問題」的提示,會穩定地產生模式分析後就停止。

AI 改變了什麼,又沒有改變什麼

關於 AI 撰寫程式碼的研究,讓審查瓶頸變得更嚴峻,而不是創造出這個瓶頸。2022 年發表於 IEEE S&P 的 NYU 針對 GitHub Copilot 建議所進行的研究發現,約 40% 的生成片段在 18 個 CWE 類別中含有可利用的模式。2023 年發表於 ACM CCS 的史丹佛開發者行為研究,則發現了影響更深遠的效應:使用輔助工具的開發者對其程式碼安全更有信心,卻產出在可量測程度上更不安全的程式碼。更多產出加上更高信心,就是審查問題。

以語言模型作為審查者,會改變該方程式中的一個變數——吞吐量。它能閱讀每一個 pull request,成本按 token 計而非按席位計,並且可以明確地詢問上述六個問題。它不會改變這些問題,也不會獲得讓審查值得信賴的兩項特性:確定性與執行程式碼的能力。同一份 diff 審查兩次可能產生不同的發現,嚴重程度會在多次執行之間變動,而審查者無法證明被標記的路徑可達。

Screenshot of the OrcaCode Review product page on OrcaRouter headed 'Open engine. Open prompt. Open bill.', describing a pull-request reviewer whose rules and merge gate run on GitHub under an MIT licence and are billed per token, with an example finding about an idempotency key that still varies in time.

OrcaCode Review是這個構想的產品化形式,值得仔細檢視,因為其中的設計取捨揭示了各種失效模式。一份能把失敗案例孤立成具體、可重現評論的審查——這把金鑰仍會隨時間變動,因此重試的扣款會鑄造出新金鑰,可能導致重複扣款,而這裡只需改一行——正是在做審查者該做的事。一份只回報「潛在安全問題」卻沒有重現步驟的審查,是在做模式分析,而精確度還不如模式比對器。至於以開放授權發布的規則與閘門引擎,則是在提出一項值得檢驗的隱含主張:審查者的判斷也應該可被審查。

有效的雙層配置

在確定性掃描器與機率性審查器並存的情況下,實務上能長久運作的安排,是讓各自負責它真正擅長的工作。掃描器在每次推送時執行:只需數秒、不靠模型、沒有變異,能捕捉機密、危險的 API 呼叫、不安全的反序列化與模型幻想出的匯入。審查器則在每個拉取請求時執行:需數分鐘、由模型支援,能捕捉字裡行間的邏輯——授權檢查是否在抓取之前執行、冪等鍵是否綁定到穩定值、新路由是否繼承了防護。

接著是大家常跳過的那一層:關卡。不會阻擋合併的檢查發現只是通知,而通知都要到週五才有人看。關卡必須範圍窄,而且必須絕對——重大發現就阻擋,其餘一律只留評論——豁免也必須白紙黑字寫下來,因為關卡的價值不在於擋下第一個發現,而在於讓每一次繞過都看得見。

審查這次變更,而非整個程式碼庫

diff 審查的實務紀律是一組簡短步驟,其價值在於它可以在一個下午內教會。閱讀 diff 時,要看它新增了哪些攻擊面,而不是看它改變了什麼。對每個新的進入點,沿著輸入追過 diff 結束的位置再往前一跳——漏洞通常就在它呼叫的函式裡,而作者並沒有讀過那個函式。對每條新路由,檢查守衛註冊。對每個新相依套件,檢查它是否存在,以及這個名稱是否已被別人發布。對每個新設定值,問它在實際部署的位置會預設成什麼。

這些都不需要精通該語言,因此這種做法可以擴及到非安全專家的審查者。但它確實要求審查者查看差異(diff),而不是查看整個檔案,因為整套方法取決於知道哪些內容是新增的。

而誠實的限制是:diff 審查無法找出在 diff 之前就已存在的瑕疵。如果應用程式從未做過基準審查,那麼現有最有價值的資安工作,並不是找更優秀的審查者——而是針對程式碼庫中涉及金錢、身分與檔案存取的部分,帶著上述六個問題,做一次刻意的通盤檢視。在那之後,每一次後續的 diff 審查,都是在保護已經被看過一次的東西。在那之前,每一次 diff 審查,都是在保護一個未知的東西。