<!-- title: csrf -->
CSRF 跨站请求伪造详解:原理、利用与防御
一、漏洞原理
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种"借刀杀人"式的攻击:攻击者并不直接入侵网站,而是诱导已登录的受害者浏览器,在用户不知情的情况下向目标网站发出伪造请求。由于浏览器会自动携带该站点下的 Cookie,服务器看到的是一个"带合法凭证的正常请求",于是把攻击者的操作当成了受害者本人的意愿。
与 XSS 对比着理解最清晰:XSS 是"攻击者把恶意代码送进你的网站执行",信任问题出在前端渲染;CSRF 则是"攻击者借你的网站信任别人的浏览器",信任问题出在服务端只认 Cookie、不认操作意图。一个成熟网站对浏览器说"带着我的 Cookie 来的,就是你本人",这正是 CSRF 攻击的立足点。
完成一次 CSRF 需要同时满足几个条件:目标操作有价值(转账、改邮箱、改密码);操作全部靠 Cookie 鉴权而无其他校验;请求参数可以被攻击者预测;受害者恰好处于该站的登录状态。这也是为什么银行类高危操作早已普遍加入短信验证——本质上就是在破坏上述条件。
二、示例代码
以下是一个存在 CSRF 漏洞的转账接口(虚构站点 example.com):
<?php
// transfer.php —— 只靠 Session Cookie 鉴权,无任何防 CSRF 措施
session_start();
if (!isset($_SESSION['uid'])) { exit('请先登录'); }
// 直接执行转账
db_exec("UPDATE accounts SET balance = balance - ? WHERE user = ?",
[$_POST['amount'], $_POST['to']]);
echo '转账成功';
?>攻击者只要让已登录的受害者浏览器自动发出这个 POST 请求即可得手。修复后的安全写法是引入"一次性 CSRF Token"——表单里埋一个随机值,服务端校验:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // 每个会话生成随机令牌
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('非法请求');
}
// ... 校验通过,才执行转账
}
?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<!-- 表单其余字段 -->
</form>攻击者无法读到受害者会话里的 token(受同源策略保护),伪造的请求过不了校验。
三、三种典型 Payload(教学演示)
1. GET 型:一张图片发起的请求
早期很多站点的敏感操作用 GET 实现(如 GET /transfer?to=bob&amount=100),攻击者只需在任意网页里插一张"图":
<img src="https://example.com/transfer?to=attacker&amount=100" style="display:none">受害者打开页面的瞬间,浏览器就去请求了这个 URL 并自动带上 Cookie。这条 Payload 也是"敏感操作禁止使用 GET"的最好教材。
2. POST 型:自动提交的隐藏表单
对 POST 接口,攻击者托管一个自动提交的表单页面诱骗用户点击:
<form id="f" action="https://example.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="100">
</form>
<script>document.getElementById('f').submit();</script>页面一打开表单就静默提交,全程无需用户输入任何东西。
3. 无感知型:隐藏 iframe 发包
把上面的表单塞进 <iframe name="x"> 再 target="x" 提交,受害者的页面外观完全不变,请求已在后台完成。这种变体常与钓鱼文案结合("点我领优惠券"),说明**"用户没有输入"不等于"请求是用户发起的"**。
四、防御建议
- CSRF Token 是标配:所有敏感表单/接口携带随机令牌并在服务端校验(各主流框架如 Spring Security、Django、Laravel 都有内置实现,直接开启即可)
- Cookie 设置 SameSite:会话 Cookie 加
SameSite=Lax或Strict,现代浏览器默认不再在跨站请求中携带 Cookie,能挡住绝大多数 CSRF(注意旧浏览器兼容性) - 校验 Origin/Referer:服务端校验请求来源头是否为本站域名,作为第二道辅助防线
- 敏感操作二次确认:转账、改绑邮箱、改密码等高危操作要求验证码、短信验证或重新输入密码,同时把"改邮箱"本身也保护起来(防止攻击者先劫持邮箱再改密码)
- 权限分离:日常浏览与敏感操作使用不同会话等级,管理后台强制二次认证
- 拒绝 GET 修改数据:一切产生副作用的操作只接受 POST/PUT/DELETE,从设计上杜绝 GET 型 CSRF
五、总结
CSRF 攻击的优雅与危险都在于"全盘利用浏览器既有机制":Cookie 自动携带 + 跨站请求不受限。防御思路也随之清晰——在每个敏感请求里放一点"攻击者拿不到的随机证据"(Token),并让 Cookie 少参与跨站(SameSite)。它和 XSS 是一对孪生课题:XSS 偷身份,CSRF 借身份;实际攻防中二者常被组合使用,所以防御体系也应当成对建设。