您现在的位置是:网站首页 > CSRF防护文章详情
CSRF防护
陈川
【
Node.js
】
45631人已围观
4048字
CSRF攻击的原理
CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的Web安全威胁。攻击者诱导用户在已认证的Web应用中执行非预期的操作。这种攻击利用了Web应用对用户浏览器的信任机制。
典型的CSRF攻击流程:
- 用户登录信任的网站A,服务器返回认证Cookie
- 用户未登出情况下访问恶意网站B
- 网站B包含向网站A发起请求的代码
- 浏览器自动携带网站A的Cookie发送请求
- 网站A服务器认为这是用户的合法请求
// 恶意网站中的攻击代码
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="amount" value="10000"/>
<input type="hidden" name="to" value="attacker"/>
</form>
<script>document.forms[0].submit();</script>
Node.js中的CSRF防护机制
同步令牌模式
最常用的防护方法是同步令牌(Synchronizer Token Pattern)。服务器生成随机令牌,存储在会话中,并在表单中包含该令牌。
// 使用express和csurf中间件
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const csrfProtection = csrf({ cookie: true });
const app = express();
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));
// 路由示例
app.get('/form', csrfProtection, (req, res) => {
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
res.send('数据正在处理');
});
双重Cookie验证
另一种方法是设置双重Cookie:
- 服务端在响应头中设置自定义Cookie
- 前端从Cookie中取出值,添加到请求头中
- 服务端验证请求头中的值是否与Cookie匹配
// 服务端设置Cookie
app.use((req, res, next) => {
res.cookie('XSRF-TOKEN', req.csrfToken(), {
httpOnly: false // 允许前端读取
});
next();
});
// 前端Axios配置
axios.interceptors.request.use(config => {
config.headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
return config;
});
高级防护策略
SameSite Cookie属性
现代浏览器支持SameSite Cookie属性,可以有效防止CSRF:
app.use(session({
secret: 'your-secret',
cookie: {
sameSite: 'strict', // 或'lax'
secure: true // 仅HTTPS
}
}));
SameSite有三种模式:
- Strict:完全禁止第三方Cookie
- Lax:允许部分安全请求(如导航)携带Cookie
- None:关闭SameSite保护
自定义请求头
要求AJAX请求携带自定义头,因为跨域请求默认不能发送自定义头:
// 前端代码
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': token
},
body: JSON.stringify(payload)
});
// 服务端验证
app.post('/api/transfer', (req, res) => {
if (!req.get('X-Requested-With')) {
return res.status(403).send('Invalid request');
}
// 处理逻辑
});
实际应用中的注意事项
令牌存储与分发
令牌应该:
- 每个会话使用不同令牌
- 每个表单使用不同令牌
- 令牌应有有效期
// 动态令牌生成
const crypto = require('crypto');
function generateToken(session) {
const token = crypto.randomBytes(32).toString('hex');
session.csrfTokens = session.csrfTokens || [];
session.csrfTokens.push(token);
// 限制令牌数量
if (session.csrfTokens.length > 5) {
session.csrfTokens.shift();
}
return token;
}
AJAX请求处理
对于AJAX应用,需要确保:
- 初始页面加载时获取CSRF令牌
- 后续请求携带该令牌
// 前端存储令牌
let csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// 所有AJAX请求自动添加
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
}
});
错误处理
应该提供清晰的CSRF验证失败响应:
app.use((err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') return next(err);
res.status(403).json({
error: '会话已过期,请刷新页面重试'
});
});
性能优化考虑
令牌缓存
对于高流量应用,可以考虑:
const tokenCache = new LRU({
max: 1000, // 缓存1000个令牌
maxAge: 1000 * 60 * 15 // 15分钟
});
app.post('/api', (req, res) => {
const token = req.headers['x-csrf-token'];
if (tokenCache.has(token)) {
// 快速验证
return next();
}
// 完整验证流程
});
静态资源豁免
对不需要CSRF保护的端点进行豁免:
const csrfWhitelist = ['/api/public', '/healthcheck'];
app.use(csrfProtection.unless({
path: csrfWhitelist
}));
与其他安全措施的结合
内容安全策略(CSP)
CSP可以限制外部资源加载,间接防止某些CSRF攻击:
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'"
);
next();
});
速率限制
防止令牌暴力破解:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100次请求
});
app.use('/login', limiter);