Django的安全特性(二) 跨站點請求僞造(CSRF)保護


一、CSRF是什麼?

CSRF,中文名爲跨站請求僞造,CSRF攻擊指的是惡意用戶盜用你的信息,以你的名義發送惡意請求。

這將包括:以你的名義發送郵件,發送信息,盜取賬號,購買商品,甚至貨幣轉賬等惡性行爲。


二、CSRF攻擊原理

如圖:
在這裏插入圖片描述

更具體的可以參閱這篇博客:
CSRF攻擊與防禦

這裏不再過多介紹。


三、在Django中使用CSRF保護

Django的CSRF中間件和相關的模板標籤提供了易於使用的csrf保護。

針對CSRF攻擊的第一個保護措施是確保 safe 的請求方法(如GET、HEAD)沒有副作用,對於需要提交數據的方法(如POST、PUT),可以按照如下步驟進行保護:

  1. 默認情況下,在你的項目的settings.py文件中,MIDDLEWARE參數已經設置了django.middleware.csrf.CsrfViewMiddleware中間件,如果你想要自定義這個設置,請確保該中間件位於已經假定CSRF攻擊已經被處理過的視圖中間件之前。

【注】
不建議去禁用該中間件,但如果你禁用了它,你需要在你想要保護的視圖中使用csrf_protect()以確保抵禦CSRF攻擊。

  1. 在任何使用了POST的表單中,聲明{% csrf_token %}標籤。如:
<form method='post'>
	{% csrf_token %}
	...
</form>

但如果該POST表單以外部url爲目標,則不應該這麼做,因爲這將導致csrf令牌泄露,從而產生漏洞。

  1. 在相應的視圖函數中,確保使用RequestContext作爲上下文變量,RequestContext始終啓用django.template.context_processors.csrf,這樣才能保證 {% csrf_token %} 正常工作。如果你使用的是render、類視圖或者 contrib apps,那麼不用擔心,這些默認都是使用RequestContext的。

四、AJAX

雖然上面的方法可以用於Ajax POST請求,但是存在着不方便,因爲每次Ajax,你都需要確保你是否將csrf_token作爲POST數據一併傳遞了。因此這裏還有一個替代方案,在每個 XMLHttpRequest 上設置一個自定義的 X-CSRFToken header,該X-CSRFToken header將作爲csrf_token的值。該方案通常更加方便,因爲許多Js框架都提供了允許在request上設置請求頭的hooks

其中,X-CSRFToken header可以由CSRF_HEADER_NAME設置,CSRF_HEADER_NAME是setting.py文件的一個配置項,它的默認值是HTTP_X_CSRFTOKEN。該請求頭與request.META中的其它請求頭一樣,通過將字符大寫,連字符變下劃線,以及添加HTTP_前綴,從而將從服務器中接收到的頭名稱標準化。

但是想要這麼做,你仍然需要先獲得csrf_token,具體怎樣獲得它將取決於CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY設置。


CSRF_USE_SESSIONS :settings配置項。控制是否將csrf_token存儲在用戶session中而不是在cookie中,默認值是False。將csrf_token存儲在cookie中是安全的(這也是Django的缺省值),但其它的Web框架常把它存儲在session中。另外,默認的錯誤處理視圖也需要csrf_token,因此如果使用CSRF_USE_SESSIONS,則應該在任何可能引發錯誤以導致錯誤視圖的中間件之前插入SessionMiddleware中間件。

CSRF_COOKIE_HTTPONLY:settings配置項。默認值是False,控制是否在CSRF cookie上使用HTTPOnly標誌,如果該值設爲True,則客戶端的Js腳本將無法訪問CSRF cookie。HttpOnly是一個包含在set-Cookie HTTP響應頭中的標誌,它可以有效降低惡意攻擊者的Js代碼訪問cookie的風險。與該配置項類似的還有SESSION_COOKIE_HTTPONLY,它的默認值爲True,功能類似,用於控制Js代碼是否能獲取到session cookie。


(1)當CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY均爲False時獲取csrf_token

這種情況下,csrf_token會保存在cookie,默認該cookie的名字是 csrftoken cookie,你也可以通過CSRF_COOKIE_NAME配置項將 csrftoken 改爲其它的名字。

獲取csrftoken cookie的代碼將類似如下:

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

通過現成的處理cookie的js庫,還可以有更簡單的方式獲取它,一行代碼就OK:

var csrftoken = Cookies.get('csrftoken');

該js庫的github地址:js-cookie

注意

如果你在模板中顯式使用了{% csrf_token %},那麼在Dom中也會包含csrftoken,但是csrf中間件更優先使用cookie中的csrftoken,因此你也應該使用cookie。

如果你在模板中忘記了使用{% csrf_token %},那麼Django可能將不會設置csrftoken cookie,在將表單動態的添加到頁面中時,這種情況很常見,解決的辦法是使用Django提供的 ensure_csrf_cookie() 視圖裝飾器,強制該視圖渲染的模板必須攜帶 {% csrf_token %}。


(2)當CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY均爲True時獲取csrf_token

如果CSRF_USE_SESSIONS爲True,則csrf_token保存在session中,如果CSRF_COOKIE_HTTPONLY爲True,則儘管csrf_token保存在cookie中,但你無法提供Js代碼獲取到它。因此在這種情況下,我們必需要在Dom中獲取csrf_token。

{% csrf_token %}
<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();
</script>

最後,你還需要在Ajax請求頭中設置token:

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

五、裝飾器

與添加中間件的無差別保護不同,你可以在特定的視圖中使用csrf_protect裝飾器,這些裝飾器提供與csrf中間件相同的保護措施。它要求被修飾的視圖必須能夠接受POST表單數據,同時該視圖的模板需要包含csrf_token標籤。

用法:

from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章