一.CSRF(跨站请求攻击)攻击的原理:
1.攻击原理:
- 网站使用
cookie
来存放用户的登录凭证 cookie
会在同源的http请求中自动携带
2.基本攻击过程:
- 用户登录了受信任网站A,并在本地生成网站A的
cookie
- 不登出网站A(A网站的
cookie
还有效),访问了危险网站B - 危险网站B里面隐藏了一些访问A的接口,用户在访问B网站的时候,就会不自觉的去调用访问A网站的接口,这时候因为A网站的
cookie
还有效,所以危险网站B里面隐藏的对A的接口就能访问成功
二.CSRF攻击示例:
1.示例:
- 例如,
aaa.com
这个网页都是通过session_id
来记录用户的登录状态的 aaa.com
页面上有一个对作品点赞的功能,点赞提交地址为aaa.com/api.like?id=777
- 小明已经登录了
ww.aaa.com
,自然aaa.com
这个网站就会将小明的登录状态session_id
存在cookie
中 - 黑客自己创建了另外一个网站
bbb.com
,并在页面中放了这样一个元素<img src="aaa.com/api.like?id=888">
,这样的话,一旦用户进入这个bbb.com
页面,就会请求aaa.com
这个网站的点赞接口aaa.com/api.like?id=888
,而且点赞的用户对象是888
- 小明登录了
aaa.com
,且表明身份信息的cookie
还没有失效 - 最后因为小明的登录信息尚未过期,访问时就会带上
aaa.com
的cookie(该过程详见 这里),那就等于给id为888
这个作品点赞了
2.常见的CSRF攻击手段总结:
- 欺骗受害用户完成该用户权限许可的任意操作,例如:
- 获取用户的隐私数据——诱骗用户调用获取隐私数据的接口
- 更改用户账号的内容——诱骗用户调用修改账号信息的接口
- 购物消费——诱骗用户调用消费接口
- ...
- 配合其他漏洞攻击
- CSRF蠕虫——即产生蠕虫效果,使CSRF攻击一传十,十传百
- 示例:有一个聊天网站A,它的 ++获取好友列表接口++ 和 ++私信好友接口++ 都存在CSRF漏洞,攻击者可以将其组合成一个CSRF蠕虫:
- 用户已经登录了网站A,且不登出
- 黑客发布一个危险网站B,用户访问时,就会被诱骗访问获取好友列表接口,获取好友的信息
- 然后再利用私信好友的漏洞,诱骗用户给每个好友发送指向网站B的信息
- 只要有好友查看了这条私信信息里面的链接,CSRF蠕虫就会不断的传播下去
- 示例:有一个聊天网站A,它的 ++获取好友列表接口++ 和 ++私信好友接口++ 都存在CSRF漏洞,攻击者可以将其组合成一个CSRF蠕虫:
三.CSRF的接口攻击类型:
CSRF不仅针对GET请求,其他的类型的请求都可被利用来攻击
1.GET类型(最简单的)
- 方式1:借助一些自动发起请求的元素
- 例如,在访问含有这个img的页面后,成功向
http://wooyun.org/csrf?xx=11
发出了一次HTTP请求
- 例如,在访问含有这个img的页面后,成功向
// 只要设置这个图片的宽高为0,用户是根本感觉不到这个`<img>`元素存在
<img src=http://wooyun.org/csrf?xx=11 />
复制代码
-
方式2:页面里面放入一个自动提交的表单,模拟一次GET请求
-
方式3:页面内部自动发起一个get方法的ajax请求
2.POST类型
- 方式1:页面里面放入一个自动提交的表单,模拟一次POST请求
<form action=http://wooyun.org/csrf.php method=POST>
<input type="text" name="xx" value="11" />
</form>
<script> document.forms[0].submit(); </script>
复制代码
- 方式2:页面内部自动发起一个post方法的ajax请求
- 方式3:页面以GET请求发起,后台接受到后再以POST方式转发
四.规避攻击的方法:
1.后端判断referer是否合法(不推荐)
Referer
记录了HTTP请求的来源地址- 一般情况下,受一个页面安全限制的请求都是来源于这个网站
- 通过HTTP的referer可知道,用户是通过哪个网站发送这个请求的。
// koa服务器检查Referer示例:
app.use(async (ctx, next) => {
let referer = ctx.headers.Referer;
// 验证Referer是否是以 test.example 开头的
if((referer != null ) && (_.startsWith(referer, "test.example")) {
// 验证通过
await next();
} else {
验证失败,返回错误
ctx.status = 401;
return ctx.body = {
err: '危险的请求,拒绝访问'
}
}
})
复制代码
- 注意: Referer的判断并不是好方法,有很多缺陷,具体可见 这里
- 某些浏览器,例如
IE6
、FF2
都是可以自己设置Referer值的 - 因为Referer会记录用户的访问记录,侵犯隐私,因此在最新的浏览器中,用户可以自己设置发送请求时不再提供Referer,这种在请求时就会被误当作CSRF攻击
- 判断referer是否为某域名,可以自己来伪造
- 例如:判断Referer开头是否以 126.com 以及 126 子域名,而不验证根域名为126.com,这里就可以伪造出 x.126.com.xxx.com 的域名
- ...
- 某些浏览器,例如
2.请求中添加上token并验证,即校验信息不通过cookie来实现
- 该做法的原因:
- 要明白
cookie
的获得和携带的区别:- 获得cookie: 即通过js获取cookie中的参数值,有同源策略的限制
- 携带cookie: http请求时自动携带上cookie,会自动带上该同源域下的所有cookie
token
是csrf.com页面渲染时一起带过来的,这样的话,如果不在csrf.com页面发起这个点赞请求,不同域的网站是拿不到token的- 而CSRF只能通过自动携带cookie去发起攻击,,因此此方式可拦截
- 要明白
- 两种具体做法:
- 前端记录token,在请求参数中添加
token
,后台判断:
// 前端 //登录成功后,将token保存在本地(可以是cookie方式,也可以是 localStorage 方式) // 然后每次请求时添加一个 token 参数 // 后端,每个请求过来都验证token是否有效: app.use( async (ctx, next) => { var token = req.session.token; var csrfToken = req.param.csrftoken; if(token != null && xhrToken != null && token.equals(xhrToken)) { // success await next(); } else { // error return error } }) 复制代码
- 前端记录token,在请求头中添加自定义头,存放
token
,后台判断:
// expressJwt是express框架中可用的JWT校验插件 var expressJwt = require('express-jwt'); var validateJwt = expressJwt({ secret: config.secrets.session }); app.use( async (ctx, next) => { // 也同样允许校验 token 在请求参数中的 if(req.query && req.query.hasOwnProperty('access_token')) { req.headers.authorization = 'Bearer' + req.query.access_token; } // 下面会校验请求头中的 authorization 头 validateJwt(ctx, next); }) 复制代码
- 前端记录token,在请求参数中添加
五.注意和总结:
- CSRF攻击针对的是以
cookie
机制来验证登录权限的接口 - CSRF攻击不仅可以攻击get类型的接口,也可以攻击其他方法类型的接口
- CSRF攻击的对象,不管有没有开放同源接口访问限制,因为可以通过表单的方式发起请求,无视跨域限制
- 解决CSRF攻击最有效的方法就是在请求中携带token到后台去验证
- 可以在请求参数中携带
- 可以在请求头中携带