🍃前言
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. 模拟攻击流程
-
启动两个应用:
node bank-app.js node malicious-app.js -
访问银行网站(http://localhost:3000/login.html),用 alice/alice123 登录
-
不要退出,新标签页访问恶意网站(http://localhost:4000)
-
点击"立即抽奖"按钮,你会发现钱被转走了!
CSRF攻击的关键点
- 用户已登录:用户在银行网站保持登录状态
- 会话保持:浏览器会自动携带cookie/session
- 诱骗点击:恶意网站伪造请求,利用用户的登录状态执行操作
- 无感知:用户可能完全不知道发生了什么
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: 严格模式,完全禁止第三方cookielax: 宽松模式,允许安全的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防御的?
- CSRF令牌:主要防御机制
- SameSite Cookie:额外保护层
- Origin/Referer检查:补充防御
- 关键操作二次验证:如短信/邮件验证
总结
上面就是CSRF的全部的内容
下次见!!
🍃