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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章