Cross-Site Request Forgery Attacks#

概述#

Cross-Site Request Forgery (CSRF 或 XSRF),又稱「sea-surf」,是指攻擊者誘騙使用者點擊惡意連結,觸發使用者不預期的副作用操作。由於你的網站有公開 URL,其他網站可以連結到你的網站,而並非所有連結都出於善意。

CSRF 是極為常見的漏洞,大多數主要網站都曾受到影響。攻擊者曾利用 CSRF:

  • 竊取 Gmail 聯絡人清單
  • 觸發 Amazon 的一鍵購買
  • 修改路由器設定

CSRF 攻擊剖析#

CSRF 攻擊通常利用會改變伺服器狀態的 GET requestGET request 在使用者點擊連結時觸發,且請求的全部內容都包含在 URL 中,因此特別容易被利用。

Twitter 蠕蟲案例#

早期的 Twitter 允許透過 GET request 發推文(現在改用 POST),這使得 CSRF 攻擊成為可能:

https://twitter.com/share/update?status=in%20ur%20twitter%20CSRF-ing%20ur%20tweets

一個聰明的駭客利用這個漏洞創造了一個病毒式蠕蟲

  1. 建構一個惡意連結,點擊後會發布一則包含不雅內容的推文,推文中同時包含同一個惡意連結
  2. 誘騙少數受害者點擊
  3. 這些受害者的推文出現在他們的時間線上
  4. 其他使用者好奇點擊後,也被迫發布同樣的推文
  5. 很快數萬名 Twitter 使用者都被迫發布了該內容

這就是第一個 Twitter 蠕蟲的誕生過程。Twitter 開發團隊緊急修補了這個安全漏洞。

緩解措施 1:遵循 REST 原則#

防禦 CSRF 的第一步是確保 GET request 不會改變伺服器狀態

Representational State Transfer (REST) 原則要求將網站操作對應到適當的 HTTP method:

HTTP Method用途
GET取得資料或頁面
PUT建立新物件(如留言、上傳)
POST修改現有物件
DELETE刪除物件

關鍵是不要將改變伺服器狀態的操作指派給 GET request。登入、登出、修改密碼、發文、關閉帳號等操作都應該使用 PUTPOSTDELETE

保護 GET request 可以阻擋大部分 CSRF 攻擊,但其他 HTTP verb 仍然存在漏洞,因此需要第二層防禦。

緩解措施 2:實作 Anti-CSRF Cookies#

即使 GET request 安全了,攻擊者仍可能誘騙使用者透過第三方網站上的惡意表單或腳本發起 POST request。為此需要使用 anti-CSRF cookie

運作原理#

Anti-CSRF cookie 是一個由 web server 產生的隨機字串 token:

  1. 伺服器在 HTTP response 中設定 cookie:Set-Cookie: _xsrf=5978e29d4ef434a1
  2. 網站的每個 HTML 表單中嵌入相同的 token 作為 hidden field:<input type="hidden" name="_xsrf" value="5978e29d4ef434a1">
  3. 使用者提交表單時,伺服器比對表單中的 _xsrf 值與 cookie 中的值
  4. 如果不匹配,伺服器拒絕該請求

由於瀏覽器的 same-origin policy,cookie 只能由設定它的網域設定和讀取。第三方惡意網站無法取得你的 anti-CSRF token,因此無法建構合法的請求。

以 Python Tornado web server 為例:

<form action="/new_message" method="post">
  {% module xsrf_form_html() %}
  <input type="text" name="message" />
  <input type="submit" value="Post" />
</form>

xsrf_form_html() 函式會產生隨機 token 並以 hidden input 的形式寫入表單。

Anti-CSRF cookie 也應該用於驗證 JavaScript 發出的 HTTP request(PUTDELETE)。JavaScript 需要從 HTML 中取得 anti-CSRF token,並附加在 HTTP request 中回傳給伺服器。

預設情況下,瀏覽器會在發送請求時附上目標網站先前設定的所有 cookie,不管請求來源是哪個網站。這表示來自惡意第三方網站的跨站請求也會攜帶你的安全 cookie。

設定 SameSite 屬性可以告訴瀏覽器在跨站請求時移除 cookie:

SameSite=Strict#

Set-Cookie: _xsrf=5978e29d4ef434a1; SameSite=Strict;

瀏覽器只在來自同一網站的請求中才會發送此 cookie。缺點是從外部連結進入你的網站時也不會發送 cookie,使用者可能需要重新登入。

SameSite=Lax#

Set-Cookie: session_id=82938d911e13f3; SameSite=Lax;

只允許 GET request 從其他網站發送 cookie,POST 等其他類型的請求不會附帶 cookie。

建議在所有 cookie 上設定 SameSite 屬性。對 session cookie 使用 SameSite=Lax 可以兼顧使用者體驗(允許外部連結無縫進入)和安全性(阻擋跨站 POST 請求),前提是你的 GET request 不會產生副作用。

額外防禦:要求敏感操作重新驗證身份#

Reauthentication(重新驗證)是指在執行敏感操作(如修改密碼、發起付款)前,要求使用者重新輸入登入憑證。

這個做法的好處:

  • 為 CSRF 攻擊增加額外的防禦層
  • 給使用者明確的提示,表示即將執行重要且可能危險的操作
  • 保護在共用裝置或被竊裝置上仍保持登入狀態的使用者

如果你的網站處理金融交易或機密資料,強烈建議在使用者執行敏感操作時要求重新輸入憑證。

總結#

防禦 CSRF 攻擊的三道防線:

  1. 遵循 REST 原則:確保 GET request 無副作用(side-effect free),不改變伺服器狀態
  2. 使用 anti-CSRF cookies:透過隨機 token 驗證 POST/PUT/DELETE 請求確實來自你的網站
  3. 設定 SameSite cookie 屬性:阻止瀏覽器在跨站請求中發送 cookie

對於特別敏感的操作,額外要求使用者重新驗證身份,既能加強 CSRF 防禦,也能保護在共用或被竊裝置上已登入的使用者。