Hi,我是布兰妮甜 !在当今高度互联的Web应用环境中,安全威胁无处不在。CSRF(跨站请求伪造)作为一种常见的Web安全漏洞,长期位居OWASP Top 10安全威胁榜单。这种攻击方式利用了网站对用户浏览器的信任,可能导致用户在不知情的情况下执行非预期的操作,如资金转账、密码更改等。本文将深入剖析
CSRF攻击原理
,详细介绍多种有效的防护机制
,并提供实用的JavaScript代码示例,帮助开发者构建更加安全的Web应用。无论您是前端还是后端开发者,理解并实施CSRF防护都是开发现代Web应用的必备技能。
文章目录
一、什么是CSRF攻击?
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱使用户在不知情的情况下,以用户身份向服务器发送恶意请求。这种攻击利用了Web应用对用户浏览器的信任机制。
简单来说,当用户登录某个网站后,攻击者可以构造一个恶意页面,诱使用户访问,该页面会自动向目标网站发送请求,由于用户已登录,这些请求会带上合法的会话凭证,从而执行攻击者预设的操作。
二、CSRF攻击原理
- 用户已登录目标网站
- 目标网站没有足够的CSRF防护措施
- 用户访问了攻击者构造的恶意页面
攻击示例
假设有一个银行网站的转账接口:
POST /transfer HTTP/1.1
Host: bank.example.com
Content-Type: application/x-www-form-urlencoded
amount=1000&to=attacker
攻击者可以构造如下恶意页面:
<!-- 恶意网站上的页面 -->
<body onload="document.forms[0].submit()">
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="amount" value="1000" />
<input type="hidden" name="to" value="attacker" />
</form>
</body>
当已登录银行网站的用户访问此页面时,会自动提交转账表单,完成攻击。
三、CSRF防护机制
3.1 验证HTTP Referer头部
检查请求的来源是否合法:
function checkReferer(req) {
const referer = req.headers.referer;
if (!referer || !referer.startsWith('https://yourdomain.com')) {
throw new Error('Invalid request source');
}
}
缺点:Referer可能被篡改或缺失(某些隐私设置会禁止发送Referer)。
3.2 使用CSRF Token
这是最常用的防护方法,原理是:
- 服务器生成一个随机token,存储在session中
- 将token嵌入表单或meta标签
- 提交表单时带上token,服务器验证token是否匹配
服务端实现示例:
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
app.use(session({
secret: 'your secret key',
resave: false,
saveUninitialized: true
}));
// 生成CSRF Token中间件
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(16).toString('hex');
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// 表单页面
app.get('/form', (req, res) => {
res.send(`
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="${res.locals.csrfToken}">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
`);
});
// 处理表单提交
app.post('/process', (req, res) => {
const { _csrf, data } = req.body;
if (_csrf !== req.session.csrfToken) {
return res.status(403).send('Invalid CSRF token');
}
// 处理合法请求
res.send(`Data received: ${data}`);
});
app.listen(3000);
前端AJAX请求示例:
// 从meta标签获取CSRF Token
function getCSRFToken() {
return document.querySelector('meta[name="csrf-token"]').content;
}
// 发起AJAX请求
function postData(data) {
fetch('/api/endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCSRFToken()
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log(data));
}
3.3 SameSite Cookie属性
SameSite是Cookie的一个属性,可以限制第三方网站发起的请求携带Cookie:
// 设置SameSite属性
app.use(session({
secret: 'your secret key',
cookie: {
sameSite: 'strict', // 或 'lax'
secure: true // 仅HTTPS
}
}));
SameSite有三种模式:
- Strict:完全禁止第三方Cookie
- Lax:允许部分安全请求(如导航)携带Cookie
- None:不限制(必须与Secure一起使用)
3.4 双重提交Cookie验证
这种方法将CSRF Token同时放在Cookie和请求中(表单或头部),服务器验证两者是否匹配:
// 设置CSRF Token Cookie
app.use((req, res, next) => {
const token = crypto.randomBytes(16).toString('hex');
res.cookie('XSRF-TOKEN', token, { httpOnly: false });
req.csrfToken = token;
next();
});
// 验证中间件
app.use((req, res, next) => {
const cookieToken = req.cookies['XSRF-TOKEN'];
const bodyToken = req.body._csrf || req.headers['x-csrf-token'];
if (!cookieToken || cookieToken !== bodyToken) {
return res.status(403).send('CSRF validation failed');
}
next();
});
四、现代框架中的CSRF防护
4.1 Express.js中使用csurf中间件
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
// 表单页面
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// 处理表单提交
app.post('/process', csrfProtection, (req, res) => {
// 如果token验证失败会自动返回403
res.send('Form processed');
});
4.2 在React中处理CSRF Token
// 从服务器获取CSRF Token并存储在meta标签
fetch('/csrf-token')
.then(res => res.json())
.then(data => {
const meta = document.createElement('meta');
meta.name = 'csrf-token';
meta.content = data.token;
document.head.appendChild(meta);
});
// 封装带有CSRF Token的fetch
function secureFetch(url, options = {}) {
const token = document.querySelector('meta[name="csrf-token"]').content;
options.headers = {
...options.headers,
'X-CSRF-Token': token
};
return fetch(url, options);
}
五、总结
CSRF防护是现代Web应用安全的重要组成部分。通过理解CSRF攻击原理和掌握各种防护技术,开发者可以有效地保护用户免受此类攻击。在实际开发中,建议结合使用CSRF Token和SameSite Cookie属性,并根据应用的具体需求选择合适的防护策略。