CSRF 攻击
CSRF(Cross-site request forgery,跨站请求伪造),它是指攻击者利用了用户的身份信息(cookie),执行了用户非本意的操作。
用户登陆过 bank.com 设置了 cookie,骇客通过社会工程学让用户访问他的危险网站,浏览器在解析网页时就会发送请求,携带上 bank.com 的 cookie,请求执行了用户非本意的操作。
GET 请求
<img src="https://bank.com/transfer?to=骇客&money=10000" alt="" style="display: none" />
POST 请求
<!-- index.html -->
<iframe src="./frame.html" style="display: none"></iframe>
<!-- frame.html -->
<form action="https://bank.com/transfer" method="post">
<input type="text" name="to" value="骇客" />
<input type="text" name="money" value="10000" />
</form>
<script>
document.querySelector('form').submit();
</script>
表单提交的默认行为是会跳转到目标地址,为了防止被用户发现,套一层 iframe
。
防御方式
不使用 cookie
防御力:⭐️⭐️⭐️⭐️⭐️
CSRF 攻击的核心是浏览器 根据规则 自动携带 cookie 的默认行为,所以完全可以不使用 cookie,而是用 token 和 浏览器本地存储 实现身份认证。
缺点:
- 兼容性略差,低版本浏览器可能没有开放本地存储相关的 API
- SSR(服务端渲染)会遇到问题,但可解决
- 比如服务端根据 cookie 判断用户是否已登陆,已登陆和未登陆的页面是不同的
- 解决方案:先让用户访问另一个页面,在另一个页面中读取本地存储、设置好 token 再请求当前页面
使用 cookie 的 sameSite
防御力:⭐️⭐️⭐️⭐️
详见 cookie 的组成
现代浏览器对安全的越来越重视,sameSite
已从原来的默认值为 None
变成了默认值为 Lax
,且 SameSite=None
必须设置为 Secure
缺点:
- 兼容性差,
sameSite
的出现时间不长,一些浏览器可能并没有支持 - 容易挡住自己人
使用 CSRF Token
防御力:⭐️⭐️⭐️⭐️⭐️
CSRF Token 机制是目前比较主流的的防御手段,其核心思路是在需要验证的请求中添加一个无法预测、无法使用浏览器策略直接提交的字段,该 Token 一般是一次性的。
CSRF Token 可以是 POST 请求的一个参数、或者是一个自定义的 HTTP Header,Token 可以存放于 <mate>
标签中、JS 变量中、Cookie 中、浏览器 Local Storage 中或者 DOM 的任何位置,Token 可以是同步令牌、加密令牌或者 HMAC Token。
不同的生成、存储及使用方式,所带来的的安全性也是存在区别的,一般推荐使用 HMAC Token、存储于 DOM 中,并在 Ajax 请求中添加自定义 Header 传输 Token。使用 CSRF Token 的优点在于识别准确,后续新开发接口只需按照规则开发便可避免 CSRF 漏洞。缺点在于推 CSRF Token 机制需要前端和客户端开发付出大量时间进行调整,且一旦 CSRF Token 机制运转,旧版本 APP 可能无法使用。
具体来说,CSRF Token 的流程是(拿表单提交举例):
- 用户访问页面时,服务器会在后端生成一个随机的 token,并将 token 返回给前端页面。
- 前端页面在生成表单时,将 token 值添加到表单中作为隐藏字段。
- 当用户提交表单时,浏览器会将 token 字段一同发送给服务器。
- 服务器在接收到表单请求时,会验证请求中的 token 值是否与服务器预期的一致。如果一致,则处理请求,否则拒绝请求。
由于攻击者无法获取到服务器生成的随机 token,也无法在用户提交表单时伪造一个有效的 token,因此 CSRF Token 可以有效地防范 CSRF 攻击。
使用 Referer
防护
防御力:⭐️⭐️
Referer 是一个请求头,值是当前页面的域,由浏览器自动添加,无法被开发者修改或删除,后端可以通过该请求头判断请求是否可信。
过去很常用,现在已经发现漏洞:当 URL 是 Base64 编码形式时,Base64 编码代码中的请求不会携带 Referer 请求头。
例如将如下代码进行 Base64 编码后:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<form action="https://bank.com/transfer" method="post">
<input type="text" name="to" value="骇客" />
<input type="text" name="money" value="10000" />
</form>
<script>
document.querySelector('form').submit()
</script>
</body>
</html>
将得到的 Base64 编码字符串放入 iframe
的 src
中,其请求 https://bank.com/transfer
不会携带 Referer 请求头:
<iframe
src="data:text/html;base64,PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04IiAvPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+CiAgICA8dGl0bGU+RG9jdW1lbnQ8L3RpdGxlPgogIDwvaGVhZD4KICA8Ym9keT4KICAgIDxmb3JtIGFjdGlvbj0iaHR0cHM6Ly9iYW5rLmNvbS90cmFuc2ZlciIgbWV0aG9kPSJwb3N0Ij4KICAgICAgPGlucHV0IHR5cGU9InRleHQiIG5hbWU9InRvIiB2YWx1ZT0i6aqH5a6iIiAvPgogICAgICA8aW5wdXQgdHlwZT0idGV4dCIgbmFtZT0ibW9uZXkiIHZhbHVlPSIxMDAwMCIgLz4KICAgIDwvZm9ybT4KCiAgICA8c2NyaXB0PgogICAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdmb3JtJykuc3VibWl0KCkKICAgIDwvc2NyaXB0PgogIDwvYm9keT4KPC9odG1sPg=="
style="display: none"
></iframe>
而且后端检验规则可能很复杂甚至难以维护,部分场景会存在误判现象(例如直接点开分享链接),整个链路传输转发过程中,可能存在字段信息缺失问题导致的误判,将会严重影响可用性。并且需要精确维护可信域名列表。
进行二次验证
防御力:⭐️⭐️⭐️⭐️⭐️
当进行高危险度或高机密度的操作时,要求用户重新输入密码登陆,或手机号验证、邮箱验证等。