「每日一题」CSRF 是什么?

先从一个故事说起(故事纯属虚构,恶意模仿后果自负):

小谷最近遭遇电信诈骗被骗的倾家荡产,于是他想到了报复社会。在知乎上狂点800个没有帮助1000个举报后,他决定做点正事干票捞钱的生意。
「很多视频网站都有赠送礼品的功能,假如所有人都赠送我个礼物,我再转卖掉,不就发财啦」小谷寻思着。
说干就敢,经过一番折腾测试,小谷发现视频网站赠送礼物的接口是:

https://xxxx.com/gift/send?target=someone&giftId=ab231

, 原来只要用户在登录状态下请求这个地址,就能给名为someone的用户赠送礼品ab231。
那如何才能让其他用户请求这个接口呢?其实只要用户点击就行,「色诱是最好的陷阱」回想起自己被骗的经过,小谷猥琐狠狠的打了一行文字 ——「想要的都在这里,今夜注定让你无眠~~ 饥人谷-最有爱的前端学习社区」,然后在各个群组里回复。
经过一天等待,有几个上钩的,但远远达不到预期,「有没有更自动的办法,让用户只要看到即使不点也能上钩呢?」。小谷开始对整个网站功能逐一过滤,突然他眼前一亮,在用户评论编辑框内看到了上传外链图片的功能。「如果把这个 url 作为图片填进去,上传后会在评论区创建一个img 标签,src 对应的就是这个地址。当用户打开页面后这个 img 就会自动加载图片也就是发送这个请求,这样一来凡是打开这个页面的人不论是不是点击这个链接都会给赠送礼物,perfect!」 小谷为自己的聪明才智惊叹。
又过了一天,果然源源不断的礼物送了过来。这个时候小谷隐隐有些担心起来,虽然礼物送的都很小,但赠送都是有历史记录的,用户查看历史记录肯定会起疑心,能不能帮用户删除这条赠送记录呢?经过测试,发现删除赠送记录的接口地址是

https://xxxx.com/gift/deleteRecord

, 接口类型为POST,请求参数为 { giftId:"ab231"}。 用户无法通过点击一个链接在不知情的情况下发送 POST 请求,怎么办呢?于是,小谷构造了一个页面:

<body>
哈哈,给你开了个玩笑,莫生气~
<iframe name="hiddenIframe"  style="display:none"></iframe>
<form action="https://xxxx.com/gift/deleteRecord" id="form" method="post" style="visibility:hidden" target="hiddenIframe">
	<input type="text" name="giftId" value="ab231">
</form>
<script>
	document.getElementById('form').submit();
	location.href = "http://xxxx.com";
</script>
</body>



当用户点开这个页面的链接后,会自动发送 POST 请求,然后跳转到原始首页。这样用户既在不知情的情况下赠送了礼品,又在不知情的情况下删除了赠送记录。大功告成后,小谷购买了个广告机在各大论坛狂发....

一周之后,警察👮叔叔来敲门了,咚🕳🕳

上面小谷的攻击流程就是典型的 CSRF (Cross Site Request Forgery)攻击,中文名:跨站请求伪造。其原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。


后续......

xxxx视频网站不断接到用户举报,自己的礼品莫名丢失。经过排查发现有攻击者利用 CSRF 进行攻击,报警后赶紧让公司的安全部门的小饥来修复漏洞。

小饥梳理了一遍公司网站所有的接口,发现很多接口都存在这个问题。于是采用了anti-csrf-token的方案。 具体方案如下:

  1. 服务端在收到路由请求时,生成一个随机数,在渲染请求页面时把随机数埋入页面(一般埋入 form 表单内,<input type="hidden" name="_csrf_token" value="xxxx">)
  2. 服务端设置setCookie,把该随机数作为cookie或者session种入用户浏览器
  3. 当用户发送 GET 或者 POST 请求时带上_csrf_token参数(对于 Form 表单直接提交即可,因为会自动把当前表单内所有的 input 提交给后台,包括_csrf_token)
  4. 后台在接受到请求后解析请求的cookie获取_csrf_token的值,然后和用户请求提交的_csrf_token做个比较,如果相等表示请求是合法的。


(上图是某电商网站的真实设置,这里页面上设置的 token和session里设置的token 虽然不直接相等,但 md5('1474357164624') === '4bd4e512b0fbd9357150649adadedd4e',后台还是很好计算的)


安全部的Leader 看了看小饥的方案,「方案出的很赞, 不过还有几点需要注意一下」:


  1. Token 保存在 Session 中。假如 Token 保存在 Cookie 中,用户浏览器开了很多页面。在一些页面 Token 被使用消耗掉后新的Token 会被重新种入,但那些老的 Tab 页面对应的 HTML 里还是老 Token。这会让用户觉得为啥几分钟前打开的页面不能正常提交?
  2. 尽量少用 GET。假如攻击者在我们的网站上传了一张图片,用户在加载图片的时候实际上是向攻击者的服务器发送了请求,这个请求会带有referer表示当前图片所在的页面的 url。 而如果使用 GET 方式接口的话这个 URL 就形如: xxxx.com/gift?
    ,那相当于攻击者就获取了_csrf_token,短时间内可以使用这个 token 来操作其他 GET 接口。


「这个项目,就由你来推动实施,晚上加加班争取这两天搞定~」


欢迎进群探讨技术,点击此微信扫码进群

广告:前端培训,就饥人谷靠谱。

编辑于 2019-07-15

文章被以下专栏收录