✍️深入理解CSRF攻击及Express防御策略

151 阅读2分钟

🍃前言

CSRF这一种攻击方式出现的频率太高了,与XSS差不多。

本期就来说说CSRF攻击以及防御策略

🤔什么是CSRF攻击?

CSRF(Cross-Site Request Forgery,跨站请求伪造) 是一种常见的Web安全威胁。

CSRF攻击利用了Web应用对用户浏览器的信任

攻击者诱骗用户在【已登录的目标网站上执行非预期的操作】。

这种攻击【利用了Web应用对用户浏览器的信任机制】。

简单嗦:当用户登录了某个网站(如银行网站)后,又在同一浏览器中访问了恶意网站,这个恶意网站可以伪造请求让用户在不知情的情况下执行某些操作(如转账)。

🌰CSRF攻击原理演示(Express环境)

让我们通过一个本地示例来理解CSRF攻击是如何工作的。

1. 创建受害者网站(银行模拟)

首先创建一个简单的Express应用模拟银行网站:

mkdir csrf && cd csrf && code .
pnpm init
pnpm add express body-parser cookie-parser express-session

创建 bank-app.js:

const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const session = require('express-session');

const app = express();

// 中间件
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // 本地测试用,生产环境应为true
}));

// 模拟用户数据库
const users = {
  alice: { password: 'alice123', balance: 1000 }
};

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (users[username] && users[username].password === password) {
    req.session.user = username;
    return res.send('登录成功!<a href="/account">查看账户</a>');
  }
  res.send('登录失败');
});

// 账户页面
app.get('/account', (req, res) => {
  if (!req.session.user) return res.redirect('/login.html');
  res.send(`
    <h1>欢迎, ${req.session.user}</h1>
    <p>余额: $${users[req.session.user].balance}</p>
    <form action="/transfer" method="POST">
      <h2>转账</h2>
      金额: <input type="number" name="amount"><br>
      目标账户: <input type="text" name="to"><br>
      <button type="submit">转账</button>
    </form>
    <a href="/logout">退出</a>
  `);
});

// 转账处理
app.post('/transfer', (req, res) => {
  if (!req.session.user) return res.status(403).send('未登录');
  
  const { amount, to } = req.body;
  const user = users[req.session.user];
  
  if (user.balance >= amount) {
    user.balance -= Number(amount);
    users[to].balance += Number(amount);
    return res.send(`成功转账 $${amount}${to}`);
  }
  
  res.send('余额不足');
});

// 退出
app.get('/logout', (req, res) => {
  req.session.destroy();
  res.redirect('/login.html');
});

// 登录页面
app.get('/login.html', (req, res) => {
  res.send(`
    <form action="/login" method="POST">
      用户名: <input type="text" name="username"><br>
      密码: <input type="password" name="password"><br>
      <button type="submit">登录</button>
    </form>
  `);
});

app.listen(3000, () => console.log('银行应用运行在 http://localhost:3000'));

2. 创建攻击者网站

创建 malicious-app.js:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send(`
    <h1>免费抽奖!</h1>
    <p>点击下方按钮赢取百万大奖!</p>
    <form action="http://localhost:3000/transfer" method="POST">
      <input type="hidden" name="amount" value="500">
      <input type="hidden" name="to" value="attacker">
      <button type="submit">立即抽奖</button>
    </form>
    <img src="http://localhost:3000/account" onerror="document.forms[0].submit()" style="display:none;">
  `);
});

app.listen(4000, () => console.log('恶意网站运行在 http://localhost:4000'));

3. 模拟攻击流程

  1. 启动两个应用:

    node bank-app.js
    node malicious-app.js
    
  2. 访问银行网站(http://localhost:3000/login.html),用 alice/alice123 登录

  3. 不要退出,新标签页访问恶意网站(http://localhost:4000)

  4. 点击"立即抽奖"按钮,你会发现钱被转走了!

CSRF攻击的关键点

  1. 用户已登录:用户在银行网站保持登录状态
  2. 会话保持:浏览器会自动携带cookie/session
  3. 诱骗点击:恶意网站伪造请求,利用用户的登录状态执行操作
  4. 无感知:用户可能完全不知道发生了什么

Express中的CSRF防御

1. 使用CSRF令牌

最有效的防御方式是使用CSRF令牌。修改银行应用:

安装csurf中间件:

npm install csurf

更新 bank-app.js

// 添加在顶部
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

// 更新账户路由
app.get('/account', csrfProtection, (req, res) => {
  if (!req.session.user) return res.redirect('/login.html');
  res.send(`
    <h1>欢迎, ${req.session.user}</h1>
    <p>余额: $${users[req.session.user].balance}</p>
    <form action="/transfer" method="POST">
      <input type="hidden" name="_csrf" value="${req.csrfToken()}">
      <h2>转账</h2>
      金额: <input type="number" name="amount"><br>
      目标账户: <input type="text" name="to"><br>
      <button type="submit">转账</button>
    </form>
    <a href="/logout">退出</a>
  `);
});

// 更新转账路由
app.post('/transfer', csrfProtection, (req, res) => {
  // 原有逻辑不变
});

现在恶意网站无法伪造有效的CSRF令牌,攻击将失败。

2. 同站Cookie (SameSite)

设置cookie的SameSite属性:

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { 
    secure: false, // 本地测试用
    sameSite: 'strict' // 或 'lax'
  }
}));

SameSite有三个值:

  • strict: 严格模式,完全禁止第三方cookie
  • lax: 宽松模式,允许安全的HTTP方法(如GET)的跨站请求
  • none: 无限制(需要与Secure一起使用)

3. 检查Origin/Referer头部

添加中间件检查请求来源:

app.use((req, res, next) => {
  const origin = req.headers.origin || req.headers.referer;
  if (req.method === 'POST' && origin && !origin.includes('localhost:3000')) {
    return res.status(403).send('CSRF检测: 非法请求来源');
  }
  next();
});

市场上是如何进行CSRF防御的?

  1. CSRF令牌:主要防御机制
  2. SameSite Cookie:额外保护层
  3. Origin/Referer检查:补充防御
  4. 关键操作二次验证:如短信/邮件验证

总结

上面就是CSRF的全部的内容

下次见!!

🍃