Django框架学习16--csrf防御机制及原理

csrf攻击说明

1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

3.用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

4.网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

5.浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

Django csrf防护原理:

Django预防CSRF攻击的方法是在用户提交的表单中加入一个csrftoken的隐含值,这个值和服务器中保存的csrftoken的值相同,这样做的原理如下:

1、在用户访问django的可信站点时,django反馈给用户的表单中有一个隐含字段csrftoken,这个值是在服务器端随机生成的,每一次提交表单都会生成不同的值

2、当用户提交django的表单时,服务器校验这个表单的csrftoken是否和自己保存的一致,来判断用户的合法性.

3、当用户被csrf攻击从其他站点发送精心编制的攻击请求时,由于其他站点不可能知道隐藏的csrftoken字段的信息这样在服务器端就会校验失败,攻击被成功防御

具体操作

全局:

中间件 django.middleware.csrf.CsrfViewMiddleware

局部:

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

@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

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

实际例子:

urls.py中增加

url(r'^index$',views.index),
url(r'^csrf_verify/$',views.csrf_verify),

访问http://127.0.0.1:8000/index,展现csrf.html界面

csrf.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/login/" method="post">
        {% csrf_token %}
        <p>11111111111111</p>
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10s免登录
        <input type="submit" value="提交" />
    </form>
</body>
</html>
def index(request):
    return render(request,"csrf.html")

def csrf_verify(request):
    csrf
    print(5555555555555555)
    if request.method == "GET":
        return render(request, 'csrf.html')
    elif request.method == "POST":
        print(22222222222222222222)
        user = request.POST.get('user')
        pwd = request.POST.get('pwd')
        csrf_token = request.POST.get("csrftoken")
        print(csrf_token)
        print(11111111111111111111111111111111111)
        if user == 'root' and pwd == "123123":
            # 生成随机字符串
            # 写到用户浏览器cookie
            # 保存在服务端session中
            # 在随机字符串对应的字典中设置相关内容
            request.session['username'] = user
            request.session['islogin'] = True
            if request.POST.get('rmb', None) == '1':
                # 认为设置超时时间
                request.session.set_expiry(10)
            return render_to_response('login.html',context={"token":csrf_token})
        else:
            return render(request, 'csrf.html')

点击提交按钮进行提交后,如果正确输入root,123123,则进入login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<div style="margin: 20% 40%;">
    <h1>欢迎登录!</h1>
    <form action="/action/" method="post">
        <p>11111111111111111111111111111</p>
        {{token}}
        {% csrf_token %}
        <p>
            <label for="id_username">用户名:</label>
            <input type="text" id="id_username" name="username" placeholder="用户名" autofocus required/>
        </p>
        <p>
            <label for="id_password">密码:</label>
            <input type="password" id="id_password" placeholder="密码" name="password" required>
        </p>
        <p style="color:red">
            {{msg}}
        </p>
        <input type="submit" value="确定">
        <a href="/forget">忘记密码?</a>
    </form>
    <br><br>
    <a href="/register">新用户先注册</a>
</div>

</body>
</html>

此时查看元素和cookie

 

每次刷新页面,form表单中的token都会刷新,而cookie中的token却只在每次登录时刷新,并且两个token的值并不相同,那么django是如何进行校验的呢?

官方文档的解释:

When validating the ‘csrfmiddlewaretoken’ field value, only the secret, not the full token, is compared with the secret in the cookie value. This allows the use of ever-changing tokens. While each request may use its own token, the secret remains common to all.
This check is done by CsrfViewMiddleware.

官方文档中说到,检验token时,只比较secret是否和cookie中的secret值一样,而不是比较整个token。

token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。
django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。

所以只要验证两个token通过salt解密出的secret一致就可以说明是一致的了。

查看源码:csrf.py

def _compare_salted_tokens(request_csrf_token, csrf_token):
    # Assume both arguments are sanitized -- that is, strings of
    # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
    return constant_time_compare(
        _unsalt_cipher_token(request_csrf_token),
        _unsalt_cipher_token(csrf_token),
    )


def _unsalt_cipher_token(token):
    """
    Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
    CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
    the second half to produce the original secret.
    """
    salt = token[:CSRF_SECRET_LENGTH]
    token = token[CSRF_SECRET_LENGTH:]
    chars = CSRF_ALLOWED_CHARS
    pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
    secret = ''.join(chars[x - y] for x, y in pairs)  # Note negative values are ok
    return secret

验证:

from django.middleware import csrf

s1 = csrf._unsalt_cipher_token("yNHcXGnUVv2oM4N0tICGDWqFRbZFM0oHm9KuxNGirhsMz2C4HMRxZuAJz6MtkG4D")
s2 = csrf._unsalt_cipher_token("JiI8graveYvuYsYu6ld37jQl2958rsh1xELqQytTKKVSLqNykpsUtR0pK4SWZ8XX")
print(s1,s2)

输出结果:

YwdsKhtyGWAyX8Zeoep1wIkeS5XYIQQ6 
YwdsKhtyGWAyX8Zeoep1wIkeS5XYIQQ6

所以通过验证可知是一样的。

总结:网上有不少关于django csrf token验证原理的文章都是错的,是因为他们根本不知道csrf-token的结构组成。

通过源码可以知道,当用户请求包含

{% csrf_token %}并且是post的请求的网页时,django会返回给浏览器cookie一个csrftoken,界面上会有一个

标签,当用户进行post提交时,浏览器携带csrfmiddlewaretoken和cookie中的csrftoken给服务器,django经过解析,校验这两个token生成的secret是否一致,一致返回True,返回正常界面,不一致则返回403

因为界面中的csrfmiddlewaretoken每次都会发生变化,第三方网站是不可能获取到的,所以就有效防止了csrf攻击

注意:含有csrf_token标签的页面不能用render_to_response方法返回,否则仍然会报403错误,需使用render方法返回

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值