Cross-Site Request Forgery
Cross-site request forgery 的核心概念是攻擊者從自己控制的頁面誘發一個指向目標服務的請求,由受害者的瀏覽器依網域規則自動附上憑證,於是那個請求以受害者的身分執行。攻擊者全程讀不到憑證,也讀不到回應——same-origin policy 擋住讀取這一步——因此他的能力限於發動寫入:改密碼、改綁定信箱、送出一筆交易、刪除資料。這個機制成立的唯一前提是伺服器把「瀏覽器附上了憑證」當成授權依據。
概念位置
它與 cross-site scripting 的關係是能力包含而非並列。能在目標頁面上執行程式碼的攻擊者,做得到這裡的全部動作,還額外讀得到回應與同源儲存;反過來,只能誘發請求的攻擊者拿不到任何資料。兩者的門檻因此也不同——誘發請求只需要受害者載入一個頁面或點開一封信,而執行程式碼要先有一個注入點。
它的存在條件由憑證怎麼被帶上決定,這個判斷怎麼做、以及各種組態的差別見 7.36 憑證在請求中怎麼帶。憑證改由程式碼明確寫進請求標頭時這個機制不成立,因為攻擊者的頁面誘發不出一個帶著正確標頭的請求;代價是憑證移到 JavaScript 讀得到的位置,暴露面換成跨站指令碼那一類。伺服器對伺服器的呼叫沒有自動附上憑證的機制,這個概念在那一側不適用,判讀走 7.29 API 認證的信任邊界分層。
可觀察訊號與例子
需要正視這個攻擊面的訊號是系統用 cookie 承載登入狀態,而沒有人指得出這層防護是誰決定的。SameSite 是限制 cookie 在跨站請求中送出的 cookie 屬性,取值 Lax 時只在頂層導覽的 GET 上放行。Chromium 系的瀏覽器把沒有標示這個屬性的 cookie 當成 Lax,而 Firefox 與 Safari 沒有跟進;於是這一層的涵蓋範圍由使用者手上是哪個瀏覽器決定,而「已經有保護」與「有人評估過保護到哪裡」在外觀上完全相同——掃描不回報問題,滲透測試也不一定觸發。
三層防護的缺口互不重疊,這是它們要並用而非三選一的理由。SameSite 由瀏覽器執行,缺口在頂層導覽的 GET 請求仍然附上憑證(用 GET 執行寫入的端點因此落在保護之外),以及同一個可註冊網域下的子網域之間不算跨站——「可註冊網域」指 example.com 這一層,a.example.com 與 b.example.com 都算在它裡面。Origin 或 Referer 檢查由伺服器執行,不受前兩個缺口影響,代價是要維護允許清單。頁面帶出、請求送回的額外隨機值(常稱 CSRF token)不依賴瀏覽器的預設值,但它以「攻擊者讀不到這個值」為前提,前提由同源政策提供、由跨站指令碼拿掉。
實務上最常見的失效形態是子網域。活動頁、文件站或外包維護的服務掛在同一個可註冊網域下,其中一個被拿下之後,從它發出的請求對主站不算跨站,SameSite 完全不介入,而主站這一側沒有任何設定變更或異常日誌可以顯示這件事發生了。
設計責任
防護要掛在端點上而非掛在 HTTP 方法上。以方法為條件的實作會在系統裡留下一批用 GET 執行寫入的端點不受保護,而那批端點(一鍵退訂、切換設定、匯出)通常年代久遠、不在任何人的檢查清單上。盤點的做法是列出所有會改變狀態的端點,逐一問它憑什麼認為請求來自自己的頁面。
同一個可註冊網域下的所有主機在 SameSite 的判定裡是一體的,於是信任邊界的實際大小由 cookie 的規則決定、而不是由來源的規則決定。把子網域交給不同團隊或外部廠商維護時,主站的防護因此要包含一層不依賴瀏覽器判定的檢查。
系統同時存在多種帶法時,防護面由仍然靠自動附上的那些端點決定。主要路徑改走請求標頭之後,登入與憑證換發端點通常仍靠 cookie,而它們常因為「已經改成 token 了」這個整體印象而不再被檢視。