CSRF攻擊與Django防範

linkfun發表於2020-02-29

什麼是CSRF

CSRF,全稱 Cross Site Request Forgery,跨站請求偽造,是一種欺騙受害使用者在已登入的web應用上按照攻擊者的指令執行惡意操作的攻擊行為。攻擊的影響範圍取決於受害使用者所擁有的的許可權,因為攻擊者是依賴使用者在當前會話中的鑑權進行惡意操作的。

CSRF是如何工作的

CSRF僅在使用者已被授權的情況下才能生效。通過CSRF,攻擊者可以繞開web應用的鑑權機制,進而利用受害者的使用者許可權對web應用進行惡意操作。比如網上銀行等場景。

下面是CSRF攻擊的兩個主要組成部分:

  1. 第一步是欺騙使用者點選一個連結或者載入一個頁面,通常是利用一些社交手段誘使使用者點選一個惡意連結。
  2. 第二部是在這個惡意連結中,通過使用者的瀏覽器向web應用發起一個偽造的請求。這個偽造請求看起來是合法的,但是攜帶的是攻擊者偽造的資料。與此同時,瀏覽器會在請求中帶上與該web應用關聯的cookie。因為cookie中包含了使用者之前登陸的token鑑權資訊,在使用者沒有登出或者token沒有過期之前,該鑑權資訊是持續有效的。這樣攻擊者就可以依靠該cookie直接獲取到使用者的許可權進行惡意操作了。

(HTTP session和cookie機制請見cookie與session的區別以及在Django中的實現)

CSRF攻擊與Django防範

CSRF攻擊實際上就是利用了HTTP會話的cookie機制,因為瀏覽器會在每次請求中都帶上與web應用關聯的cookie。

CSRF攻擊示例

假設這是一個合法的url,用於向指定的User使用者轉賬1000塊錢。

http://example.com/transfer?amount=1000&account=User
複製程式碼

當使用者傳送該請求時,如果使用者已經登入過web應用且鑑權通過,則瀏覽器會在請求中自動攜帶包含鑑權資訊的cookie,這樣使用者進行轉賬時就不需要再次進行登入驗證;如果使用者還未登入鑑權,則會需要先進行登入鑑權,然後瀏覽器才能進一步攜帶cookie資訊發起轉賬請求。 web應用需要拿到轉賬請求中攜帶的cookie用於驗證使用者資訊,才能得知是要從哪個使用者的賬戶中將錢轉出。

1. 使用GET請求進行攻擊

如果web應用可以接受GET請求,那攻擊者只需要在惡意連結的html中加入如下的程式碼,通過img標籤自動向web應用偽造一個轉賬請求。利用瀏覽器自動攜帶cookie的機制,就可以從使用者賬戶中盜走1000塊錢。

<img src="http://example.com/transfer?amount=1000&account=Fred" />
複製程式碼

該場景中的web應用除了存在CSRF漏洞以外,還有一個問題:任何的GET請求都應該是“只讀“的,不應該對資料產生任何影響。所以該場景中的web應用設計是不合理的。

2. 使用POST請求進行攻擊

下面是使用POST進行攻擊的html表單程式碼

<h1>You Are a Winner!</h1>
<form action="http://example.com/api/account" method="post">
 <input type="hidden" name="Transaction" value="withdraw" />
 <input type="hidden" name="Amount" value="1000" />
 <input type="submit" value="Click Me"/>
</form>
複製程式碼

試想如下攻擊場景:

  1. 使用者登入web應用example.com,並進行了鑑權。
  2. web應用對使用者進行授權,並在響應中攜帶了包含鑑權token的cookie。
  3. 使用者在沒有登出的情況下,又開啟了一個包含上述html表單的惡意頁面。(注意該表單中的action是將請求傳送到存在漏洞的web應用,而不是惡意頁面本身,這就是Cross-Site,跨站請求)
  4. 使用者點選惡意頁面中的"Click Me"按鈕提交,瀏覽器在請求中攜帶cookie發給web應用。
  5. 偽造的請求利用攜帶的cookie通過了web應用的鑑權,取得了使用者所擁有的的許可權,進行惡意操作。

值得注意的是,SSL是無法防護CSRF攻擊的。因為攻擊者只需要在偽造請求中指定https訪問即可。

防範CSRF攻擊

最常用的CSRF防範機制就是使用csrf token機制。

  1. csrf token基於一個隨機生成的祕鑰secret,並通過salt hash方式加密生成csrftoken,插入到Cookie中。該csrftoken在使用者登入階段生成,在session結束前保持不變。

CSRF攻擊與Django防範

  1. 每一個響應的POST表單中,都會插入一個隱藏的csrfmiddlewaretoken欄位。該欄位的值也是對1中的secret進行salt hash,每次請求表單頁面都會使用一個隨機的salt,所以每次響應中表單裡面插入的csrfmiddlewaretoken都是不一樣的。

CSRF攻擊與Django防範

  1. 對於每一次HTTP請求,只要request method不是GET, HEAD, OPTIONS or TRACE,都會要求同時在request header的cookie中攜帶csrftoken,以及在request body中攜帶csrfmiddlewaretoken,並對二者進行校驗。如果缺失或者校驗不通過,則返回403 error。

CSRF攻擊與Django防範
CSRF攻擊與Django防範

  1. 在校驗csrftoken和csrfmiddlewaretoken時,不是直接對比兩個token,而是解密後對比二者的祕鑰secret是否相同。因為即使使用者每次請求在表單中獲得的csrfmiddlewaretoken都是變化的,但是在整個session中,secret是保持不變的。

注意,在POST表單中使用csrf token時,要確保表單是提交給自己的web應用的,而不是提交給外部的應用。比如在表單action中指定提交到baidu或者其他網站,別人是無法識別請求中的csrf token的,並且同時也存在洩漏csrf token的風險。

總結

cookie因為其自動在請求中攜帶的特點,很容易受到CSRF攻擊。攻擊者不需要獲取使用者的cookie,也可以直接利用使用者的cookie進行惡意操作。CSRF攻擊的影響取決於使用者所擁有的許可權。

所以我們在web應用設計中要注意,一方面是要嚴格遵守HTTP規範,GET請求一定是”只讀“的,避免接收GET請求進行狀態變更(如資料修改等);另一方面是對於所有設計狀態變更的請求,如POST、PUT、DELETE等,都需要進行csrf token校驗。

【本文參考】

Introduction to CSRF

Cross Site Request Forgery protection

相關文章