blog.dopana

Back

CSRF 是 Cross-Site Request Forgery(跨站请求伪造)的缩写。虽然名字听起来颇有学术色彩,但其核心攻击逻辑却非常“接地气”:利用你已经登录的身份,诱骗你的浏览器背着你向目标网站发送恶意请求。

1. 10 岁小孩也能听懂的通俗解释(ELI5)#

想象你刚走进一家高档 VIP 会员俱乐部(例如你的网上银行或社交媒体主页):

  1. 你在前台成功验证身份后,工作人员在你的手背上盖了一个“VIP 认证印章”(这就是浏览器中保存的 Session Cookie)。
  2. 只要这个印章还在,你在俱乐部里点餐或消费时,服务员看一眼你的手背就知道你是尊贵的会员,不再需要你重复出示身份证。
  3. 离开俱乐部后,你路过了一个来路不明的路边摊(恶意网站 evil.com)。
  4. 摊主偷偷写了一张转账单:“从该会员的账户转账 1 万元给我”,然后趁你不注意抓起你盖有 VIP 印章的手在纸上按了个手印,并派人送去了 VIP 俱乐部。
  5. 俱乐部的前台看到印章真实有效 \rightarrow 误以为真的是你本人的授权操作 \rightarrow 瞬间完成了转账扣款!

你自始至终都没有在银行网站上点击过转账,但你的浏览器却被当作“工具人”代劳了。这就是 CSRF。

flowchart LR
    subgraph AttackerSite["恶意网站 (evil.com)"]
        MaliciousScript["隐藏脚本 / 自动提交表单<br/>POST https://bank.com/transfer"]
    end

    subgraph UserBrowser["用户浏览器 (Client)"]
        Victim["已登录的用户<br/>(持有有效的 Session Cookie)"]
    end

    subgraph BankServer["银行目标服务器 (bank.com)"]
        Backend["Cookie 校验通过<br/>-> 错误执行恶意转账!"]
    end

    Victim -->|1. 访问恶意网页| MaliciousScript
    MaliciousScript -->|2. 静默触发跨站请求| Victim
    Victim -->|3. 自动附加 Cookie 并发出请求| Backend

2. CSRF 漏洞成立的两个关键前提#

CSRF 的发生既不需要黑客破解你的密码,也不需要盗取你的 Session 密钥,它完全建立在浏览器与服务器之间的盲目信任之上:

  1. 浏览器的 Cookie 自动携带机制(Ambient Credentials): 浏览器向目标域名(如 bank.com)发送请求时,会默认自动附带该域名下存储的所有 Cookie,而不管这个请求是在哪个页面发起的。
  2. 服务器缺乏请求来源校验: 服务器只机械地校验“请求是否带有合法 Cookie(是否已登录)”,却忽略了最致命的问题:“这个请求到底是不是从我自家的前端页面发出来的?”。

3. 彻底厘清:CSRF 与 XSS 的本质区别#

很多开发者经常混淆 XSS 和 CSRF,两者的核心差异在于:

比较维度XSS (跨站脚本攻击)CSRF (跨站请求伪造)
攻击本质在目标网站内注入并执行恶意 JavaScript 脚本诱导浏览器从外部网站向目标网站发送未授权请求
是否盗取 Cookie可以直接读取并盗走 Cookie(除非配置了 HttpOnly无法读取 Cookie(只是借用浏览器的身份凭证)
产生根源前端没有对用户输入进行严格过滤或转义渲染依赖了浏览器跨站自动携带 Cookie 的底层机制

[!NOTE] XSS 相当于小偷直接偷走了你的身份证;而 CSRF 则是骗子拉着你拿着身份证去办事,你甚至不知道自己办了什么事。

4. 常见攻击场景复现#

场景一:利用 <img> 标签触发未防护的 GET 请求#

如果敏感的业务操作不规范地使用了 HTTP GET 方法:

evil.com/page.html
<!-- 浏览器自动静默拉取图片,并自动附带受害者在银行的会话 Cookie -->
<img src="https://bank.com/api/transfer?to=attacker&amount=10000" width="0" height="0" />
html

场景二:利用隐藏表单自动提交 POST 请求#

即使你的接口严格使用了 HTTP POST,黑客依然可以在恶意页面中构造隐藏表单,并在页面加载时通过 JavaScript 自动提交:

evil.com/attack.html
<form id="csrfForm" action="https://social.com/api/delete-account" method="POST">
  <input type="hidden" name="confirm" value="true" />
</form>

<script>
  // 页面加载完成后立即自动提交
  document.getElementById('csrfForm').submit();
</script>
html

5. 全方位的 CSRF 防御策略#

1. CSRF Token(同步令牌模式)#

这是最经典也是最可靠的防御方案:

  • 当服务端渲染表单或初次建立会话时,生成一个唯一的、高熵的随机字符串(CSRF Token)并绑定到用户会话中。
  • 前端提交表单或发起请求时,必须在请求体或请求头中携带该 Token。
  • 受限于浏览器的同源策略(SOP),第三方恶意网站无法跨域读取你网站上的 CSRF Token,因此伪造的请求会因缺少正确 Token 而被直接拒绝。
transfer.html
<form action="/transfer" method="POST">
  <!-- 服务端注入的保密 CSRF Token -->
  <input type="hidden" name="_csrf" value="d9a8f7b2c3e4..." />
  <input type="number" name="amount" />
  <button type="submit">确认转账</button>
</form>
html

通过在 Set-Cookie 响应头中配置 SameSite 属性,可以严格控制 Cookie 在跨站请求中的发送策略:

  • SameSite=Strict:最为严格,完全禁止在任何跨站请求中发送 Cookie(安全性最高,但用户从外部链接点击进入时需重新登录)。
  • SameSite=Lax (现代浏览器默认值):阻止在 POST、隐藏表单、<img> 等跨站请求中发送 Cookie;仅在安全的顶级导航(如直接点击 <a> 链接)时允许携带。
  • SameSite=None:在所有跨站场景下均允许发送 Cookie(必须配合 Secure 属性在 HTTPS 下使用)。
Set-Cookie: sessionId=xyz123; Path=/; Secure; HttpOnly; SameSite=Lax
http

[!TIP] 为身份认证 Cookie 设置 SameSite=Lax(或 Strict)并配合 SecureHttpOnly,即可直接封死绝大多数基础 CSRF 攻击路径。

适用于无状态(Stateless)或单页应用(SPA)架构:

  1. 服务端设置一个前端可读的随机 Token Cookie(例如 XSRF-TOKEN)。
  2. 前端 JavaScript 读取该 Cookie 的值,并在发起 AJAX 请求时将其注入到自定义请求头中(例如 X-XSRF-TOKEN)。
  3. 后端对比请求头中的值与 Cookie 中的值,一致则认定请求合法。

4. 校验 Origin / Referer / Sec-Fetch-Site 请求头#

在后端拦截器或网关层,严格核对请求来源域名:

middleware/csrfCheck.js
export function verifyOrigin(req, res, next) {
  const origin = req.headers['origin'] || req.headers['referer'];
  const allowedHost = 'https://mybank.com';

  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    if (!origin || !origin.startsWith(allowedHost)) {
      return res.status(403).json({ error: 'CSRF Protection: Invalid Request Origin' });
    }
  }
  next();
}
javascript

6. SPA 与 JWT 时代下的 CSRF 现状#

在以前后端分离、单页应用(SPA)为主流的现代架构中:

  • 如果将 JWT 令牌保存在内存中,并在发起请求时手动附加到 Authorization: Bearer <token> 请求头中,天然对 CSRF 免疫,因为浏览器绝不会自动跨站拼接自定义认证请求头。
  • 但如果为了方便或持久化将会话 JWT 存放在 Cookie 中,CSRF 威胁依然完好存在,仍必须严格部署上述防护策略。

7. 总结#

CSRF 深刻揭示了 Web 世界中“无条件信任”所带来的安全隐患。

构建坚不可摧的 Web 应用防御准则:

  1. 涉及数据变更的操作绝不使用 GET 方法。
  2. 确保所有身份凭证 Cookie 均配置了 SameSite=LaxSameSite=Strict
  3. 关键业务表单与接口强制推行 CSRF Token 或自定义校验请求头。
  4. 后端中间件严格核验 OriginSec-Fetch-Site 请求来源。

参考资料#