CSRF(Cross-site request forgery跨站请求伪造)是一种对网站的恶意利用,在2007年曾被列为互联网20大安全隐患之一。
关于CSRF,要从一个故事开始。
老王丢钱事件
这个故事要从程序员老王丢了1万块钱说起,总之是进了小偷,找回无果。丢钱后的老王一直在思考,钱是怎么丢的、为何丢钱、为何是我丢钱。
后来老王出现了严重的心理问题,他决定报复社会。
老王首先研究了网银系统,他发现转账是通过GET形式
https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=abei
这意思就是说将liuxiaoer的1000元钱转给abei,当然当请求到达银行服务器后,程序会验证该请求是否来自合法的session并且该session的用户就是liuxiaoer并且已经登录。
老王自己也有一个银行账号wang2,他尝试登录并且通过浏览器发送请求给银行,代码如下
https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2
但是失败了。因为当前登录账号是老王自己,发送请求后服务器发现session所属用户wang2和account=liuxiaoer并不是一个人,因此被拒绝。
也就是说这个操作必须是liuxiaoer自己才可以,复仇的力量是可怕的,老王通过facebook层层找到了liuxiaoer就是快递员老刘的银行账号。
于是一个伟大的计划诞生了,老王的计划是这样的。
- 首先做一个网页,在网页中加入如下代码
src="https://bank.abc.com/withdraw?account=liuxiaoer&amount=1000&to=wang2"
然后通过各种情景让老刘(liuxiaoer)访问了此网页。
- 当老刘(liuxiaoer)访问此网页后,上面的请求会被发送到银行,此刻还会带着老刘(liuxiaoer)自己的浏览器cookie信息,当然这样一般也不会成功,因为银行服务器发现老刘(liuxiaoer)并不在登录状态。
- 老王想了一个招,他在淘宝找了一个灰色商人老李,让他通过种种方法,总之让老刘(liuxiaoer)通过浏览器给老李转了一次款。
- 就在第三步操作的2分钟内,老王成功让老刘(liuxiaoer)再一次访问了自己做的网页,你知道的,此刻老刘(liuxiaoer)在银行的session还没有过期,老王网页给银行服务器发送请求后,验证通过,打款成功。
- 老王收到了款,老刘(liuxiaoer)并不知道这一切,对于银行来说这是一笔在正常不过的转账。
这就是CSRF攻击,浏览器无法拦截。
CSRF攻击特点
基于上面血淋淋的故事,我们总结下CSRF攻击的几个特点。
- 黑客借助于受害者的cookie等浏览器信息骗取服务器新人,黑客并拿不到cookie等。
- 由于浏览器同源策略,黑客无法拿到攻击的响应结果,能做的只是发起请求。
- CSRF攻击主要是发送修改数据请求。
因此我们要保护的是所有能引起数据变化的客户端请求,比如新建、更新和删除。
CSRF防御方案
基于CSRF攻击特点,在业界目前防御CSRF攻击主要有三种策略:
- 验证HTTP Referer字段;
- 在请求地址中添加token并验证;
- 在HTTP头中自定义属性并验证。
HTTP Referer
在http请求的时候,头部有一个叫做Referer的字段,该字段记录本次请求的来源地址。因此服务器端可以通过此字段是否为同一个域名来判断请求是否合法,因为客户自己做的网页发起的请求,其Referer为黑客网站。
这种方法最简单,并且不需要修改业务代码,我们只需要对到达服务器的每个请求做一次拦截分析即可。
但是此方法的缺点也是明显的,因为Referer的值是浏览器的,虽然HTTP协议不允许去修改,但是如果浏览器自身存在漏洞,那么就有可能导致Referer被人工设置,不安全。
比如IE6,就可以通过方法篡改Referer值。
就算是最新的浏览器此方法也不是绝对可用的,这涉及了用户的隐私,很多用户会设置浏览器不提供Referer,因此服务器在得不到Referer的情况下不能贸然的决绝服务,有可能这是一个合法请求。
添加Token
CSRF攻击之所以能成功,是因为黑客完全伪造了一次用户的正常请求(这也是浏览器无法拦截的原因),并且cookie信息就是用户自己的,那么我们如果在请求中放入一些黑客无法去伪造的信息(不存在与cookie中),不就可以抵御了么!
比如在请求前生成一个token放到session中,当请求发生时,将token从session拿出来和请求提交过来的token进行对比,如果相等则认证通过,否则拒绝。token时刻在变化,黑客无法去伪造。
针对于不同类型的请求一般方案是
- GET放到url中
- POST放到表单的隐藏域
在HTTP头部增加属性
这个方法在思路上和上面的token方式一样,只不过将token放到了HTTP头部中,不再参数传递,通过XMLHttpRequest类可以一次性的给所有请求加上csrftoken这个HTTP头属性并设置值。
这种方法适合上面批量添加token不方便的情况,一次性操作,不过局限性也比较大,XMLHttpRequest请求通常用在ajax方法中,并非所有请求都适合。