CSRF 跨站请求伪造

2020-05-07  本文已影响0人  代码表演艺术家

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 **CSRF **或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。下面通过一个实例来说明:

假如在某个网站上,用户可以修改自己的邮箱地址,这个修改的操作是通过POST请求到服务器上的一个url,就像这样:

POST /email/change HTTP/1.1
Host: website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE

email=jack@normal-user.com

黑客研究了这个请求的规律后,自己搭建了这样一个页面:

<html>
  <body>
    <form action="https://website.com/email/change" method="POST">
      <input type="hidden" name="email" value="hijack@evil-user.net" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

如果一个受害人进入了这个黑客页面,情况是:

django 的 CSRF防御

django项目默认会在setting 里的MIDDLEWARE 配置里添加csrf的防御中间件 'django.middleware.csrf.CsrfViewMiddleware',原理是这样的:

django在cookie里放置一个键csrftoken,这是一个64位的字符串,每次向服务器提交数据的时候都会带上这个cookie值,同时,在post或put, delete 数据时,数据里要有csrfmiddlewaretoken 这个特殊的数据,在后台django会比较cookie里的csrftoken和请求数据里的csrfmiddlewaretoken是否匹配,如果匹配才会正常处理,否则直接报错。

django可以在form表单通过添加{% csrf_token %}来自动生成如下不可见的csrfmiddlewaretoken字段

<input type="hidden" name="csrfmiddlewaretoken" value="kdA7zoTmrLpZkRU30IsLxNxzJzCxrw9y2pY2OCAzeKteRRi9M9woQknJq2ImxTLN">

这个字段提交到后台时会通过CsrfViewMiddleware中间件来检查,不需要我们操作。

注意:上面提到的检查csrftoken和csrfmiddlewaretoken匹配的操作不是简单的逐字比较是否相同,因为如果你刷新你的表单,会发现表单的csrfmiddlewaretoken在变,而cookie里的csrftoken并没有变。
其实无论是csrftoken还是csrfmiddlewaretoken都是有两部分组成,一个随机的 salt 和 秘文secret,通过对csrftoken和csrfmiddlewaretoken分别进行加盐揭秘,最后比较是否相等。

django ajax请求问题

因为自己在javascript 里构造的ajax请求没有csrfmiddlewaretoken 这个值,所以一般ajax 通过post,put,delele 请求到后台会报csrf缺少错误,怎么办?
网上很多的答案都是简单除暴的去掉csrf校验,比如通过删掉配置文件里的CsrfViewMiddleware中间件来全局禁用csrf,或者使用@csrf_exempt装饰器装饰view来局部禁用csrf校验,
但这些方法都是不安全的,就好像你每次回家都要掏钥匙开锁觉得太麻烦,然后干脆出门不锁门一样,没有真的解决问题,而是逃避问题。

正确的解决方法是在ajax请求里手动加上csrf,
比如在模板里写ajax, 这种比较简单,但是不灵活, 没法写到js 文件里,并且每一个ajax请求都是加上csrfmiddlewaretoken

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },

另一种改进的方法是设置全局的ajax参数,后面的所有请求都不用再添加csrfmiddlewaretoken了

$.ajaxSetup({
  data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

上面两种代码因为使用了django变量,所以还是必须要写在模板文件里,不完美。

完全脱离后端的方法是在js里获取cookie值,如下:

function getCookie(name) {
# 可以使用现成的js 库https://github.com/js-cookie/js-cookie/ 来代替getCookie这个函数
    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();
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

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

通过getCookie('csrftoken')获取cookie里的csrftoken,然后在$.ajaxSetup 设置请求头的X-CSRFToken 值(前面是设置请求体的csrfmiddlewaretoken),
注意这里 function csrfSafeMethod 函数,不会对GET,HEAD,这些请求方式进行csrf保护。这就要求我们平时写代码时一定要遵守HTTP请求规范,不要在GET,HEAD,OPTIONS 这些请求里进行任何修改操作。有些人因为GET请求比较简单,把增删改查都用GET请求操作,虽然功能上可以实现,但是很危险,
举个经典的例子,假如你的网站有个链接是这样的:

href = "myAppDeleteImportantData.aspx?UserID=27"

并且google-bot出现并为您的页面编制索引?那么会发生什么?

上一篇 下一篇

猜你喜欢

热点阅读