您现在的位置是:网站首页 > CSRF防护文章详情

CSRF防护

CSRF攻击的原理

CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的Web安全威胁。攻击者诱导用户在已认证的Web应用中执行非预期的操作。这种攻击利用了Web应用对用户浏览器的信任机制。

典型的CSRF攻击流程:

  1. 用户登录信任的网站A,服务器返回认证Cookie
  2. 用户未登出情况下访问恶意网站B
  3. 网站B包含向网站A发起请求的代码
  4. 浏览器自动携带网站A的Cookie发送请求
  5. 网站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:

  1. 服务端在响应头中设置自定义Cookie
  2. 前端从Cookie中取出值,添加到请求头中
  3. 服务端验证请求头中的值是否与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应用,需要确保:

  1. 初始页面加载时获取CSRF令牌
  2. 后续请求携带该令牌
// 前端存储令牌
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);

上一篇: 加密与哈希

下一篇: XSS防护

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步