Cross-Site Request Forgery Attacks#
概述#
Cross-Site Request Forgery (CSRF 或 XSRF),又稱「sea-surf」,是指攻擊者誘騙使用者點擊惡意連結,觸發使用者不預期的副作用操作。由於你的網站有公開 URL,其他網站可以連結到你的網站,而並非所有連結都出於善意。
CSRF 是極為常見的漏洞,大多數主要網站都曾受到影響。攻擊者曾利用 CSRF:
- 竊取 Gmail 聯絡人清單
- 觸發 Amazon 的一鍵購買
- 修改路由器設定
CSRF 攻擊剖析#
CSRF 攻擊通常利用會改變伺服器狀態的 GET request。GET request 在使用者點擊連結時觸發,且請求的全部內容都包含在 URL 中,因此特別容易被利用。
Twitter 蠕蟲案例#
早期的 Twitter 允許透過 GET request 發推文(現在改用 POST),這使得 CSRF 攻擊成為可能:
https://twitter.com/share/update?status=in%20ur%20twitter%20CSRF-ing%20ur%20tweets一個聰明的駭客利用這個漏洞創造了一個病毒式蠕蟲:
- 建構一個惡意連結,點擊後會發布一則包含不雅內容的推文,推文中同時包含同一個惡意連結
- 誘騙少數受害者點擊
- 這些受害者的推文出現在他們的時間線上
- 其他使用者好奇點擊後,也被迫發布同樣的推文
- 很快數萬名 Twitter 使用者都被迫發布了該內容
這就是第一個 Twitter 蠕蟲的誕生過程。Twitter 開發團隊緊急修補了這個安全漏洞。
緩解措施 1:遵循 REST 原則#
防禦 CSRF 的第一步是確保 GET request 不會改變伺服器狀態。
Representational State Transfer (REST) 原則要求將網站操作對應到適當的 HTTP method:
| HTTP Method | 用途 |
|---|---|
GET | 取得資料或頁面 |
PUT | 建立新物件(如留言、上傳) |
POST | 修改現有物件 |
DELETE | 刪除物件 |
關鍵是不要將改變伺服器狀態的操作指派給 GET request。登入、登出、修改密碼、發文、關閉帳號等操作都應該使用
PUT、POST或DELETE。
保護 GET request 可以阻擋大部分 CSRF 攻擊,但其他 HTTP verb 仍然存在漏洞,因此需要第二層防禦。
緩解措施 2:實作 Anti-CSRF Cookies#
即使 GET request 安全了,攻擊者仍可能誘騙使用者透過第三方網站上的惡意表單或腳本發起 POST request。為此需要使用 anti-CSRF cookie。
運作原理#
Anti-CSRF cookie 是一個由 web server 產生的隨機字串 token:
- 伺服器在 HTTP response 中設定 cookie:
Set-Cookie: _xsrf=5978e29d4ef434a1 - 網站的每個 HTML 表單中嵌入相同的 token 作為 hidden field:
<input type="hidden" name="_xsrf" value="5978e29d4ef434a1"> - 使用者提交表單時,伺服器比對表單中的
_xsrf值與 cookie 中的值 - 如果不匹配,伺服器拒絕該請求
由於瀏覽器的 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(
PUT和DELETE)。JavaScript 需要從 HTML 中取得 anti-CSRF token,並附加在 HTTP request 中回傳給伺服器。
緩解措施 3:使用 SameSite Cookie 屬性#
預設情況下,瀏覽器會在發送請求時附上目標網站先前設定的所有 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 請求),前提是你的GETrequest 不會產生副作用。
額外防禦:要求敏感操作重新驗證身份#
Reauthentication(重新驗證)是指在執行敏感操作(如修改密碼、發起付款)前,要求使用者重新輸入登入憑證。
這個做法的好處:
- 為 CSRF 攻擊增加額外的防禦層
- 給使用者明確的提示,表示即將執行重要且可能危險的操作
- 保護在共用裝置或被竊裝置上仍保持登入狀態的使用者
如果你的網站處理金融交易或機密資料,強烈建議在使用者執行敏感操作時要求重新輸入憑證。
總結#
防禦 CSRF 攻擊的三道防線:
- 遵循 REST 原則:確保
GETrequest 無副作用(side-effect free),不改變伺服器狀態 - 使用 anti-CSRF cookies:透過隨機 token 驗證
POST/PUT/DELETE請求確實來自你的網站 - 設定 SameSite cookie 屬性:阻止瀏覽器在跨站請求中發送 cookie
對於特別敏感的操作,額外要求使用者重新驗證身份,既能加強 CSRF 防禦,也能保護在共用或被竊裝置上已登入的使用者。