柴少的博客 也许终将一事无成,却不能甘于平庸。

Django之CSRF(十一)

一、django与CSRF介绍

1.1 什么是CSRF

       CSRF, Cross Site Request Forgery, 跨站点伪造请求。一句话总结:都是cookie惹的祸。

cookie的作用流程

        当输入一串网址登陆一个安全的站点如:www.XXX.com,并登陆到你的账号,服务器就会分配一个cookie给浏览器,里面包含了认证的信息,用户在这个站点打开其他的网页时将就不用再输入用户名和密码,浏览器会自动将这个cookie的值包含到请求中。服务器收到请求后,验证cookie的值是否和自己保存的值相同,如果是就证明用户身份合法,允许进行下一步的操作。这样做的好处就是简化的用户操作,不用没进行一步就要输入用户名密码 。
       因为每个用户的生成的cookie都不同,cookie正确的话服务器可以认为对端是真实的用户。然而,坏就坏在请求不一定是用户在了解的情况下发送的,这样对于这些请求浏览器也会带上cookie,服务器就仍然将它作为正常的消息处理,csrf正是利用了这一点,具体流程如下描述。

CSRF的攻击流程

图片.png

#上图来自于:http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

user A 使用浏览器访问可信的网站website1进行业务,此时浏览器会保存website1相关的cookie
user A 使用浏览器访问一个不可信站点website2,如果website2中的网页具有指向website1的链接,攻击就有可能发生。有如下几种情况:
a、website2返回给用户的页面包含website1的链接,点击这个链接就会跳转到website1
b、website2返回给用户的页面包含<img src='XXX'>,其中XXX就是指向website1的链接,这样用户只要访问website2的页面就被攻击了,因为浏览器会解析<img src='XXX'>标签,自动获取XXX的’图片内容‘
c、website2返回给用户的页面包含自动加载的js,且js中有跳转到website1的动作,同上,用户只要访问website2就被攻击了
a、b、c三总跳转的方式不同但是利用的原理是一样的,那就是用户在本地还保存这website1的cookie,再次通过website2上的链接请求website1时,浏览器就会自动将website1对应的cookie加上,这样website1就会认为这个请求是用户合法请求而进行处理。
website2上的攻击者就可以通过编写特定的请求内容,进行攻击。

1.2 Django的解决办法

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。

全局:
中间件 django.middleware.csrf.CsrfViewMiddleware
局部:
@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

Django中CSRF防护原理:

       在用户访问django的可信站点时,django反馈给用户的表单中有一个隐含字段csrftoken,这个值是在服务器端随机生成的,每一次提交表单都会生成不同的值。当用户提交django的表单时,服务器校验这个表单的csrftoken是否和自己保存的一致,来判断用户的合法性。当用户被csrf攻击从其他站点发送精心编制的攻击请求时,由于其他站点不可能知道隐藏的csrftoken字段的信息这样在服务器端就会校验失败,攻击被成功防御,这样就能避免被 CSRF 攻击。

  1. 在返回的 HTTP 响应的 cookie 里,django 会为你添加一个 csrftoken 字段,其值为一个自动生成的 token

  2. 在所有的 POST 表单时,必须包含一个 csrfmiddlewaretoken 字段 (只需要在模板里加一个 tag, django 就会自动帮你生成,见下面)

  3. 在处理 POST 请求之前,django 会验证这个请求的 cookie 里的 csrftoken 字段的值和提交的表单里的 csrfmiddlewaretoken 字段的值是否一样。如果一样,则表明这是一个合法的请求,否则,这个请求可能是来自于别人的 csrf 攻击,返回 403 Forbidden.

  4. 在所有 ajax POST 请求里,添加一个 X-CSRFTOKEN header,其值为 cookie 里的 csrftoken 的值.

图片.png

#当用post提交数据的时候,django会去检查是否有一个csrf的随机字符串,如果没有就会报错.

Django 里如何使用 CSRF 防护 :

首先,最基本的原则是:GET 请求不要用有副作用。也就是说任何处理 GET 请求的代码对资源的访问都一定要是“只读“的。
要启用 django.middleware.csrf.CsrfViewMiddleware 这个中间件。
再次,在所有的 POST 表单元素时,需要加上一个 {% csrf_token %} tag。
在渲染模块时,使用 render。render会处理 csrf_token 这个 tag,  从而自动为表单添加一个名为 csrfmiddlewaretoken 的 input。

二、提交例子

2.1 通过form提交

login.html :

<form action="/login/" method="POST">
    {% csrf_token %}
    <input type="text" name="user" />
    <input type="text" name="pwd" />
    <input type="checkbox" name="rmb" value="1" /> 10秒免登录
    <input type="submit" value="提交" />
</form>

图片.png

#标红的地方就是{% csrf_token %}帮我们拿到的csrf字符串,当用户访问login页面的时候,会生成一个csrf的随机字符串,并且cookie中也存放了这个随机字符串,当用户再次提交数据的时候会带着这个随机字符串提交原理就是先拿到这个字符串然后提交的时候带着这个字符串过去,没有这个字符串会出现403报错。

2.2 通过ajax提交

写例子之前先来抓一下,看看django到底要的是个啥字段:

def login(request):
    from django.conf import settings
    print(settings.CSRF_HEADER_NAME)

图片.png

图片.png

#在后端抓到了,这里的HTTP_X_CSRFTOKEN是django在X_CSRF的前面添加了HTTP_,所以实际传递的是就是X_CSRFtoken,而在前端页面的ajax传递的时候由于不能使用下划线所以传递的是X_CSRFtoken。

好了下面开始写这个完整的例子:

login.html :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10秒免登录
        <input type="submit" value="提交" />
        <input id="btn1" type="button" value="按钮" />
        <input id="btn2" type="button" value="按钮" />
    </form>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        $(function(){
            $.ajaxSetup({
            //ajaxSetup() 方法为将来的 AJAX 请求设置默认值
                beforeSend: function(xhr,settings){
                //beforeSend(xhr)发送请求前运行的函数。
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                    //这样就会在提交ajax之前执行这个方法,从而在所有的ajax里都加上这个csrftoken这里的xhr是XMLHttpRequest的简写,ajax调用的就是这个方法    
                }
            });
            $('#btn1 , #btn2').click(function () {
                $.ajax({
                    url: '/login/',
                    type:"GET",
                    data: {'user': 'root', 'pwd': '123'},
                    // headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    //$.cookie('csrftoken')和form表单里面的{% csrf_token %}值是不一样的这个要注意
                    success:function(arg){
                    }
                })
            });
        })
    </script>
</body>
</html>

图片.png

2.3 如果只对某些页面进行csrf验证呢

#可以把django.middleware.csrf.CsrfViewMiddleware这个中间件注释掉 
from django.views.decorators.csrf import csrf_exempt,csrf_protect

@csrf_protect
def index(request):
    # session中获取值
    if request.session.get('is_login',None):
        return render(request,'index.html',{'username': request.session['username']})
    else:
        return HttpResponse('gun')

#当然如果让哪些不需要csrf验证可以通过@csrf_exempt来实现。

2.4 只对特定的操作进行csrf验证

如果想要实现在当get方式的时候不需要提交csrftoken,当post的时候需要,实现这种效果的代码如下:

 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);
                }
            }
        });

#这样就实现了当GET|HEAD|OPTIONS|TRACE这些方式请求的时候不需要提交csrftoken。

作者:chaishaopeng 分类:Django学习 浏览:1211 评论:0
留言列表
发表评论
来宾的头像