跨站请求伪造

你登录了网上银行,跨站页面一切正常。请求你点了一下“查看余额”,伪造页面跳转了一下,跨站然后又回到了登录界面。请求你没多想,伪造重新输入密码,跨站进去了。请求一切照旧。伪造但就在刚才那零点几秒的跨站跳转里,你的请求钱可能已经转到了别人的户头上。这不是伪造科幻片,这是跨站跨站请求伪造——CSRF,一个潜伏在网页背后、请求专门利用你“已登录”身份的伪造窃贼。

它的原理其实特别简单,简单到让人觉得荒唐。你登录了网站A,浏览器里存着A的登录凭证(比如Cookie)。这时候你又打开了另一个网站B,B的页面上藏着一条指向A的请求——可能是一张图片,一个链接,甚至是一段自动提交的表单。你的浏览器看到这个请求,二话不说,带着A的Cookie就发出去了。服务器一看,这Cookie是合法的,请求是“你”发的,于是照单全收。整个过程你浑然不觉,就像有人借了你的身份证去办了一张信用卡,而银行完全没怀疑。

我见过一个真实案例。某个论坛用户,只是浏览了一个带恶意图片的帖子,就被修改了绑定邮箱和密码。攻击者通过CSRF拿到了账户控制权,然后在论坛上以他的名义发了一堆垃圾广告。那个用户事后一头雾水,说“我什么都没点啊”。确实,他没点任何可疑的东西,只是“看”了一下页面。这就是CSRF最阴险的地方——它不需要你点击,不需要你输入任何信息,只需要你“正在登录着”。

防御手段其实也谈不上多高深。最常用的就是加一个随机token——服务器在表单里塞一个只有它和你的浏览器知道的随机字符串,提交时必须带回来。攻击者没法提前知道这个token,自然伪造不了请求。另一种是验证请求来源,检查Referer头——但这玩意儿经常被浏览器或隐私插件屏蔽,不太靠谱。还有更狠的,比如二次验证,转账前必须再输一次密码或验证码。麻烦是麻烦了点,但确实管用。

说实话,现在很多主流框架都内置了CSRF防护,Spring Security、Django、Laravel这些都有现成的中间件。按理说新项目踩坑的机会不大。但麻烦在于存量系统——那些年久失修的老项目,或者接口设计得乱七八糟的内部系统,往往没做防护。还有不少开发者图省事,把CSRF保护给关了,只为了“接口调试方便”。这种图省事的代价,就是给攻击者留了一扇后门。

我自己就干过类似的事。前几年写了个小工具,后台管理界面图省事没加token,想着“反正就我自己用”。后来有一次在公共WiFi下测试,突然意识到——如果这时候我正好登录着后台,而某个网页里藏着一条指向我后台的请求,我的管理权限就可能被借走。那一瞬间我后背有点发凉。后来老老实实把token加上了,还加了个简单的IP白名单。

所以你看,CSRF的攻击成本极低,低到一张图片就能搞定。但防御成本也低,低到加几行代码就行。问题不在于技术有多难,而在于很多人压根没意识到自己写的那几个接口,正赤裸裸地暴露在互联网上。下次你登录一个老旧的内部系统,或者某个没什么人维护的小网站时,不妨想想——它真的防住了吗?