karchinkong / Blogsystem

个人博客 iView + Express + Mysql
6 stars 0 forks source link

CSRF 了解与防御 #2

Open karchinkong opened 6 years ago

karchinkong commented 6 years ago

什么是 CSRF?

CSRF(Cross-site request forgery),中文名称:跨站请求伪造。

CSRF 会盗用登录者信息,模拟登录者发送请求。

CSRF 攻击是源于Web的隐式身份验证机制。Web 的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。CSRF 攻击的一般是由服务端解决。

原理与过程

1.用户登录了受信任网站 A,在本地生成 cookie。 2.在没退出 A 的情况下,打开 tab 访问危险网站 B,B 恶意发出请求访问 A。 3.浏览器带上 A 生成的 cookie 向 A 发出恶意请求成功,从而 B 攻击成功。

简单攻击

一般分为 Get 与 Post 攻击。Get 攻击最简单,假设 Github 有一个关注的 Get 接口: https://github.com/follow?id=123

id 表示即将关注的用户,比如说关注我。我在本篇文章里内置一段恶意代码:

<img style="width:0" src="https://github.com/follow?id=123" />

如果你已经登录了 Github,同时点开了我的这篇文章,在 Github 没有防御 csrf 的情况下,你便会自动关注了我。

一般在需要操作数据时,会使用 POST 请求。但 POST 也不是绝对的安全,POST 也可以被模拟。

用 form 表单 + iframe 即可模拟 POST 请求。

<form name="myForm" method="post" target="myFrame">
  <input type="hidden" name="id" value="123"/>
  <input type="submit" />
</form>
<iframe name="myFrame" style="display:none;"></iframe>

同样发起了 CSRF 攻击。

防御 CSRF

一般防御 CSRF 有三种方法:

验证 HTTP Referer 字段。
加验证码验证。
加 token 验证。

同时,建议 Get 请求用来查找,Post 请求用来操作。

  1. 验证 HTTP Referer 字段

在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。服务器可以通过判断 Referer 来防御,如果是其它网站,则拒绝该请求。

该方法简单便捷,但是也不是万无一失。部分浏览器可以允许篡改 Referer 值, 同样可能防御不了 CSRF 攻击。同时有些浏览器允许用户禁用 Referer,服务器会因为没有 Referer 值而拒绝该请求。

  1. 加验证码验证

在通常情况下,验证码能很好遏制 CSRF 攻击。但是出于用户体验考虑,总不能没请求一次服务器就要用户输一次验证码吧。所以验证码只能作为一种辅助手段,不能作为主要解决方案。

  1. 加 token 验证

主要两种实现:

服务端分配 token。
服务端和客户端通过一样的算法和输入匹配。

但如果网站同时存在 XSS 漏洞,还是会导致 token 泄露,从而导致该方法形同虚设。

  1. 服务端分配 token

服务端随机生成一个 token,存放在 session 中,分布式环境可以使用 Redis 存储 token。然后返回给前端,前端在请求服务器时带上 token 供服务器验证。

此方法前端不用做太多的运算。

  1. 服务端和客户端通过一样的算法和输入匹配

服务端和客户端通过一样的算法和输入计算 token 值,进行比较。

前端随机生成一个 sessionKey,存放在 cookie 中,再使用 time33 算法生成 token。前端请求服务端时将 sessionKey 与 token 都传过去,服务端再通过相同的 time33 算法验证。

此方法服务端不用特意保存 token。

特例分享

目前有两个网站,a.jd.com 与 b.jd.com。

a 下有 sessionKey 存在 cookie 下,domain 为当前二级域名。当 b 想跨域请求 a 的接口时,由于二级域名之间无法获取 cookie,所以 b 获取不到 a 的 sessionKey(生成不了对应的 token),从而请求被拒绝。

若此时 b 自己随机生成 sessionKey 存在 cookie 里,并设置 domain 为 a。此时访问 a,会发现请求成功。但是!但是!too young,先访问 a,再来试试刚才的请求,会发现 cookie 里不仅有 b 的 sessionKey,还会带上 a 的 sessionKey。

结果就是请求被决绝, 403 Forbidden。不知道服务端是怎么取 cookie 里的 sessionKey,可能取 a 的,可能随机取一个,但是这个方法是行不通的。

此时,将 a 的 sessionKey 存在 cookie 下时将 domain 设置为顶级域名 jd.com 时,让 b 能拿到,b 才可请求成功。