什么是 CSRF 漏洞
CSRF(Cross Site Request Forgery,跨站请求伪造,也叫 XSRF)漏洞是由于未校验请求来源,导致攻击者可在第三方站点发起 HTTP 请求,并以受害者的目标网站登录态(cookie、session 等)请求,从而执行一些敏感的业务功能操作,比如更改密码、修改个人资料、关注好友。
从以上可以得知,漏洞比较依赖业务功能。有时虽然存在 CSRF 但并没有实际危害,也不能算是真正意义上的 CSRF 漏洞。比如常规的登录账号功能,如果你不知道密码就无法登录,而如果知道了,那还需要构造 CSRF 请求吗?如果是为了实现多次登录失败,令目标账号暂时无法登录,那么也不需要用 CSRF。如果是一些发消息、发微博的功能,那同样可以产生蠕虫效果,比如微博就曾发生过好多次此类 CSRF 蠕虫漏洞。
CSRF 分类
从漏洞利用角度来分类的话,CSRF 可以分为 CSRF 读与 CSRF 写。
- CSRF 读:通过伪造请求来获取返回的敏感信息,比如用户资料;常见的就是 JSON 劫持,以及利用 Flash API加载页面获取敏感信息。
- CSRF 写:通过伪造请求去修改网站数据,比如修改密码、发表文章、发送消息等操作。
CSRF 的攻击手法
以 DVWA 中的 CSRF 题目(Security Level 设置为 Low)为例
输入密码提交的同时抓包。此处直接用 Chrome 的 Network 功能,抓包后发现只是个 GET 请求,那利用起来就简单了:直接构造以下链接发给受害者,受害者点击后就会被修改密码为你设置的密码。如下所示:
http://127.0.0.1/vulnerabilities/csrf/?password_new={你设置的密码}&password_conf={你设置的密码}&Change=Change
像这种 GET 型的 CSRF 漏洞利用就是仅需要修改下原 GET 的参数值,构造个链接发给对方就可以了;甚至直接使用图片链接嵌入到受害者可能访问的页面(博客、论坛、邮件等等),也可以实现漏洞的利用,这种利用方式更加隐蔽。
如果是 POST 请求,就需要编写利用代码,用 JS 脚本去实现自动提交表单,然后把它放在自己控制的服务器上。假设存放地址为 http://hacker.com/exploit.html ,再生成短网址 http://dwz.date/d74a 发给受害者。
exploit.html 利用代码如下:
<html>
<form name = "test" action = "http://127.0.0.1/vulnerabilities/csrf" method = "post" >
<input type = "hidden" value="hacker" name="password_new" >
<input type = "hidden" value="hacker" name="password_conf" >
<input type = "hidden" value="Change" name="Change" >
</form>
<script>document.test.submit();</script>
</html>
这里是就模拟原网站的表单提交内容设置的,每个 input 都添加 type=“hidden” 的属性是为了不在网页中显示,最后再利用 document.test.submit() 去自动提交表单,其中 “test” 是指 form 名单。
如果受害者访问上述包含 exploit.html 的 http://dwz.date/d74a ,就会发起以下请求:
上述的利用方式主要介绍的是 POST 类型的利用手法。
JSON 劫持攻击
JSON(JavaScript Object Notation,JavaScript 对象符号)是一种可以序列化任意数据,并能被 JavaScript 注释器直接处理的简单数据交换格式。我们来看一段 JSON 格式的包含用户信息的数据:
{
"people":[
{
"name": "Brett",
"email":"Brett@qq.com"
},
{
"name":"Jason",
"Jason@qqx.com"
}
]
}
JSON 劫持是一种特殊的 CSRF 攻击方式,本质上也是未对请求来源做有效校验导致的,它主要是用来窃取服务器返回的敏感信息。
实现 JSON 劫持主要有两种攻击方式:覆写数据构造器和执行回调函数。
覆写数据构造器
若服务端返回的 JSON 数据中包含一个序列化数组,那攻击者就可以重定义数组构造器,以实现 JSON 数据的访问。比如 2006 年的 Gmail 就曾出现过 JSON 劫持联系人列表的漏洞,漏洞 CGI 位于:
http://mail.google.com/mail/?url_scrubbed
它会返回联系人列表的 JSON 数据。
[[“ct”,“Your Name”,“foo@gmail.com”], [“ct”,“Another Name”,“bar@gmail.com”] ]
因此,可通过覆盖数组构造器来读取 JSON 数据。
从以上代码中我们可以总结整个攻击流程:
- 通过<script>加载目标 JSON 对象到页面中;
- 覆写 Array 对象,并设置数组元素的 setter 为 getNext 函数,有时也可以使用Object.prototype.__defineSetter__来覆盖 setter;
- getNext 函数读取包含联系人信息的 JSON 信息。
执行回调函数
不同域名之间传递数据时,无法通过 JavaScript 直接跨域访问,因此需要在访问脚本的请求中指定一个回调函数,用于处理 JSON 数据。正因如此,攻击者也可以利用它来劫持其他域返回的数据。这种攻击方式是当前 JSON 劫持中最为常见的方式。
以前的 QQ 网购就曾出现过这种 JSON 劫持漏洞,其利用代码如下:
<html>
<body>
<script>
function any(obj){
alert(obj);
}
</script>
<script src='http://act.buy.qq.com/w/newbie/queryisnew?callback=any' ></script>
</body>
</html>
通过 callback 参数指定返回数据的处理函数 any,在 any 函数中,你可以根据 JSON 数据内容执行特定的处理,获取你想要的数据然后回传到自己控制的服务器上。
CSRF 检测方法:
通过前面对 CSRF 原理的讲解,测试思路就很容易了:
- 抓包记录正常的 HTTP 请求;
- 分析 HTTP 请求参数是否可预测,以及相应的用途;
- 去掉或更改 referer 为第三方站点,然后重放请求;
- 判断是否达到与正常请求的同等效果,若是则可能存在 CSRF 漏洞,反之则不存在。
自动化的测试思路也是一样的实现方法,只不过很多时候不知道请求参数的实际用途,比较难评估其危害和价值。正如前面所说的,CSRF 的危害取决于参数用途,这也导致很多时候需要人工验证,不然很容易误报。我个人认为,目前没有特别好的自动化 CSRF 检测工具,大多是一些半自动的辅助类工具,比如 BurpSuite 上的 CSRF PoC 生成功能。
防御 CSRF
防御 CSRF 的关键思路就是令请求参数不可预测,所以常用的方法就是在敏感操作请求上使用 POST 代替 GET,然后添加验证码或 Token 进行验证。
这里不推荐 referer(即请求头中的来源地址)限制方法,因为通过 javascript:// 伪协议就能以空 referer 的形式发起请求,很容易绕过限制。如果你直接禁止空 referer,一些移动 App 上的请求又可能无法完成,因为移动 App 上的 http/https 请求经常是空 referer。
验证码
在一些重要的敏感操作上设置验证码(短信、图片等等),比如更改密码(此场景下也可要求输入原密码,这也是不可预测值)、修改个人资料等操作时。
Token 验证
对于 CSRF 的防御,Token 验证无疑是最常用的方法,它对用户是无感知的,体验上比验证码好太多了。
在提交的表单中,添加一个隐藏的 Token,其值必须是保证不可预测的随机数,否则没有防御效果。下面是服务器生成并返回给当前用户的:
提交表单后,会连同此 Token 一并提交,由服务器再做比对校验。
生成 Csrf Token 的算法,常常会取登录后 cookie 中的某值作为输入,然后采用一些加密/哈希算法生成,这也是为了方便后台校验和区分用户。
除了 Cookie Token,还可以使用伪随机值的 Session Token,即服务端生成一个伪随机数,存储到 $_SESSION 中,然后返回给用户的页面中隐藏此 Token;等用户提交后,再拿它与存储在 $_SESSION 的 Token 值比较。这是当前比较常用的 Token 生成与校验方式。
总结下,先由服务端生成随机数作为 Token,然后存储到 Session 中,不一定都非要存储到 Cookie 中,然后在返回给用户的表单中插入隐藏的 Token,用户提交后,由服务器来比对提交的 Token 与 Session 中的 Token 是否一致,以此判断请求是否合法。