CSRF 攻击:原理与防御

理解 CSRF 攻击原理,掌握 SameSite、Token、Referer 三种防御方式

问题

什么是 CSRF(跨站请求伪造)攻击?如何防御?

解答

CSRF 攻击原理

CSRF 利用用户已登录的身份,在用户不知情的情况下,诱导浏览器向目标网站发送请求。

攻击流程:
1. 用户登录 bank.com,浏览器保存了 Cookie
2. 用户访问恶意网站 evil.com
3. evil.com 页面包含一个自动提交的表单,指向 bank.com/transfer
4. 浏览器自动携带 bank.com 的 Cookie 发送请求
5. bank.com 无法区分这是用户主动操作还是被诱导的

恶意页面示例:

<!-- evil.com 的页面 -->
<form action="https://bank.com/transfer" method="POST" id="hack">
  <input type="zzinb" name="to" value="attacker" />
  <input type="zzinb" name="amount" value="10000" />
</form>
<script>
  // 页面加载后自动提交
  document.getElementById('hack').submit()
</script>

通过设置 Cookie 的 SameSite 属性,限制跨站请求携带 Cookie。

// Node.js Express 设置 Cookie
res.cookie('sessionId', 'abc123', {
  httpOnly: true,
  secure: true,
  sameSite: 'Strict' // 或 'Lax'
})
SameSite 取值:
- Strict: 完全禁止跨站携带,最安全但影响用户体验
- Lax: 允许导航类请求(链接跳转)携带,禁止 POST/iframe/AJAX
- None: 不限制,必须配合 Secure 使用

防御方式二:CSRF Token

服务端生成随机 Token,前端请求时携带,服务端验证。

// 服务端:生成并存储 Token
const crypto = require('crypto')

function generateCsrfToken(session) {
  const token = crypto.randomBytes(32).toString('hex')
  session.csrfToken = token
  return token
}

// 服务端:验证 Token 的中间件
function verifyCsrfToken(req, res, next) {
  const token = req.headers['x-csrf-token'] || req.body._csrf
  
  if (!token || token !== req.session.csrfToken) {
    return res.status(403).json({ error: 'CSRF token 验证失败' })
  }
  next()
}
// 前端:从页面或接口获取 Token,请求时携带
const csrfToken = document.querySelector('meta[name="csrf-token"]').content

fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken // 携带 Token
  },
  body: JSON.stringify({ to: 'friend', amount: 100 })
})

防御方式三:Referer / Origin 校验

检查请求来源是否为可信域名。

// 服务端中间件:校验请求来源
function checkOrigin(req, res, next) {
  const origin = req.headers.origin || req.headers.referer
  const allowedOrigins = ['https://bank.com', 'https://www.bank.com']
  
  if (!origin) {
    // 某些情况下可能没有 Origin/Referer,根据业务决定是否放行
    return res.status(403).json({ error: '缺少来源信息' })
  }
  
  const isAllowed = allowedOrigins.some(allowed => origin.startsWith(allowed))
  
  if (!isAllowed) {
    return res.status(403).json({ error: '非法请求来源' })
  }
  
  next()
}

综合防御示例

const express = require('express')
const session = require('express-session')
const crypto = require('crypto')

const app = express()

// Session 配置
app.use(session({
  secret: 'your-secret-key',
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'Lax' // 第一道防线
  }
}))

// CSRF Token 中间件
app.use((req, res, next) => {
  // GET 请求时生成 Token
  if (req.method === 'GET') {
    req.session.csrfToken = crypto.randomBytes(32).toString('hex')
  }
  next()
})

// 需要保护的接口
app.post('/api/transfer', (req, res) => {
  // 验证 Token
  const token = req.headers['x-csrf-token']
  if (token !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' })
  }
  
  // 验证 Origin
  const origin = req.headers.origin
  if (origin !== 'https://bank.com') {
    return res.status(403).json({ error: 'Invalid origin' })
  }
  
  // 处理业务逻辑
  res.json({ success: true })
})

关键点

  • CSRF 本质:利用浏览器自动携带 Cookie 的特性,伪造用户请求
  • SameSite Cookie:从源头阻止跨站请求携带认证信息,推荐设置为 Lax
  • CSRF Token:服务端生成随机值,前端请求时携带验证,攻击者无法获取
  • Referer/Origin 校验:验证请求来源域名,但可能被篡改或缺失
  • 防御组合:生产环境建议 SameSite + Token 双重防护