攻击模型
受害者已登录 bank.example 并持有 Cookie。他们访问 evil.example,后者向 bank.example/transfer 提交隐藏表单。浏览器自动附带银行 Cookie,除非银行检查攻击者无法伪造的内容,否则无法区分该请求与合法请求。
防御
import crypto from 'node:crypto';
app.use((req, res, next) => {
if (!req.session) return next();
if (!req.session.csrf) req.session.csrf = crypto.randomBytes(32).toString('base64url');
res.locals.csrfToken = req.session.csrf;
next();
});
app.post('/transfer', (req, res) => {
if (req.body.csrf !== req.session.csrf) return res.status(403).send('invalid csrf token');
// 执行转账
});
会话 Cookie 应设置 SameSite=Lax 或 Strict 以及 Secure。高风险操作要求重新认证。把 Origin 或 Referer 校验作为纵深防御,但不能单独依赖。
适用范围
所有使用 Cookie 认证且会改变状态的接口都需要 CSRF 防护,包括以 POST 实现的登出。与 CORS 相比,CSRF 防护阻止请求被服务端执行,而 CORS 只控制攻击者页面能否读取响应。使用显式 Bearer Token 的 API 不受经典 CSRF 影响,但有自己的令牌存储风险。
陷阱
静态的、泄漏在 URL 中的、或未与会话绑定的 Token 几乎没有保护作用。状态变更的 GET 请求会绕过许多 Token 检查。应在浏览器中提交跨源表单进行测试,而不只是 curl,因为 curl 不执行浏览器的凭据规则。