概述
跨站请求伪造(Cross-Site Request Forgery,简称CSRF)是Web应用中常见的安全漏洞之一。攻击者利用用户已登录的身份,诱导用户执行非预期的操作,从而达到篡改数据、转移资金等恶意目的。本集将深入探讨CSRF攻击的原理、危害以及Vue 3应用中的防御策略,帮助开发者构建更加安全的Vue 3应用。
一、CSRF攻击基础概念
1.1 CSRF攻击的定义
CSRF攻击是一种网络攻击方式,攻击者通过诱导用户访问恶意网站或点击恶意链接,利用用户已登录的身份,在用户不知情的情况下,向目标网站发送伪造的请求,从而执行非预期的操作。
1.2 CSRF攻击的工作原理
- 用户登录目标网站A,获取身份凭证(如Cookie、Token)
- 目标网站A将身份凭证存储在用户浏览器中
- 用户在未登出网站A的情况下,访问恶意网站B
- 恶意网站B向网站A发送伪造的请求,请求中包含恶意操作(如转账、修改密码等)
- 用户浏览器携带网站A的身份凭证,向网站A发送请求
- 网站A验证身份凭证有效,执行恶意操作
1.3 CSRF攻击的特点
- 利用用户身份:攻击者不需要获取用户的凭证,只需利用用户已登录的状态
- 跨站执行:攻击请求来自第三方网站,而非用户直接操作
- 难以检测:攻击请求与正常请求在外观上没有区别
- 危害巨大:可导致用户数据被篡改、资金损失等严重后果
二、CSRF攻击的类型
2.1 GET型CSRF
GET型CSRF攻击是指攻击者通过诱导用户点击包含恶意GET请求的链接,执行非预期操作。
攻击示例:
<!-- 恶意网站B中的HTML -->
<img src="https://bank.example.com/transfer?to=attacker&amount=10000" style="display:none;">当用户访问恶意网站B时,浏览器会自动发送GET请求到银行网站,执行转账操作。
2.2 POST型CSRF
POST型CSRF攻击是指攻击者通过表单提交包含恶意操作的POST请求。
攻击示例:
<!-- 恶意网站B中的HTML -->
<form action="https://bank.example.com/transfer" method="POST" id="csrf-form">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>
document.getElementById('csrf-form').submit();
</script>当用户访问恶意网站B时,浏览器会自动提交表单,执行转账操作。
2.3 链接型CSRF
链接型CSRF攻击是指攻击者通过社交媒体、邮件等方式,诱导用户点击包含恶意请求的链接。
攻击示例:
https://bank.example.com/transfer?to=attacker&amount=10000当用户点击该链接时,如果用户已登录银行网站,就会执行转账操作。
三、CSRF攻击的危害
3.1 用户数据被篡改
攻击者可以通过CSRF攻击修改用户的个人信息、密码、邮箱等敏感数据。
3.2 资金损失
对于金融类网站,攻击者可以通过CSRF攻击执行转账、购买等操作,导致用户资金损失。
3.3 权限提升
攻击者可以通过CSRF攻击修改用户权限,提升为管理员权限,从而进一步控制网站。
3.4 数据泄露
攻击者可以通过CSRF攻击获取用户的敏感数据,如个人信息、交易记录等。
3.5 网站声誉受损
CSRF攻击可能导致网站用户数据泄露、资金损失等问题,严重影响网站的声誉和用户信任。
四、CSRF攻击的防御原理
4.1 同源策略
同源策略是浏览器的安全机制,限制不同源的网页之间的交互。但同源策略并不能完全防止CSRF攻击,因为GET请求、表单提交等不受同源策略限制。
4.2 验证请求来源
通过验证请求的Origin或Referer头,可以判断请求是否来自合法的来源。
4.3 随机令牌验证
为每个请求添加随机生成的令牌(Token),服务器验证令牌的有效性,从而防止CSRF攻击。
4.4 SameSite Cookie
设置Cookie的SameSite属性,限制Cookie的发送范围,从而防止跨站请求携带Cookie。
五、Vue 3中的CSRF防护措施
5.1 SameSite Cookie
SameSite Cookie是最有效的CSRF防御措施之一,通过设置Cookie的SameSite属性,可以限制Cookie在跨站请求中的发送。
SameSite属性值:
Strict:只允许同源请求携带CookieLax:允许部分跨站请求携带Cookie(如GET请求)None:允许所有跨站请求携带Cookie,但必须同时设置Secure属性
设置示例:
Set-Cookie: sessionId=123456; HttpOnly; Secure; SameSite=StrictVue 3项目中配置:
- 前端配置:Vue 3本身不需要特殊配置,由后端设置Cookie属性
- 后端配置:
- Node.js/Express:
const express = require('express'); const app = express(); app.use((req, res, next) => { res.cookie('sessionId', '123456', { httpOnly: true, secure: true, sameSite: 'strict' }); next(); }); - Django:
# settings.py SESSION_COOKIE_SAMESITE = 'Strict' CSRF_COOKIE_SAMESITE = 'Strict'
- Node.js/Express:
5.2 CSRF Token
CSRF Token是一种常用的防御措施,通过为每个请求添加随机生成的令牌,服务器验证令牌的有效性,从而防止CSRF攻击。
实现原理:
- 服务器生成随机令牌,存储在用户会话中
- 服务器将令牌发送给前端,前端存储令牌
- 前端发送请求时,将令牌添加到请求中
- 服务器验证令牌的有效性,只有令牌有效才执行请求
Vue 3中实现CSRF Token:
5.2.1 后端生成并返回Token
Node.js/Express示例:
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const app = express();
// 配置会话
app.use(session({
secret: 'secret_key',
resave: false,
saveUninitialized: true
}));
// 生成CSRF Token
app.use((req, res, next) => {
// 生成随机Token
const csrfToken = crypto.randomBytes(32).toString('hex');
// 存储到会话中
req.session.csrfToken = csrfToken;
// 返回给前端
res.locals.csrfToken = csrfToken;
next();
});
// 验证CSRF Token
app.post('/protected', (req, res) => {
const { csrfToken } = req.body;
if (csrfToken && csrfToken === req.session.csrfToken) {
// Token验证通过,执行操作
res.json({ success: true });
} else {
// Token验证失败
res.status(403).json({ success: false, message: 'CSRF Token验证失败' });
}
});5.2.2 前端存储并发送Token
Vue 3组件示例:
<template>
<form @submit.prevent="handleSubmit">
<input type="hidden" :value="csrfToken" name="csrfToken">
<input type="text" v-model="data" placeholder="请输入数据">
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const csrfToken = ref('')
const data = ref('')
// 获取CSRF Token
onMounted(async () => {
const response = await axios.get('/csrf-token')
csrfToken.value = response.data.csrfToken
})
// 提交表单
const handleSubmit = async () => {
try {
const response = await axios.post('/protected', {
csrfToken: csrfToken.value,
data: data.value
})
console.log('提交成功', response.data)
} catch (error) {
console.error('提交失败', error)
}
}
</script>5.2.3 Axios全局配置
配置Axios自动添加CSRF Token:
// src/utils/axios.js
import axios from 'axios'
const instance = axios.create({
baseURL: '/api',
timeout: 10000
})
// 从Cookie中获取CSRF Token
const getCsrfTokenFromCookie = () => {
const match = document.cookie.match(/(^|;)\s*csrfToken\s*=\s*([^;]+)/)
return match ? match[2] : null
}
// 请求拦截器,自动添加CSRF Token
instance.interceptors.request.use(config => {
const token = getCsrfTokenFromCookie()
if (token) {
// 添加到请求头
config.headers['X-CSRF-Token'] = token
// 或添加到请求体
if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
config.data = config.data || {}
config.data.csrfToken = token
}
}
return config
}, error => {
return Promise.reject(error)
})
export default instance5.3 验证Origin/Referer头
通过验证请求的Origin或Referer头,可以判断请求是否来自合法的来源。
实现原理:
- 浏览器在发送跨站请求时,会自动添加Origin或Referer头
- 服务器验证Origin或Referer头是否来自合法的域名
- 只有验证通过的请求才会被执行
Vue 3项目中实现:
后端验证示例(Node.js/Express):
app.use((req, res, next) => {
const origin = req.headers.origin;
const referer = req.headers.referer;
const allowedOrigins = ['https://example.com', 'https://www.example.com'];
// 验证Origin头
if (origin && allowedOrigins.includes(origin)) {
return next();
}
// 验证Referer头
if (referer) {
const refererOrigin = new URL(referer).origin;
if (allowedOrigins.includes(refererOrigin)) {
return next();
}
}
// 验证失败
res.status(403).json({ success: false, message: '请求来源验证失败' });
});5.4 双重提交Cookie
双重提交Cookie是一种无需服务器存储Token的防御措施,通过将Token同时存储在Cookie和请求中,服务器验证两者是否一致。
实现原理:
- 服务器生成随机Token,存储在Cookie中
- 前端从Cookie中获取Token,添加到请求中
- 服务器验证请求中的Token与Cookie中的Token是否一致
- 只有一致的请求才会被执行
Vue 3中实现双重提交Cookie:
5.4.1 后端生成Token
Node.js/Express示例:
const express = require('express');
const crypto = require('crypto');
const app = express();
// 生成CSRF Token并存储在Cookie中
app.use((req, res, next) => {
// 检查是否已存在Token
if (!req.cookies.csrfToken) {
// 生成随机Token
const csrfToken = crypto.randomBytes(32).toString('hex');
// 存储在Cookie中
res.cookie('csrfToken', csrfToken, {
httpOnly: false, // 允许前端访问
secure: true,
sameSite: 'strict'
});
}
next();
});
// 验证CSRF Token
app.post('/protected', (req, res) => {
const { csrfToken } = req.body;
const cookieToken = req.cookies.csrfToken;
if (csrfToken && csrfToken === cookieToken) {
// Token验证通过,执行操作
res.json({ success: true });
} else {
// Token验证失败
res.status(403).json({ success: false, message: 'CSRF Token验证失败' });
}
});5.4.2 前端实现
Vue 3组件示例:
<template>
<button @click="handleSubmit">执行操作</button>
</template>
<script setup>
import { ref } from 'vue'
import axios from 'axios'
// 从Cookie中获取CSRF Token
const getCsrfToken = () => {
const match = document.cookie.match(/(^|;)\s*csrfToken\s*=\s*([^;]+)/)
return match ? match[2] : null
}
// 提交请求
const handleSubmit = async () => {
try {
const csrfToken = getCsrfToken()
const response = await axios.post('/protected', {
csrfToken: csrfToken
})
console.log('操作成功', response.data)
} catch (error) {
console.error('操作失败', error)
}
}
</script>5.5 自定义请求头
通过添加自定义请求头,可以防止CSRF攻击,因为浏览器在发送跨站请求时,不会自动添加自定义请求头。
实现原理:
- 前端发送请求时,添加自定义请求头
- 服务器验证请求中是否包含自定义请求头
- 只有包含自定义请求头的请求才会被执行
Vue 3中实现:
Axios配置示例:
// src/utils/axios.js
import axios from 'axios'
const instance = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'X-Requested-With': 'XMLHttpRequest' // 添加自定义请求头
}
})
export default instance后端验证示例:
app.use((req, res, next) => {
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
return next();
}
res.status(403).json({ success: false, message: '请求头验证失败' });
});六、Vue 3 CSRF防护最佳实践
6.1 结合多种防御措施
单一的防御措施可能存在漏洞,建议结合多种防御措施,如SameSite Cookie + CSRF Token,以提高安全性。
6.2 对敏感操作加强防护
对于敏感操作(如转账、修改密码、删除数据等),应加强防护措施,如:
- 使用Strict模式的SameSite Cookie
- 要求用户重新验证身份(如输入密码、验证码等)
- 添加操作确认机制
6.3 合理使用HTTP方法
遵循RESTful API设计原则,合理使用HTTP方法:
- GET请求只用于获取数据,不执行修改操作
- POST/PUT/DELETE请求用于修改数据
- 避免使用GET请求执行敏感操作
6.4 保持库和框架更新
定期更新Vue 3及相关库(如Axios),修复已知的安全漏洞。
6.5 实施内容安全策略(CSP)
实施严格的CSP策略,可以防止恶意脚本的执行,从而减轻CSRF攻击的影响。
CSP配置示例:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none';6.6 教育用户安全意识
提高用户的安全意识,避免点击未知链接、访问不可信网站,定期登出敏感网站。
七、CSRF攻击案例分析
7.1 案例:某银行网站CSRF攻击
背景:某银行网站存在CSRF漏洞,攻击者通过CSRF攻击窃取用户资金。
攻击过程:
- 用户登录银行网站,获取身份凭证
- 攻击者发送包含恶意链接的邮件给用户
- 用户点击恶意链接,访问恶意网站
- 恶意网站向银行网站发送转账请求,请求中包含攻击者的账户和转账金额
- 用户浏览器携带银行网站的身份凭证,向银行网站发送请求
- 银行网站验证身份凭证有效,执行转账操作
防御措施:
- 实施SameSite Cookie
- 添加CSRF Token验证
- 对敏感操作要求二次验证
- 实施内容安全策略
7.2 案例:某社交平台CSRF攻击
背景:某社交平台存在CSRF漏洞,攻击者通过CSRF攻击发布恶意内容。
攻击过程:
- 用户登录社交平台,获取身份凭证
- 攻击者创建包含恶意表单的网站
- 用户访问恶意网站,表单自动提交
- 恶意表单向社交平台发送发布内容请求,内容包含恶意链接
- 社交平台验证身份凭证有效,发布恶意内容
防御措施:
- 实施CSRF Token验证
- 验证请求的Origin/Referer头
- 对发布内容进行审核
八、Vue 3 CSRF防护代码实践
8.1 创建CSRF防护的Axios实例
// src/utils/axios.js
import axios from 'axios'
// 创建Axios实例
const api = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
// 从Cookie中获取CSRF Token
const getCsrfToken = () => {
const match = document.cookie.match(/(^|;)\s*csrfToken\s*=\s*([^;]+)/)
return match ? match[2] : null
}
// 请求拦截器,添加CSRF Token
api.interceptors.request.use(config => {
// 获取CSRF Token
const csrfToken = getCsrfToken()
// 添加到请求头
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken
}
// 添加到请求体
if (['post', 'put', 'delete'].includes(config.method) && config.data) {
config.data = { ...config.data, csrfToken }
}
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截器,处理错误
api.interceptors.response.use(response => {
return response
}, error => {
if (error.response && error.response.status === 403) {
console.error('CSRF验证失败:', error.response.data.message)
// 可以添加重定向到登录页或显示错误信息
}
return Promise.reject(error)
})
export default api8.2 创建CSRF防护的表单组件
<template>
<form @submit.prevent="handleSubmit" class="csrf-form">
<!-- 隐藏的CSRF Token字段 -->
<input type="hidden" :value="csrfToken" name="csrfToken">
<!-- 表单内容 -->
<slot name="fields"></slot>
<!-- 提交按钮 -->
<div class="form-actions">
<slot name="actions">
<button type="submit" class="submit-btn">提交</button>
</slot>
</div>
</form>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import api from '@/utils/axios'
const props = defineProps({
// 是否自动获取CSRF Token
autoFetchToken: {
type: Boolean,
default: true
},
// 自定义CSRF Token
token: {
type: String,
default: ''
}
})
const emit = defineEmits(['submit', 'error'])
const csrfToken = ref(props.token)
const isLoading = ref(false)
// 获取CSRF Token
const fetchCsrfToken = async () => {
try {
const response = await api.get('/csrf-token')
csrfToken.value = response.data.csrfToken
} catch (error) {
emit('error', error)
console.error('获取CSRF Token失败:', error)
}
}
// 监听token属性变化
watch(() => props.token, (newToken) => {
if (newToken) {
csrfToken.value = newToken
}
})
// 组件挂载时获取Token
onMounted(() => {
if (props.autoFetchToken && !props.token) {
fetchCsrfToken()
}
})
// 处理表单提交
const handleSubmit = async (event) => {
isLoading.value = true
try {
// 触发submit事件,传递表单数据和CSRF Token
emit('submit', {
csrfToken: csrfToken.value,
event
})
} catch (error) {
emit('error', error)
console.error('表单提交失败:', error)
} finally {
isLoading.value = false
}
}
// 暴露方法
defineExpose({
fetchCsrfToken,
isLoading
})
</script>
<style scoped>
.csrf-form {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-actions {
margin-top: 20px;
text-align: right;
}
.submit-btn {
padding: 10px 20px;
background-color: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.submit-btn:hover {
background-color: #66b1ff;
}
.submit-btn:disabled {
background-color: #a0cfff;
cursor: not-allowed;
}
</style>8.3 使用CSRF防护的表单组件
<template>
<div class="app">
<h1>CSRF防护示例</h1>
<CsrfForm @submit="handleSubmit" @error="handleError" ref="csrfFormRef">
<template #fields>
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" v-model="formData.username" class="form-input">
</div>
<div class="form-group">
<label for="email">邮箱</label>
<input type="email" id="email" v-model="formData.email" class="form-input">
</div>
</template>
<template #actions>
<button
type="submit"
class="submit-btn"
:disabled="csrfFormRef?.isLoading"
>
{{ csrfFormRef?.isLoading ? '提交中...' : '提交' }}
</button>
</template>
</CsrfForm>
<div v-if="message" :class="['message', message.type]">
{{ message.text }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import CsrfForm from '@/components/CsrfForm.vue'
import api from '@/utils/axios'
const csrfFormRef = ref(null)
const formData = ref({
username: '',
email: ''
})
const message = ref(null)
// 处理表单提交
const handleSubmit = async ({ csrfToken }) => {
try {
const response = await api.post('/update-profile', {
...formData.value,
csrfToken
})
message.value = {
type: 'success',
text: '个人信息更新成功'
}
} catch (error) {
message.value = {
type: 'error',
text: '个人信息更新失败: ' + (error.response?.data?.message || error.message)
}
}
}
// 处理错误
const handleError = (error) => {
message.value = {
type: 'error',
text: '操作失败: ' + error.message
}
}
</script>
<style scoped>
.app {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.message {
margin-top: 20px;
padding: 10px;
border-radius: 4px;
}
.message.success {
background-color: #f0f9eb;
color: #67c23a;
border: 1px solid #e1f3d8;
}
.message.error {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fbc4c4;
}
</style>九、总结与展望
CSRF攻击是Web应用中常见的安全漏洞之一,对Vue 3应用的安全性构成严重威胁。通过了解CSRF攻击的原理、类型和危害,以及Vue 3中的防护措施,开发者可以构建更加安全的Vue 3应用。
防御CSRF攻击的核心原则:
- 限制Cookie的发送范围:实施SameSite Cookie
- 验证请求的合法性:添加CSRF Token验证
- 验证请求来源:检查Origin/Referer头
- 结合多种防御措施:提高安全性
- 对敏感操作加强防护:要求二次验证
未来发展趋势:
- 浏览器原生支持的防护机制将不断增强
- 开发框架和库将提供更强大的内置安全防护
- AI技术将被用于自动检测和防御CSRF攻击
- 安全开发理念将更加深入人心
通过遵循安全最佳实践,结合Vue 3的防护机制和手动防御策略,开发者可以有效防止CSRF攻击,保护用户数据安全和应用程序的完整性。
参考资料
扩展学习
- 学习OWASP Top 10安全漏洞
- 掌握内容安全策略(CSP)的详细配置
- 了解其他Web安全漏洞,如XSS、SQL注入等
- 学习使用安全扫描工具,如OWASP ZAP、Burp Suite等
- 参与开源项目的安全审计
下一集预告:我们将继续探讨Vue 3应用的安全防护,重点介绍数据加密与安全传输的原理和实现方法。