Django CSRF 说明与配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wanglei_storage/article/details/85166942

一、CSRF是什么

CSRF 全称(Cross Site Request Forgery)跨站请求伪造。也被称为One Click Attack和Session Riding,通常缩写为CSRF或XSRF。你可以这样理解:攻击者(黑客,钓鱼网站)盗用了你的身份,以你的 名义发送恶意请求,这些请求包括发送邮件、发送信息、盗用账号、购买商品、银行转账,从而使你的个人隐私泄露和财产损失。


二、CSRF漏洞现状

CSRF这种攻击方式在2000年已经被国外的安全人员提出 ,但在国内,直到2006年才开始被关注,2008年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:纽约时报,Metafilter,YouTube和百度。而现在,互联网的许多站点仍对此毫无防备,以至于安全业界称CSRF为"沉睡的巨人"。


三、CSRF攻击实例

听了这么多,可能大家还云里雾里,光听概念可能大家对于CSRF还是不够了解,下面我将举一个例子来让大家对CSRF有一个更深层次的理解。

我们先假设支付宝存在CSRF漏洞,我的支付宝账号是wl,攻击者的支付宝账户是xxx,然后我们通过网页请求的方式 http://zhifubao.com/withdraw?account=lyq&amount=10000&for=wl 可以把账号的wl的10000元转到我的另外一个 账户wl上面去。通常这个情况下,该请求发送到支付宝服务器后,服务器会先验证请求是否来自一个合法的session,并且该session的用户已经成功登录。攻击者在支付宝也有账户xxx,他直到上文中的URL可以进行转账操作,于是他自己可以发送一个请求 http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx 到支付宝后台。但是这个请求是来自攻击者而不是我wl,所以不能通过安全认证,因此该请求作废。这时,攻击者xxx想到了用CSRF的方式,他自己做了个网站,在网站中放了如下代码:http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx ,并且通过网站链接诱使我来访问他的网站。当我禁不住诱惑时就会点了进去,上述请求就会从我自己的浏览器发送到支付宝,而且这个请求会附带我的浏览器中的cookie。大多数情况下,该请求会失败,因为支付宝要求我的认证信息,但是我如果刚访问支付宝不久,还没有关闭支付宝页面,我的浏览器中的cookie存有我的认证信息,这个请求就会得到响应,从我的账户中转10000元到xxx账户里,而我丝毫不知情。


四、CSRF原理

下面简单阐述了CSRF原理
1、用户登录并信任网站A
2、验证通过,在用户处产生A的Cookie
3、用户在没有登出网站A的情况下,访问危险网站B
4、网站B要求访问第三站点网站A,发出一个request
5、根据网站B的请求,浏览器带着网站A产生的Cookie访问网站A
6、网站A不知道请求是用户发出的还是网站B发出的,由于浏览器会自动带上用户Cookie,所以网站A会根据用户的权限处理请求,这样网站B就达到了模拟用户操作的目的

从上面步骤中可以看出,要完成一次CSRF攻击,受害者必须依次完成以下两个步骤:

  • 登录受信任网站A,并在本地生成Cookie
  • 在不登出A的情况下,访问危险网站B

看到这里,你也许会问:如果我不满足以上两个条件中的一个,我就不会受到CSRF攻击。是的,确实如此,但是你不能保证以下情况不会发生

  • 你不能保证你登录了一个网站之后,不再打开一个tab页面并访问其他的网站
  • 你不能保证你关闭浏览器之后,你本地的Cookie会立刻过期,你上次的会话已经结束
  • 上述中所谓的攻击网站,可能就是一个钓鱼网站

五、CSRF如何防御

验证HTTP Referer字段

