一文带你学习跨站点请求伪造(CSRF)
1.何为CSRF
CSRF的全名是Cross Site Request Forgery,翻译成中文就是跨站点请求伪造
CSRF是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任
例如现在有一个网站,只需要请求这个URL,就能够把编号为“156713012”的博客文章删除。
http://blog.sohu.com/manage/entry.do? m=delete&id=156713012
攻击者首先在自己的域构造一个页面:
其内容为:
<img src="http://blog.sohu.com/manage/entry.do? m=delete&id=156714243" />
攻击者诱使目标用户访问这个页面,从而执行CSRF攻击
该用户看到了一张无法显示的图片,但是不幸的式此时该用户的博客已经被删除
回顾整个攻击过程,攻击者仅仅诱使用户访问了一个页面,就以该用户身份在第三方站点里执行了一次操作。
这个删除博客文章的请求,是攻击者所伪造的,所以这种攻击就叫做“跨站点请求伪造”。
CSRF漏洞的根本是:简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的💄
2.浏览器的Cookie策略
浏览器所持有的Cookie分为两种:一种是“Session Cookie”,又称“临时Cookie”;另一种是“Third-party Cookie”,也称为“本地Cookie”。
两者的区别在于,Third-party Cookie是服务器在Set-Cookie时指定了Expire时间,只有到了Expire时间后Cookie才会失效,所以这种Cookie会保存在本地;而Session Cookie则没有指定Expire时间,所以浏览器关闭后,Session Cookie就失效了。
例如:
<? php
header("Set-Cookie: cookie1=123; ");
header("Set-Cookie: cookie2=456; expires=Thu, 01-Jan-2030 00:00:01 GMT; ", false);
?>
IE出于安全考虑,默认禁止了浏览器在
<img>、<iframe>、<script>、<link>
等标签中发送第三方Cookie
攻击者则需要精心构造攻击环境,比如诱使用户在当前浏览器中先访问目标站点,使得Session Cookie有效,再实施CSRF攻击❌
在当前的主流浏览器中,默认会拦截Third-party Cookie的有:IE 6、IE 7、IE 8、Safari;不会拦截的有:Firefox 2、Firefox 3、Opera、Google Chrome、Android等。
3.P3P头的副作用
浏览器拦截第三方Cookie的发送,在某种程度上来说降低了CSRF攻击的威力。可是这一情况在“P3P头”介入后变得复杂起来。
P3P Header是W3C制定的一项关于隐私的标准,全称是The Platform for Privacy Preferences。
如果网站返回给浏览器的HTTP头中包含有P3P头,则在某种程度上来说,将允许浏览器发送第三方Cookie。在IE下即使是
<iframe>、<script>
等标签也将不再拦截第三方Cookie的发送💍
在网站的业务中,P3P头主要用于类似广告等需要跨域访问的页面。
正因为P3P头目前在网站的应用中被广泛应用,因此在CSRF的防御中不能依赖于浏览器对第三方Cookie的拦截策略,不能心存侥幸。
很多时候,如果测试CSRF时发现<iframe>
等标签在IE中居然能发送Cookie,而又找不到原因,那么很可能就是因为P3P头在作怪。
4.CSRF攻击流程
具体的攻击流程如下:
- 用户正常登录web服务,并一直保持在线
- 服务器返回用户凭证Session ,并将其保存在Cookie中
- 攻击者生成payload,并放置在用户可访问的地方
- 攻击者诱导用户点击在第3步放置的链接,此时用户一直在线,且是用同一浏览器打开(保证Cookie未失效)
- 用户点击恶意链接
- 恶意链接向服务器请求,由于用户Cookie未失效,就携带用户Cookie访问服务器
- 服务器收到请求,此时用户Cookie 未失效,并判定为“用户”发起的正常请求,并做出响应
5.CSRF的分类
GET型
在web应用中,很多接口通过GET进行数据的请求和存储,如果未对来源进行校验,并且没有token保护,攻击者可以直接通过发送含有payload的链接进行诱导点击;亦可以通过评论区或类似功能处发布图片,通过修改img地址的方式保存至页面,用户访问便会进行自动加载造成攻击
<!-- 不论什么手段,只要能让受害者访问一个链接即可 -->
<img src="https://bank.example.com/withdraw?amount=1000&to=Bob" />
POST-表单型
首先,测试时,为了扩大危害,可以尝试将POST数据包转换成GET数据包,后端如果采用例如@RequestMaping("/")
这种同时接受POST和GET请求的话,就可以成功
利用起来无非也是构造一个自动提交的表单,然后嵌入到页面中,诱导受害者访问,受害者访问后会自动提交表单发起请求
<form action=http://bank.example.com/csrf method=POST>
<input type="text" name="amount" value="1000" />
</form>
<script> document.forms[0].submit(); </script>
POST-JSON型
如果我们发现请求头中的Content-Type
值是application/json
,基本上就可以确定采用了前后端分离了
这种一般有4种利用手法:
- json转param
部分网站可能同时支持json和表单格式,所以我们可以尝试进行转换,如把 {"a":"b"}
转换为 a=b
,服务端可能也会解析
- 闭合JSON
构造闭合语句,比较鸡肋
- ajax发起请求
当跨域影响用户数据HTTP请求(如用XMLHttpRequest发送get/post)时,浏览器会发送预检请求(OPTIONS请求)给服务端征求支持的请求方法,然后根据服务端响应允许才发送真正的请求。
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: http://localhost:63342
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1800
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: content-type,access-control-request-headers,access-control-request-method,accept,origin,x-requested-with
Content-Length: 0
Date: Wed, 11 Mar 2015 05:16:31 GMT
然而如果服务端对Content-Type
进行校验,则不会响应这个OPTIONS请求,从而利用失败。但是更多的情况下服务端可能不会校验Content-Type
,或者不会严格校验Content-Type
是否为application/json
,所以很多情况下这是可用的
payload:
<script>
windows.onload = () => {
var xhr = new XMLHttpRequest()
xhr.open("POST", "http://test.example.com/csrf")
xhr.setRequestHeader("Accept", "*/*")
xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
xhr.withCredentials = true // 携带cookie
xhr.send(JSON.stringify({"a":"b"})
}
</script>
- flash+307跳转
flash已经被淘汰了
6.CSRF的快速验证
非JSON的情况下,使用burp可以快速生成POC,也可以自己写,反正原理都是发起请求即可
修改参数,之后通过BP的浏览器进行测试:
访问BP生成的网址:
提交,可以看到数据已经被修改了,代表存在CSRF漏洞:
7.CSRF的防御
验证码
验证码被认为是对抗CSRF攻击最简洁而有效的防御方法。
CSRF攻击的过程,往往是在用户不知情的情况下构造了网络请求。而验证码,则强制用户必须与应用进行交互,才能完成最终请求。因此在通常情况下,验证码能够很好地遏制CSRF攻击。但是验证码并非万能。
很多时候,出于用户体验考虑,网站不能给所有的操作都加上验证码。因此,验证码只能作为防御CSRF的一种辅助手段,而不能作为最主要的解决方案。
Referer Check
Referer Check在互联网中最常见的应用就是“防止图片盗链”。同理,Referer Check也可以被用于检查请求是否来自合法的“源”。
比如一个“论坛发帖”的操作,在正常情况下需要先登录到用户后台,或者访问有发帖功能的页面。在提交“发帖”的表单时,Referer的值必然是发帖表单所在的页面。如果Referer的值不是这个页面,甚至不是发帖网站的域,则极有可能是CSRF攻击。
无法依赖于Referer Check作为防御CSRF的主要手段(黑客可能篡改Referer)。但是通过Referer Check来监控CSRF攻击的发生,倒是一种可行的方法💎
Anti CSRF Token
CSRF为什么能够攻击成功?其本质原因是重要操作的所有参数都是可以被攻击者猜测到的。
出于这个原因,可以想到一个解决方案:把参数加密,或者使用一些随机数,从而让攻击者无法猜测到参数值。
这样,在攻击者不知道salt的情况下,是无法构造出这个URL的,因此也就无从发起CSRF攻击了
比如,一个删除操作的URL是:
http://host/path/delete? username=abc&item=123
把其中的username参数改成哈希值:
http://host/path/delete? username=md5(salt+abc)&item=123
但是这个方法也存在一些问题。首先,加密或混淆后的URL将变得非常难读,对用户非常不友好。其次,如果加密的参数每次都改变,则某些URL将无法再被用户收藏。最后,普通的参数如果也被加密或哈希,将会给数据分析工作带来很大的困扰,因为数据分析工作常常需要用到参数的明文。
因此,我们需要一个更加通用的解决方案来帮助解决这个问题。这个方案就是使用Anti CSRF Token。
回到上面的URL中,保持原参数不变,新增一个参数Token。这个Token的值是随机的,不可预测:
http://host/path/delete? username=abc&item=123&token=[random(seed)]
由于Token的存在,攻击者无法再构造出一个完整的URL实施CSRF攻击。