跨站请求伪造(CSRF)是一种利用用户已登录身份,在用户无感知情况下诱导其浏览器发起非预期请求的攻击方式。防御方通常在服务端植入校验逻辑,而攻击方则不断寻找浏览器机制与业务逻辑的缝隙。理解二者对抗的本质,需要从请求来源可信度与凭证携带规则两个维度展开。

主流CSRF防御机制的原理与实现
最常见的防御手段是同步器令牌(CSRF Token)模式。服务端在渲染表单或返回接口时下发一个随机不可预测的令牌,并要求客户端在提交敏感请求时携带该令牌。由于攻击者无法跨域读取目标站点的令牌值,便难以构造合法请求。实现时通常将令牌存于会话中,并通过隐藏字段或请求头传递。
下面给出一个基于Java Servlet的令牌校验简化示例,展示如何生成与验证令牌:
import java.security.SecureRandom;
import java.util.Base64;
public class CsrfTokenUtil {
private static final SecureRandom random = new SecureRandom();
public static String generateToken() {
byte[] bytes = new byte[32];
random.nextBytes(bytes);
// 使用URL安全编码避免特殊字符问题
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
public static boolean verifyToken(String sessionToken, String requestToken) {
if (sessionToken == null || requestToken == null) {
return false;
}
// 采用常量时间比较防止计时攻击
return sessionToken.equals(requestToken);
}
}
除了令牌机制,SameSite Cookie属性从浏览器层面限制了第三方上下文携带凭证的行为。将该属性设为Strict或Lax后,跨站请求默认不再发送会话Cookie,从根本上削弱CSRF利用条件。不过旧版浏览器不支持此属性,且Strict模式会影响正常跳转体验。
双重提交Cookie是另一种轻量方案:服务端要求请求头或参数中包含与Cookie中同名的值,由于同源脚本才能读取Cookie,攻击者无法伪造。但该方案依赖HTTPS与合理的Cookie作用域配置,否则可能被子域覆盖攻击突破。
典型CSRF绕过手法与漏洞场景
即便部署了Token,错误的前后端分离架构仍会暴露风险。例如前端将Token放在本地存储并通过JS读取,若站点存在XSS漏洞,攻击脚本便可窃取Token并自动发起请求,此时CSRF防护被XSS架空。另外,部分接口仅校验POST表单中的Token,却忽略了JSON接口可通过Content-Type为text/plain配合简单请求绕过预检,从而省略Token检查。
攻击者还常利用“被动型CSRF”完成绕过:诱使用户访问恶意页面,该页面通过图像标签或自动提交表单指向目标接口。若服务端仅用Referer校验且配置宽松,攻击者可借助开放重定向或某些浏览器插件篡改Referer实现绕过。以下代码展示一个自动提交表单的恶意页面结构:
<html>
<body onload="document.forms[0].submit()">
<form action="https://ipipp.com/account/transfer" method="POST">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="1000" />
<form>
</body>
</html>
浏览器兼容问题也是绕过点之一。在部分老版本浏览器中,SameSite属性被忽略,攻击者可如常携带Cookie。还有一些移动端WebView会错误实现同源策略,导致本应被拦截的跨站请求成功发出。因此防御不能单靠某一机制,必须叠加多层控制。
工程化防护组合与最佳实践
在生产环境中,推荐以SameSite=Lax作为基础屏障,同时为所有状态变更接口强制校验CSRF Token。对于极高权限操作,如修改密码或转账,应引入二次认证(如短信码或手势密码),使单纯伪造请求无法完成闭环。服务端还需统一拦截非简单请求,确保预检失败时不执行业务逻辑。
下面给出一个Node.js Express中结合Token与SameSite设置的 middleware 示例,展示如何统一防护:
const express = require('express');
const app = express();
// 设置会话Cookie带SameSite属性
app.use((req, res, next) => {
res.cookie('session', req.sessionID, {
httpOnly: true,
sameSite: 'lax',
secure: true
});
next();
});
// 校验CSRF Token中间件
function csrfGuard(req, res, next) {
const method = req.method.toUpperCase();
if (['GET', 'HEAD', 'OPTIONS'].includes(method)) {
return next();
}
const tokenFromHeader = req.headers['x-csrf-token'];
if (tokenFromHeader && tokenFromHeader === req.session.csrfToken) {
return next();
}
return res.status(403).send('CSRF validation failed');
}
app.post('/api/transfer', csrfGuard, (req, res) => {
// 处理转账业务逻辑
res.json({ ok: true });
});
最后需要建立持续的安全测试流程。通过自动化扫描工具模拟跨站请求,并结合代码审计确认Token生成随机性、存储隔离与校验覆盖度。只有将浏览器机制、业务逻辑与运维配置视为整体,才能在CSRF防御与绕过的长期博弈中保持主动。
CSRFtoken验证samesite属性修改时间:2026-08-15 08:57:20