根据HTTP协议,在HTTP头部中有一个Referer字段,它记录了该HTTP请求所在的地址,表示HTTP请求从哪个页面发出的。比如当你访问 http://zhifubao.com/withdraw?account=lyq&amount=10000&for=xxx ,用户必须先登录支付宝网站,然后通过点击页面的按钮来触发转账事件。此时,转账请求的Referer值就是转账页面的所在的URL,通常是以zhifubao.com域名开头的地址。如果攻击者要实行CSRF攻击,那么他只能在自己的站点构造请求,此时Referer的值就指向黑客自己的网站。因此要防御CSRF攻击,支付宝只需要对每一个转账请求验证其Referer值,如果是以zhifubao.com开头的域名,则是合法请求,相反,则是非法请求并拒绝。

这种方法的好处就是简单易行,只需要在后台添加一个拦截器来检查Referer即可。然而这种办法并不是万无一失,Referer的值是由浏览器提供的,一些低级的浏览器可以通过某种方式篡改Referer的值,这就给了攻击者可乘之机;而一些高级浏览器处于安全考虑,可以让用户设置发送HTTP请求时不再提供Referer值,这样当他们正常访问支付宝网站时,因此没有提供Referer值而被误认为CSRF攻击,拒绝访问。实际应用中通常采用第二种方法来防御CSRF攻击。


六、添加token验证

CSRF攻击之所以能够成功,是因为攻击者可以完全伪造用户的请求,该请求中所有的用户验证信息都存在cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的额cookie来通过安全验证。要防止CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于cookie中。可以在HTTP请求中以参数的形式假如一个随机产生的token,并在服务器建立一个拦截器来验证这个token,如果请求中没有token或者token不正确,则认为可能是Anti CSRF Token来防御CSRF

  • 用户访问某个表单页面
  • 服务端生成一个Token,放在用户的Session中,或者浏览器的Cookie中
  • 在页面表单附带上Token参数。
  • 用户提交请求后,服务端验证表单中的Token是否与用户Session(或Cookies)中的Token一致,一致为合法请求,不是则非法请求。

这个Token值必须是随机的,不可预测的。由于Token的存在,攻击者无法再构造一个带有合法Token的请求实施CSRF攻击。另外使用Token应注意Token的保密性,尽量把敏感操作由GET改成POST,以form或者AJAX形式提交,避免Token泄露。


七、验证码

验证码,强制用户必须与应用进行交互,才能完成最终请求。通常情况下,验证码能够很好的遏制CSRF攻击。但是处于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段。


八、尽量使用POST,限制GET

GET接口能够直接将请求地址暴露给攻击者,所以要防止CSRF一定最好不要用GET。当然POST并不是万无一失,攻击者只需要构造一个form表单就可以,但需要在第三方页面做,这样就增加了暴露的可能性。


九、在HTTP头部添加自定义属性

这种方法也是使用token并验证,但是它是把token放在HTTP请求头部中。通过使用AJAX我们可以在我们的请求头部中添加我们的自定义属性,但是这种方法要求我们将整个站的请求全部改成AJAX,如果是新站还好,老站的花无疑是个需要重写整个站点的,这是很不可取的


十、Django Form表单和Ajax csrf设置

1、form表单设置 (只需要在表单中定义 {% csrf_token %} 即可)

<form method="post" action="{{ request.path_info }}">
    {% csrf_token %}
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="submit" value="登录" />
</form>

2、ajax单个请求设置(需要定义headers参数)

<form method="post" action="{{ request.path_info }}" id="login">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="button" value="登录" id="button" />
</form>

<script src="/static/jquery-1.12.4.js" ></script>
<script src="/static/jquery.cookie.js" ></script>
<script>
$(function(){

    $('#button').click(function(){
        $.ajax({
            url: '/wanglei/login/',
            type: 'POST',
            data: $('#login').serialize(),
            dataType: 'JSON',
            headers:  {'X-CSRFtoken': $.cookie('csrftoken')},
            success: function(data) {
                if (data.status) {
                    location.reload()
                } else {
                    alert(data.message)
                }
            }
        })
    })
})

</script>

3、ajax全局设置(通过全局设置,即对当前页面所有的ajax请求都生效)

<form method="post" action="{{ request.path_info }}" id="login">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="button" value="登录" />
</form>

