如何防範 CSRF (Cross-Site Request Forgery)


Posted by Wes on 2021-03-01

CSRF(Cross-Site Request Forgery)是攻擊者利用受害者的身份,在受害者不知情的狀況下,向伺服器發送惡意請求,例如:發送訊息、購買商品、銀行轉帳等。

與 XSS(Cross-Site Scripting)不同的是,XSS 通常是在網站上輸入惡意程式碼來進行攻擊,利用的是使用者對目標網站的信任;而 CSRF 則是基於網站伺服器對使用者瀏覽器的信任。

攻擊原理

多數的網站在使用者登入後,都會在瀏覽器產生 Cookie,之後的操作就不需要再次登入,瀏覽器會自動將 Cookie 發送給網站伺服器來識別身份。不過,因為 Cookie 會在瀏覽器中存在一段時間,使得攻擊者有機可趁,在 Cookie 過期前,都有遭受 CSRF 攻擊的可能性。

最簡單的攻擊方法,是在 URL 中加上參數。假設使用者在登入部落格的情況下,點擊了攻擊者提供的連結,伺服器就會接收到 GET 請求,將文章刪除:

<a href="<https://example-blog.com/delete?id=4>"></a>

攻擊者也能利用圖片的 src 屬性發送 request

<img src="<https://example-blog.com/delete?id=4>" width="0" height="0">

甚至可以用 <form> 發送 POST 請求,讓表單提交之後的結果出現在一個看不見的 <iframe> 裡面,而且還可以自動提交,完全不需要經過使用者的任何操作。

<iframe style="display:none" name="csrf-frame"></iframe>

<form method="POST" action="<https://example-blog.com/delete>" target="csrf-frame" id="csrf-form">
  <input type="hidden" name="id" value="4">
  <input type="submit" value="submit">
</form>

<script>document.getElementById("csrf-form").submit()</script>

使用者端的防範

CSRF 之所以能夠成立,是因為使用者在被攻擊的網站中,處於登入的狀態,攻擊者才能偽造使用者的身份執行一些行為。因此,在含有敏感資訊(如個人資料、銀行帳戶等)的網站進行操作時,必須特別注意:

  1. 處於登入狀態時,不要隨意開啟其他來源不明的網站
  2. 不要使用瀏覽器記住帳號、密碼的功能
  3. 完成操作後,立刻登出

不過使用者能做的防禦有限,伺服器端還是應當做好預防措施。

伺服器端的防範

檢查 Referer

HTTP Request Headers 有一個 referer 欄位,代表發出請求的來源位址(URL),可以透過 referer 檢查是否為相同 domain 所發出的請求,不是的話就拒絕請求。不過這個做法並不完善,有幾點要注意:

  1. 有些瀏覽器可能不會在 Request Headers 中帶上 referer 欄位
  2. 有些使用者可能會關閉或停用自動帶 referer 的功能
  3. 攻擊者可以透過竄改 referer 來騙過目標網站的伺服器

圖形、簡訊驗證碼

通常在網路銀行轉帳的時候,都會收到一組簡訊驗證碼,為了確保行為是由使用者本人操作。圖形驗證碼也是同樣的道理,如果攻擊者不知道圖形驗證碼的答案,也就不可能攻擊成功了。雖然這是一個相對安全的作法,但是對使用者來說卻十分麻煩。

Synchronizer Token Pattern

Synchronizer Token Pattern(STP)指的是每次使用者發出請求時都必須傳回一個伺服器所指定的亂數,而這個亂數可以設計成適用於整個 session 階段,也可以設計成每個請求只能使用一次,後者(per-request token)的作法會比前者來得安全,但是對使用者來說較為不便,所以在大部分的情況下,前者(per-session token)反而是比較常用的作法。

伺服器產生一組隨機且不重複的 token 放在表單中隱藏的欄位:

<form action="<https://example.com>" method="POST">
  <input type="hidden" name="CSRFToken" value="KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt">
  ...
</form>

表單提交時,伺服器會比對這組 token 與自己 session 中存放的值是否相同,攻擊者則因為無法得知正確的 token 而請求失敗。

Double Submit Cookie

做法和 STP 類似,只不過 Double Submit Cookie 是無狀態的(stateless),意思是伺服器不需要保存 token,而是存放在 client-side 的 cookie 中。表單提交時,伺服器會比對 cookie 中 token 與表單中 token 的值:

Set-Cookie: CSRFToken=KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt
<form action="<https://example.com>" method="POST">
  <input type="hidden" name="CSRFToken" value="KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt">
  ...
</form>

受到同源政策的限制,攻擊者不能從其他 domain 讀取或設置目標網站的 cookie,因此無法得知正確的 token。

不過這個做法依然有些缺點,攻擊者如果掌握了目標網站的 subdomain,就能夠設置 cookie,因此最好確保每個 subdomains 都是安全的,並且只接受 HTTPS connections。

上面的做法,token 是由 server-side 產生,如果網站是 SPA(Single Page Application)的話,則可以由 client-side 產生,把 token 統一放在 Request Headers 裡面,就不用每個表單都放。(axios 就有提供這樣的功能)

SameSite Cookie

SameSite 不允許 cookie 來自跨來源的請求(cross-origin requests)。分別有三種模式:StrictLaxNone

  1. Strict 最為嚴格,<a href="..."><form>new XMLHttpRequest 等等,只要不是相同來源的請求,就不會帶上這個 cookie。
  2. Lax 稍微寬鬆,除了 GET 以外的方法都不會帶上這個 cookie。
  3. None 則不限制跨站請求。
Set-Cookie: JSESSIONID=xxxxx; SameSite=Strict
Set-Cookie: JSESSIONID=xxxxx; SameSite=Lax

目前 Google Chrome 的預設值是 Lax。在 Lax 模式下,使用者可以從外部的連結進入網站,並且保持登入狀態,有些網站會在敏感操作時,才帶上 SameSite=Strict 的 cookie,以防止 CSRF 攻擊。

References


#Security #csrf #Cross-Site Request Forgery #cookie







Related Posts

展開運算子(Spread Operator) & 其餘運算子(Rest Operator)

展開運算子(Spread Operator) & 其餘運算子(Rest Operator)

MTR04_0615

MTR04_0615

React-[路由篇]-SPAs與React Router (上)

React-[路由篇]-SPAs與React Router (上)


Comments