1.1. 定义
**跨站请求伪造**(英语:Cross-site request forgery),也被称为 **one-click attack** 或者 **session riding**,通常缩写为 **CSRF** 或者 **XSRF**, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨站脚本(XSS)
相比,**XSS** 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
跨站请求伪造攻击,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个用户自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了web中用户身份验证的一个漏洞:**简单的身份验证只能保证请求是发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的**。
Note
简单来说就是你点击我构造的恶意链接,我就可以以你的名义去发起一个http请求
1.2. 举例
- 假如X银行用以执行转账操作的URL地址如下
```
https://bank.example.com/withdraw?amount=1000&to=PayeeName
```
- 一个恶意攻击者在另一个网站中
https://evil.com/
中放置如下代码
```html
<img src="https://bank.example.com/withdraw?amount=1000&to=Bob" />
```
- 如果有登陆了X银行的用户访问恶意站点
https://evil.com/
,那么就会携带cookie去请求对应的转账URL,向Bob
转账1000元
Note
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方,**只要能让受害者发起对应的请求即可**,如上述中的转账请求。
攻击者也不需要控制放置恶意代码的网站,例如他可以将这种地址藏在各大论坛,博客等任何用户生成内容的网站中,这意味着**如果服务端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险**。
通过例子也能够看出,攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是**欺骗用户的浏览器,让其以用户的名义执行操作**。
1.3. 攻击流程
具体的攻击流程如下:
- 用户正常登录web服务,并一直保持在线
- 服务器返回用户凭证Session ,并将其保存在Cookie中
- 攻击者生成payload,并放置在用户可访问的地方
- 攻击者诱导用户点击在第3步放置的链接,此时用户一直在线,且是用同一浏览器打开(保证Cookie未失效)
- 用户点击恶意链接
- 恶意链接向服务器请求,由于用户Cookie未失效,就携带用户Cookie访问服务器
- 服务器收到请求,此时用户Cookie 未失效,并判定为â**用户**â发起的正常请求,并做出响应
1.4. 分类
1.4.1. GET型
这种是最容易利用的,相比于POST型来说,攻击面也大很多,比如上述CSRF转账例子中就是GET型的
在web应用中,很多接口通过GET进行数据的请求和存储,如果未对来源进行校验,并且没有token保护,攻击者可以直接通过发送含有payload的链接进行诱导点击;亦可以通过评论区或类似功能处发布图片,通过修改img地址的方式保存至页面,用户访问便会进行自动加载造成攻击
<!-- 不论什么手段,只要能让受害者访问一个链接即可 -->
<img src="https://bank.example.com/withdraw?amount=1000&to=Bob" />
1.4.2. POST-表单型
相比于GET型,这种就要多很多,因为很多开发在提交数据的功能点时都会采用POST
,如创建用户、创建文章、发消息等,利用起来也相对麻烦点
Note
测试时,为了扩大危害,可以尝试将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>
1.4.3. POST-JSON型
现在越来越多的系统都采用RESTful
风格开发,前后端分离,ajax请求后端获取数据再到前端渲染,所以上述表单型也越来越少了
如果我们发现请求头中的Content-Type
值是application/json
,基本上就可以确定采用了前后端分离了
这种一般有4â£ï¸种利用手法:
- json转param
- 闭合JSON
- ajax发起请求
- flash 307跳转
json转param
部分网站可能同时支持json和表单格式,所以我们可以尝试进行转换,也算是一个小tips吧
如把 {"a":"b"}
转换为 a=b
,服务端可能也会解析
闭合JSON
这种要求对**Content-Type**没有限制,比如传输的数据为 {"a":"b"}
,那么我们就可以构造一个表单
<form action=http://test.example.com/csrf method=POST>
<!-- 重点是下面这一行 -->
<input type="hidden" name='{"a":"' value='b"}' />
</form>
<script> document.forms[0].submit(); </script>
这样自动提交表单的时候,提交的data就是 {"a":"=b"}
,闭合成了json
Note
实际环境中本人没遇到过,基本上遇到的都是强制要求Content-Type
为json
ajax发起请求
- XMLHttpRequest跨域预检
当跨域影响用户数据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
,所以很多情况下这是可用的
<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的跨域与307跳转来绕过http自定义头限制,307跟其他3XX HTTP状态码之间的区别就在于,HTTP 307可以确保重定向请求发送之后,请求方法和请求主体不会发生任何改变。HTTP 307会将POST body和HTTP头重定向到我们所指定的最终URL,并完成攻击
详情参考该系列我的另一片文章:一次XSS和CSRF的组合拳进攻(CSRF JSON)
1.5. 挖掘
算是一些挖掘经验吧,很多小伙伴都知道这个漏洞,但是不知道如何挖掘。
1.5.1. 应用场景
其实所有需要登陆认证且存在操作的地方,都可能存在CSRF;比如修改个人信息、发送邮件、创建管理员用户等等,只能查看的功能不考虑,因为不能算真正利用
1.5.2. 如何快速验证
Tip
观察数据包,如果header头和data中都没有token,然后尝试删除referer,还是能成功发送请求的话,就可以确定存在CSRF漏洞了
为了保险起见,在时间充足的情况下,还是需要尽量通过POC验证下,一般不需要2个账号进行验证,一个账号即可(2个只能说更保险)
非json的情况下,使用burp可以快速生成POC,也可以自己写,反正原理都是发起请求即可
登陆账号的情况下去访问这个poc,如果能成功得到自己的结果,就是OK的。
1.6. 防御
WEB的身份验证机制可以保证一个请求是来自于哪个用户的浏览器,但是却不能保证请求是否由本人发起的,所以修复和防御也是保证请求由用户本人发起即可。
Tip
简单来说,或者和客户沟通的情况下,直接说修复方法就是**防止请求重放**,他们开发也差不多都知道怎么修了
1.6.1. 令牌同步模式
令牌同步模式(英语:Synchronizer token pattern,简称STP)。
**原理是:**当用户发送请求时,服务器端应用将令牌(token:一个保密且唯一的值)嵌入HTML表格,并发送给客户端。客户端提交HTML表格时候,会将令牌发送到服务端,再由服务端对令牌进行验证。令牌可以通过任何方式生成,只要确保**随机性和唯一性**。这样确保攻击者发送请求时候,由于没有该令牌而无法通过验证。(没有token不能重放数据包)
<input type="hidden" name="\_csrf\_token" value="YidlXHhlMVx4YmJceDkxQFx4OTdceDg5a1x4OTJcbic=">
Note
STP能在HTML下运作顺利,但会导致服务端的复杂度升高,复杂度源于令牌的生成和验证。因为令牌是唯一且随机,如果每个表格都使用一个唯一的令牌,那么当页面过多时,服务器由于生产令牌而导致的负担也会增加。而使用会话(session)等级的令牌代替的话,服务器的负担将没有那么重。
1.6.2. 检查Referer字段
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,**通常来说,Referer字段应和请求的地址位于同一域名下**。
以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于bank.example.com
之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于bank.example.com
之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。
1.6.3. 添加校验token
1.6.4. 一次一用验证码
在关键操作处添加一次一用的验证码,攻击者无法事先知道验证码的值,也就无法成功构造发起请求的数据包。
需要用户交互,如果很多地方都加上,用户体验极差,所以一般不建议这个
1.6.5. 使用SameSite Cookie
- 如果Samesite Cookie被设置为
Strict
,浏览器在任何跨域请求中都不会携带Cookie,新标签重新打开也不携带,所以说CSRF攻击基本没有机会;但是跳转子域名或者是新标签重新打开刚登陆的网站,之前的Cookie都不会存在。尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都需要重新登录。对于用户来讲,可能体验不会很好。 - 如果Samesite Cookie被设置为
Lax
,那么其他网站通过页面跳转过来的时候可以使用Cookie,可以保障外域连接打开页面时用户的登录状态。但相应的,其安全性也比较低。
1.7. 个人预防
网站如果存在CSRF漏洞,个人一般要如何操作才能防止攻击到自己呢?
- 尽量每次使用隐私浏览器,因为其关闭后会清空所有的cookie
- 不要随便打开链接,一定要打开的情况下,可以使用隐私浏览器