CSRF
CSRF 的攻击原理、成立条件与常见防御方案说明
#tech / dev / frontend
#status / growing
#type / concept
#platform / browser
[!info] related notes
- 所属 MOC: security-moc, frontend-basics-moc
- 对比: xss-cross-site-scripting
- 相关概念: cross-origin, same-origin, same-site
- Cookie: cookie-vs-localstorage, cookie-session-token, jwt
CSRF
Overview
- 用户输入账号信息请求登录A网站。
- A网站验证用户信息,通过验证后返回给用户一个cookie
- 在未退出网站A之前,在同一浏览器中请求了黑客构造的恶意网站B
- B网站收到用户请求后返回攻击性代码,构造访问A网站的语句
- 浏览器收到攻击性代码后,在用户不知情的情况下携带cookie信息请求了A网站。此时A网站不知道这是由B发起的。那么这时黑客就可以进行一下骚操作了!
第3,4步细节
hacker 网站构造代码样例
<img src="https://bank.com/transfer?toAccount=hacker&amount=10000" width="0" height="0" />
浏览器原生支持跨站资源访问,当访问 hacker 网站时,浏览器看到资源地址,就会自动向该服务器发起请求。并且浏览器会自动看是否存在对应网址的 cookie,有的话自动发送。SameSite 决定跨站场景下这个自动携带是否允许。
网络安全相关
挖掘:
- 抓取一个正常请求的数据包,如果没有Referer字段和token,那么极有可能存在CSRF漏洞
- 如果有Referer字段,但是去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。
- 利用工具进行CSRF检测。如:CSRFTESTER,CSRF REQUEST BUILDER等
CSRF 攻击成功的三个必要条件
黑客要成功实施 CSRF 攻击,必须同时满足以下条件:
- 目标网站的接口存在漏洞: 接口完全依赖 Cookie 进行身份验证,且没有其他防伪造措施。
- 用户处于登录状态: 用户刚刚登录过目标网站,且 Cookie 尚未过期。
- 能猜出请求的所有参数: 如果转账接口除了需要
toAccount和amount,还需要一个用户每次都必须手动输入的支付密码,那 CSRF 就会失败,因为黑客所在的evil.com无法读取bank.com的数据(受同源策略保护),自然猜不到密码。
防御
- 验证 HTTP Referer 字段:在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址
- 在请求地址中添加 token 并验证
- 在 HTTP 头中自定义属性并验证
防御方案 A:SameSite Cookie 属性(现代浏览器的杀手锏)
这是目前防御 CSRF 最简单、最有效的方法。后端在 Set-Cookie 时,加上 SameSite 属性。SameSite
SameSite=Strict: 最严格。只有当前网页的 URL 与请求目标一致时,才会带上 Cookie。也就是说,如果你在evil.com发起对bank.com的请求,Cookie 绝对不会被带上。CSRF 直接失效。SameSite=Lax(现代浏览器默认值): 稍微放宽一点。允许部分跨站的 GET 请求(比如你通过<a>标签从外部网站跳转到bank.com时会带 Cookie,保证用户体验),但跨站的 POST、PUT、DELETE 等修改数据的请求,或者通过 AJAX、图片加载发出的 GET 请求,统统不带 Cookie。- 注意:虽然现代浏览器默认
Lax,但老旧浏览器可能不支持,所以不能完全依赖它。
防御方案 B:CSRF Token(同步令牌模式,最经典的防御)
如果你的应用架构必须使用 Cookie,且需要兼容老浏览器,这是最标准的做法。
- 下发 Token: 用户登录后,服务器生成一个随机的、不可预测的字符串(CSRF Token)。这个 Token 可以通过专门的接口发给前端,或者嵌在 HTML 页面里(如果是 SSR 服务端渲染)。
- 请求携带: 前端在发送修改数据的请求(POST/PUT等)时,除了浏览器自动带上的 Cookie 外,还需要手动将这个 CSRF Token 放在请求的 Header(如
X-CSRF-Token)或者请求体(Body)中。 - 服务器验证: 服务器收到请求,既验证 Cookie 里的 Session,又验证 Header 里的 CSRF Token 是否正确。
- 为什么这能防住? 因为黑客所在的
evil.com受到浏览器的同源策略 (Same-Origin Policy) 限制,无法通过代码读取到bank.com返回的 CSRF Token。黑客只能让浏览器自动带 Cookie,却无法伪造出那个放在 Header 里的随机 Token,请求就会被后端拒绝。
防御方案 C:彻底抛弃 Cookie 鉴权(比如上文提到的 localStorage 方案)
这就是为什么我们在上一个问题里说,“JWT 存在 localStorage 中天生免疫 CSRF”。 如果你不使用 Cookie 来维持会话,而是要求前端每次请求都把 Token 放在 Authorization: Bearer <token> 这样的自定义 Header 中,CSRF 攻击就无从下手了。因为浏览器从来不会“自动”帮你加上自定义 Header。跨站伪造的请求根本不会携带用户的凭证。