什么是CSRF
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击网站执行某项操作的目的。
一个典型的CSRF攻击流程:
- 用户登录a.com,并保留了登录凭证(cookie)
- 攻击者引诱用户访问b.com,如,诱导性文字按钮等
- b.com向a.com发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的cookie
- a.com接收到请求后,对请求进行验证,并确认是受害用户的凭证,误以为是用户自己发送的请求
- a.com以用户名义执行了act=xx
- 攻击完成,攻击者在用户不知情的情况下,冒充用户,让a.com执行了自己定义的操作
例如:
用户A浏览Gmail邮件,因好奇点击了一封“甩卖比特币,只要998”的广告邮件。打开后一片空白,随后用户A关闭了页面。但此时CSRF攻击已经完成,用户A的Gmail邮件将逐一转发到黑客的邮箱。具体是怎么发生的呢,关键在于那个空白页面的源码:
复制代码
这个页面只要一打开就会向Gmail发送一个post请求。请求中,执行了‘Create Filter’命令(自动转发邮件),将所有邮件转发到黑客邮箱hacker@hakermail.com。由于A刚登陆了Gmail,用户登录凭证被保存在cookie中,所以这个空白页发出的post请求就携带了登陆证明,于是成功给用户A配置了过滤器。黑客可以查看所有用户A的邮件,包括一些敏感信息:邮箱验证码等等。此事件原型是2007年Gmail的CSRF漏洞:。目前此漏洞已修复。
几种常见的CSRF攻击
GET类型攻击:http请求时,敏感参数跟在url后面,攻击者很容易获取并发送包含受害者信息的跨域请求。
POST类型攻击:如上案列所示。
链接类型攻击:页面中注入恶意链接,诱导用户点击跳转。
防护策略
据上所示,CSRF通常是由第三方网站发起,因此可以通过增强自身网站的安全性来防御:
- 同源策略
- token验证
同源检测
同源策略上分为两部分:一是http请求接口的同源,还有一个是cookie的domain属性的同源。
1. http请求,可以使用origin header、referer header确定来源域名。
在部分请求中,请求的header中会携带origin字段,代表请求的域名;也有不存在origin的情况,IE11对同源的定义有别于其他浏览器,还有一个就是302重定向之后origin不包含在重定向的请求中,因为重定向请求是定向到新的服务器,避免泄露origin信息。
http请求header中还有另外一个记录该请求来源地址的属性referer。referer值是由浏览器提供的,不可排除浏览器自身安全性问题,和部分情况下攻击者隐藏甚至篡改自己请求的referer。在同源策略下,可以把referer属性设置成same-origin,对于同源的额链接和引用会发送referer,对于跨域则不携带referer。
设置referer的三种方法:在csp设置;页面头部增设meta标签;a标签增加referer属性
跳到百度复制代码
但是,在以下情况下referer没有或者不可信:
- IE6、7使用window.location.href=url进行界面跳转,会丢失referer
- IE6、7使用window.open,也会丢失referer
- https跳到http,所有浏览器都会丢失referer
- 点击flash到达另外一个网站时,referer不可信
2. 在浏览器种下cookie时,若明确cookie的domain属性(一级域名或是子域名),即仅在domain指定的域下的接口才会自动携带cookie,可以解决部分跨域问题,但仍不能避免攻击者伪造的域名和网站同域(本地服务器proxy代理请求时)。还可以设置cookie为Httponly,这样cookie只能在请求中被携带,无法用document.cooke读取、修改。
token验证
这里的token验证也有两种,一种是CSRF token的加解密校验,还有一个是双重Cookie验证。
1. CSRF token
用户打开页面时,服务器会个每个用户生成唯一的token,这个token是经过精密算法对数据进行加密,一般包括随机字符串和时间戳。token会存在服务器的session中。页面提交的请求都会携带这个token,当服务器拿到token后用相应解密算法得到时间戳、及加密字符串,再和服务器session中存储的token信息作比较来判断该请求的有效性,这类token一般是随机字符串。
分布式校验
在大型网站中,使用session存储token会带来很大压力。访问单台服务器session是同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,再路由到具体的服务器上,由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得Session机制在分布式环境下失效,因此在分布式集群中CSRF Token需要存储在Redis之类的公共存储空间。
目前的解决办法就是采用Encrypted Token Pattern方式,计算出一个结果,而不是随机字符串。这样就无需将token存储在session中,只需要在拿到token的时候再计算一次就行。
Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功。其次,验证码和密码其实也有CSRF token的作用。
2. 双重Cookie验证
双重Cookie采用以下流程:
- 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如
csrfcookie=v8g9e4ksfhw
)。 - 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例
POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw
)。 - 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。
此方法相对于CSRF Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便,只需进行请求中字段的对比,而不需要再进行查询和存储Token。
当然,此方法并没有大规模应用,其在大型网站上的安全性还是没有CSRF Token高,原因我们举例进行说明。
由于任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情况:
- 如果用户访问的网站为
www.a.com
,而后端的api域名为api.a.com
。那么在www.a.com
下,前端拿不到api.a.com
的Cookie,也就无法完成双重Cookie认证。 - 于是这个认证Cookie必须被种在
a.com
下,这样每个子域都可以访问。 - 任何一个子域都可以修改
a.com
下的Cookie。 - 某个子域名存在漏洞被XSS攻击(例如
upload.a.com
)。虽然这个子域下并没有什么值得窃取的信息。但攻击者修改了a.com
下的Cookie。 - 攻击者可以直接使用自己配置的Cookie,对XSS中招的用户再向
www.a.com
下,发起CSRF攻击。
参考: