csrf漏洞原理
浏览器里点开一个链接,漏洞钱就没了。原理这事听起来玄乎,漏洞但CSRF漏洞干的原理就是这个。
CSRF,漏洞全称Cross-Site Request Forgery,原理跨站请求伪造。漏洞名字挺唬人,原理拆开看就三件事:你登录了某个网站,漏洞攻击者骗你点了个链接,原理那个链接拿着你的漏洞身份干了件你没想干的事。
关键在于浏览器的原理一个默认行为——你访问A网站时,浏览器会自动带上A网站的漏洞Cookie。这个机制本来是原理为了方便,省得你每点一个页面都重新登录。漏洞但它有个副作用:只要请求发向A网站,不管这个请求是从A网站的页面发出的,还是从B网站的页面发出的,Cookie都会自动附上。
攻击者利用的就是这一点。
一个具体的场景
假设你刚登录了某银行网站,Cookie还在有效期内。这时候你顺手点开了一个论坛帖子,帖子里嵌了一张图片,代码大概长这样:
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
浏览器解析到这行代码,会老老实实向bank.com发起一个转账请求。因为你之前登录过,Cookie自动带上,银行服务器一看:身份合法,转账执行。你全程什么都没感觉到,论坛页面看起来也毫无异常——图片加载失败而已,谁会注意?
这就是CSRF最朴素的形态。它不需要窃取你的Cookie,不需要破解密码,只需要你“在登录状态下访问了一个恶意页面”。
为什么Cookie机制会配合攻击
有人会问:银行服务器分不清请求是从哪个页面发来的吗?
能分,但默认情况下它不查。HTTP协议本身是无状态的,服务器判断用户身份的唯一依据就是Cookie。而浏览器发送Cookie的规则是“按域名匹配”,不是“按请求来源匹配”。只要目标域名对得上,Cookie就跟着走。
HTTP头里有个Referer字段,理论上可以告诉服务器请求从哪来。但Referer并不可靠——用户可以禁用,某些浏览器策略会裁剪,攻击者也能用各种手段绕过。所以光靠Referer防CSRF,等于没防。
GET和POST都逃不掉
很多人以为CSRF只能通过GET请求发起,因为图片标签只能发GET。这个理解不完整。
攻击者完全可以做一个隐藏表单,自动提交:
<form action="https://bank.com/transfer" method="POST"> <input type="hidden" name="to" value="attacker" /> <input type="hidden" name="amount" value="10000" /> </form> <script>document.forms[0].submit();</script>
用户打开页面,表单立刻提交,效果和GET一样。POST并不能天然防御CSRF,它只是稍微增加了攻击者构造请求的成本。
防御的核心思路
既然问题出在“服务器无法区分请求是否来自本站页面”,那解决办法就是给请求加一个攻击者猜不到的东西。
CSRF Token就是这么干的。服务器在渲染表单时,嵌入一个随机字符串。提交时服务器校验这个字符串。攻击者的页面在bank.com之外,拿不到这个Token,请求就会被拒绝。
另一个有效手段是SameSite Cookie属性。设置成SameSite=Lax或Strict后,跨站请求默认不携带Cookie,直接从源头掐断了攻击链。现在主流浏览器已经默认启用Lax策略,CSRF攻击的成功率比前几年低了不少。
但别高兴太早。SameSite不是万能药——老浏览器不支持,某些场景下需要放宽策略,子域名之间的请求也可能绕过限制。CSRF Token仍然是更可靠的防线。
一个容易忽略的细节
CSRF攻击不读取响应。攻击者不关心转账是否成功,他只需要请求被发出去了。这意味着攻击者甚至不需要看到返回结果,一个盲发请求就够了。这种“只发不看”的特性让检测变得困难——服务器日志里看到的只是一个正常的已登录用户请求,没有任何异常特征。
我在实际项目里见过一个案例:某后台管理系统的“删除用户”功能只用了GET请求,且没有任何Token校验。一个管理员在登录状态下点开了一封邮件里的链接,三个用户就被删了。排查了半天,最后发现是邮件里藏了一个img标签。修起来也简单,加Token,改POST,十分钟的事。但没出事之前,没人觉得这是个问题。
CSRF的本质是信任错位——服务器太信任浏览器自动携带的凭证,而浏览器太信任用户点击的每一个链接。