美文网首页我爱编程
Django 随笔: CSRF 防御

Django 随笔: CSRF 防御

作者: 学以致用123 | 来源:发表于2018-03-16 13:43 被阅读0次

    什么是 CSRF?

    维基百科定义:https://en.wikipedia.org/wiki/Cross-site_request_forgery

    CSRF跨站点请求伪造(Cross—Site Request Forgery),为了便于理解,我们引用http://blog.csdn.net/stpeace/article/details/53512283上的例子进行说明。

    例子中涉及以下三个角色:

    Web A : 存在安全漏洞的网站;

    Web B :进行 CSRF 攻击的恶意网站;

    User C: Web A 的网站的授权用户。

    CSRF 攻击过程

    1. User C 在浏览器中打开 Web A,并输入用户名和密码进行登录;

    2. Web A 对 User C 的登录信息进行验证并通过后,Web A 生成 cookie 信息并返回给浏览器,User C 登录成功,可以向 Web A 发送请求;

    3. User C 在没有退出 Web A 的情况下,在同一个浏览器中,打开了一个访问 Web B 的页面;

    4. Web B 在 User C 不知情的情况下向 Web A 发送请求(在请求头存放浏览器中 User C 的 Cookie 信息);

    5. Web A 根据请求中的 Cookie 信息认为该请求是 User C 发送的,因此以 User C 的权限处理 Web B 发送请求。

    这样 Web B 可以通过 User C 的权限对 Web A 进行操作。

    如果 web A 为银行网站,Web B 可能通过这些权限在 User C 不知情的情况下转走 User C 账户中的资金。


    Django CSRF 解决方案


    Django 1.11 版本官方文档对此有比较全面的介绍,文档信息为:

    原文地址:https://docs.djangoproject.com/en/1.11/ref/csrf/

    翻译地址:https://www.jianshu.com/p/2b6c69f2d520


    Django 什么时候进行 CSRF 防御?

    Django 为使用 django-admin startproject 命令创建的项目自动设置 csrfviewmiddleware 后端进行 CSRF 防御。

    csrfviewmiddleware 会对 POST 、PUT 和 DELETE 等请求进行检查,如果这些请求不包含 csrf token 或者 包含的 csrf token 不正确,会返回 403 Forbidden 响应。

    解决 403 Forbidden 的简单方法

    这是我们开始 Django 开发时经常遇到的一个问题。解决 '403 Forbidden' 最简单的一个方法是取消 Django 的 csrf 防御设置,我们可以采用以下两种方法实现:

    1. 打开 settings.py 文件,注释掉或者删除 MIDDLEWARE 设置中的 'django.middleware.csrf.CsrfViewMiddleware'。

    2. 为处理 POST 请求的视图添加 csrf_exempt 装饰器。

    from django.views.decorators.csrf import csrf_exempt
    from django.http import HttpResponse
    ​
    @csrf_exempt
    def my_view(request):
     return HttpResponse('Hello world')</pre>
    

    这两种方法可以解决 '403 Forbidden' 的问题,但是却禁用了 csrfviewmiddleware 后端,网站遇到 CSRF 攻击将无法进行防御。

    因此,我们需要做的是想办法通过 csrfviewmiddleware 后端的检查。

    正确的方法

    csrfviewmiddleware 对 POST 、PUT 和 DELETE 等请求进行以下检查:

    1. cookie 和 POST ( PUT 或 DELETE ) 数据中必须包含 csrf token 信息;

    2. cookie 和 POST ( PUT 或 DELETE ) 数据中的 csrf token 必须一致。

    如果我们能满足以上两个要求,即可通过 csrfviewmiddleware 的验证。

    下面,我们解决 Django 开发中最常用的 表单 POST 请求和 AJAX POST 请求的设置方法。


    Form 表单

    只需要表单模板的 <form> 元素中加入 csrf_token 模板标签即可:

    <form action="" method="post">
     {% csrf_token %}
    

    然后使用 render() 函数或者通用视图渲染表单模板即可。后台会自动把 {% csrf_token %} 渲染为

    <input type='hidden' name='csrfmiddlewaretoken' value='****' />
    

    这样 POST 的数据中将会通过名为 csrfmiddlewaretoken 的键值对包含 csrf token 的信息,与此同时,后台还会在 cookie 中添加 csrf_token。

    这样,即可通过 csrfviewmiddleware 的检验需求。


    AJAX

    AJAX 可以采用以下三种方法来处理。

    方法 1

    一劳永逸的方法,使用 Django 提供的方法:为满足条件的 XMLHttpRequest 设置一个自定义 X-CSRFToken标头保存 CSRF token 。由于许多 JavaScript 框架提供为每个请求设置标头的 hooks,这样做会很简单。

     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', '{{ csrf_token }}');
     }
     }
     });
    

    这里设置了添加 csrf_token 的前提条件,要求满足 !csrfSafeMethod(settings.type) && !this.crossDomain 才可以添加,这样做是为了避免 csrf_token 泄露。

    注意,这里使用的是 {{ csrf_token }},Django 将其渲染为 token 的值,并在 cookie 中添加 csrf_token。这样也可以满足 POST 数据和 cookie 中包含相同的值。

    方法2

    在 ajaxSetup 的 data 中设置 csrf_token,这样会为 ajax post( put、delete )请求的 data 添加 csrf_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) {
     data:{csrfmiddlewaretoken:'{{ csrf_token }}'}
     }
     }
     });
    

    在一般情况下, 方法2 等效于方法1,但是如果 ajax post 传输序列化数据,比如:

    $.post('/', $('form').serialize())
    

    jQuery 不能将序列化数据与ajaxSetup data 中的数据进行合并,从而造成 ajax 的 POST 数据中不包含 csrf_token。

    方法3

    为每个 POST 请求的 ajax 的 data 中添加 csrf_token ,也就是:

    $.ajax({
     url:"...",
     data:{"csrfmiddlewaretoken":'{{ csrf_token }}','other_key':'value'}
     type:"POST",
     success:function (data) {
     alert(data)
     }
     })
     })
    

    这种方法简单,但是如果存在多个 ajax POST 请求,则比较麻烦。


    csrf_token 模板标签如何正常工作

    前面,我们使用了{% csrf_token %} 和 {{ csrf_token }} 模板标签。要保证 csrf_token 正常工作,需要启动 csrfviewmiddleware 后端 或者设置 csrf_protect 装饰器。如果这两者都没运行,则要使用 requests_csrf_token 装饰器。

    强烈推荐使用上面的模板标签获取 csrf_token。如果不采用这个方法,在 AJAX 中,还可以根据 CSRF_USE_SESSION 的值的不同采用下面的方法从 cookie 或者 session获取 csrf_token。

    从 cookie 中获取 csrf_token

    <script src=" http://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js "></script>
    <script>
     var csrftoken = $.cookie('csrftoken');
    

    这些代码从一个公共CDN加载jQuery cookie插件,这样我们可以与cookie交互,然后使用插件读取csrf token 的值。然后我们可以用 csrftoken 代替 {{ csrf_token }}。

    从 session 中获取 csrf_token

    我们可以这样读取 csrf_token:

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

    这种方法的前提是 HTML中必须包含 CSRF token。

    相关文章

      网友评论

        本文标题:Django 随笔: CSRF 防御

        本文链接:https://www.haomeiwen.com/subject/guglqftx.html