<script src="/static/jquery-1.12.4.js" ></script>
<script src="/static/jquery.cookie.js" ></script>
<script>
$(function(){

    /* ajax global csrf define */
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
        }
    })
    
    $('#button1').click(function(){
        $.ajax({
            url: '/wanglei/login/',
            type: 'POST',
            data: $('#login').serialize(),
            dataType: 'JSON',
            success: function(data) {
                if (data.status) {
                    location.reload()
                } else {
                    alert(data.message)
                }
            }
        })
    })
})
</script>

十一、Django csrf 全局生效与局部生效

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

全局生效

settings中的中间件:django.middleware.csrf.CsrfViewMiddleware

局部生效

注:from django.views.decorators.csrf import csrf_exempt, csrf_protect

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


1、定于csrf全局生效,而当前表单不生效


settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

views

from django.views.decorators.csrf import csrf_exempt, csrf_protect
@csrf_exempt
def bbbb(request):
    if request.method == 'POST':
        res = {'status': True, 'data': None, 'message': None}
        try:
            bbbb1 = request.POST.get('bbbb1', None)
            bbbb2 = request.POST.get('bbbb2', None)
            if bbbb1 and bbbb2:
                pass
            else:
                res['status'] = False
                res['message'] = 'bbbb1 或 bbbb2 为空'
        except Exception as e:
            res['status'] = False
            res['message'] = '请求错误'
        return HttpResponse(json.dumps(res))
    elif request.method == 'GET':
        return render(request, 'bbbb.html')

bbbb.html

    <form action="{{ request.path_info }}" method="post" id="login">
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>



2、定于csrf全局不生效,而当前表单生效

settings

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

views

@csrf_protect
def bbbb(request):
    if request.method == 'POST':
        res = {'status': True, 'data': None, 'message': None}
        try:
            bbbb1 = request.POST.get('bbbb1', None)
            bbbb2 = request.POST.get('bbbb2', None)
            if bbbb1 and bbbb2:
                pass
            else:
                res['status'] = False
                res['message'] = 'bbbb1 或 bbbb2 为空'
        except Exception as e:
            res['status'] = False
            res['message'] = '请求错误'
        return HttpResponse(json.dumps(res))
    elif request.method == 'GET':
        return render(request, 'bbbb.html')

3、html

    <form action="{{ request.path_info }}" method="post" id="login">
        {% csrf_token %}
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){

            /* ajax global csrf */
            $.ajaxSetup({
                beforeSend: function(xhr, settings){
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                }
            });

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>

十二、定义不需要CSRF防跨站请求的方法

bbbb.html

    <form action="{{ request.path_info }}" method="post" id="login">
        {% csrf_token %}
        <input type="text" name="bbbb1" />
        <input type="text" name="bbbb2" />
        <input type="submit" value="提交1" />
        <input type="button" value="button1" id="button1" />
        <input type="button" value="button2" id="button2" />
    </form>

    <script src="/static/jquery-1.12.4.js" ></script>
    <script src="/static/jquery.cookie.js" ></script>
    <script>
        $(function(){
        
            function csrfSafeMethod(method) {
                // these HTTP methods do not require CSRF protection
                return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
            }
            
            /* ajax global csrf */
            $.ajaxSetup({
                beforeSend: function(xhr, settings){
                    xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
                }
            });

            /* button1 */
            $('#button1').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    headers: {'X-CSRFtoken': $.cookie('csrftoken')},
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });


            /* button2 */
            $('#button2').click(function(){
                $.ajax({
                    url: '/wanglei/bbbb/',
                    type: 'POST',
                    data: $('#login').serialize(),
                    dataType: 'JSON',
                    success: function(data) {
                        if (data.status){
                            location.reload()
                        } else {
                            alert(data.message)
                        }
                    }
                });
            });
        })
    </script>

猜你喜欢

转载自blog.csdn.net/wanglei_storage/article/details/85166942
今日推荐