即便是使用最新的浏览器,黑客无法篡改 Referer 值,这种方法仍然有问题 。因为 Referer 值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心 Referer 值会把组织内网中的某些信息泄露到外网中 。因此,用户自己可以设置浏览器使其在发送请求时不再提供 Referer 。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是 CSRF 攻击,拒绝合法用户的访问 。
在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证 。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中 。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求 。
这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求 。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue 。而对于 POST 请求来说,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=https://www.isolves.com/it/aq/wl/2019-11-26/”tokenvalue”/>,这样就把 token 以参数的形式加入请求了 。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 JAVAscript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token 。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token 。
该方法还有一个缺点是难以保证 token 本身的安全 。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址 。由于系统也会在这个地址后面加上 token,黑客可以在自己的网站上得到这个 token,并马上就可以发动 CSRF 攻击 。为了避免这一点,系统可以在添加 token 的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加 token,如果是通向外网则不加 。不过,即使这个 csrftoken 不以参数的形式附加在请求之中,黑客的网站也同样可以通过 Referer 来得到这个 token 值以发动 CSRF 攻击 。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因 。
在 HTTP 头中自定义属性并验证
这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里 。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中 。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去 。
然而这种方法的局限性非常大 。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便 。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的 。
防御实例
Sailajs框架中的JavaScript 代码示例
下面将以 JavaScript 为例,对上述三种方法分别用代码进行示例 。
1.验证 Referer
// 从 HTTP 头中取得 Referer 值 var referer=req.headers.Referer;// 判断 Referer 是否以 test.example 开头 if((referer!=null) && (_.startsWith(referer, “test.example ”)){// 验证通过 }else{// 验证失败,返回错误 }2.验证请求中的 token
var session = req.session; // 从 session 中得到 csrftoken 属性 var token = session.csrftoken;if(token == null){// 产生新的 token 放入 session 中 token = generateToken();token.csrftoken = token ;return .... } else{// 从 HTTP 头中取得 csrftokenvar token = req.headers.csrftoken;// 从请求参数中取得 csrftokenvar xhrToken = req.param('csrftoken');if(token != null && xhrToken != null && token.equals(xhrToken)){return ... }else{return ... // ERROR }}HTTP 头中自定义属性(网上例子)var plainXhr = dojo.xhr;// 重写 dojo.xhr 方法 dojo.xhr = function(method,args,hasBody) {// 确保 header 对象存在 args.headers = args.header || {};tokenValue = https://www.isolves.com/it/aq/wl/2019-11-26/'';var token = dojo.getObject("tokenValue");// 把 csrftoken 属性放到头中 args.headers["csrftoken"] = (token) ? token : " ";return plainXhr(method,args,hasBody);};
推荐阅读
- hook php中钩子的原理与简单应用
- 局域网内使用路由器来防止ARP攻击
- 八种网络攻击手段详解
- 忍冬藤的功效与作用有哪些
- 鸵鸟养殖技术与利润 养殖100只鸵鸟的利润与成本
- 普洱茶与湖南黑茶的内质区别在哪里
- 成都故事,历史上的西蜀茶业与茶马古道
- 协议离婚、调解离婚与判决离婚的区别
- 淘宝开店快递费用 淘宝店与快递公司合作收费是怎么收的?
- 中国A级茶叶标准高于国际与欧盟