【缺陷周话】第12期:存储型 XSS
字数 2010 2025-08-18 11:37:45

存储型XSS漏洞分析与防御指南

1. 存储型XSS概述

存储型XSS(Persistent XSS)是指应用程序通过Web请求获取不可信赖的数据,在未检验数据是否存在XSS代码的情况下,便将其存入数据库。当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行XSS代码,从而形成持续攻击。

1.1 基本特征

  • 攻击载荷存储在服务器端(数据库、文件系统等)
  • 每次访问受影响页面都会触发攻击
  • 影响范围广,可攻击所有访问该页面的用户
  • 常见于留言板、评论区、用户资料等需要持久化存储用户输入的场景

1.2 与反射型XSS的区别

特征 存储型XSS 反射型XSS
存储位置 服务器端 不存储
触发方式 访问存储页面 点击恶意链接
影响范围 所有访问用户 仅点击链接的用户
持续时间 持久性 一次性

2. 存储型XSS的危害

存储型XSS可造成以下安全威胁:

  1. Cookie窃取:攻击者可获取用户会话凭证
  2. 页面劫持:篡改页面内容或重定向到恶意网站
  3. 键盘记录:记录用户输入敏感信息
  4. 传播恶意软件:利用浏览器漏洞传播恶意程序
  5. 钓鱼攻击:伪造登录表单窃取凭证

2.1 实际漏洞案例

CVE编号 受影响系统 攻击方式
CVE-2018-19178 JEESNS 1.3 通过HTML EMBED元素攻击
CVE-2018-19170 JPress v1.0-rc.5 通过设置模块输入字段攻击
CVE-2018-19089 Tianti 2.3 通过用户角色名称参数攻击
CVE-2018-17369 EasyCMS 1.3 通过文章标题、内容等字段攻击

3. 漏洞代码分析

3.1 缺陷代码示例

// 从数据库获取用户数据
Connection dbConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database", "user", "password");
Statement statement = dbConnection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT name FROM users WHERE id=0");
String data = resultSet.getString(1);

// 不充分的过滤
data = data.replaceAll("<script>", "");

// 直接输出到页面
response.getWriter().println("User name: " + data);

漏洞点分析

  1. 从数据库获取数据后未进行充分验证
  2. 仅过滤<script>标签,其他HTML标签和属性仍可造成XSS
  3. 直接输出未编码的内容到HTML响应中

3.2 攻击场景

攻击者可提交包含恶意脚本的用户名,如:


该载荷将:

  1. 被存储到数据库
  2. 每次用户访问页面时执行
  3. 绕过简单的<script>过滤

4. 修复方案

4.1 输入验证

对用户提交的数据进行严格验证:

// 使用白名单验证
if (!data.matches("[a-zA-Z0-9]+")) {
    throw new IllegalArgumentException("Invalid input");
}

4.2 输出编码

使用ESAPI进行上下文相关的输出编码:

import org.owasp.esapi.ESAPI;

// HTML实体编码
String safeOutput = ESAPI.encoder().encodeForHTML(data);
response.getWriter().println("User name: " + safeOutput);

ESAPI提供的编码方法:

  • encodeForHTML() - HTML内容编码
  • encodeForHTMLAttribute() - HTML属性编码
  • encodeForJavaScript() - JavaScript上下文编码
  • encodeForCSS() - CSS上下文编码
  • encodeForURL() - URL参数编码

4.3 综合修复代码

// 获取数据
Connection dbConnection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database", "user", "password");
Statement statement = dbConnection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT name FROM users WHERE id=0");
String data = resultSet.getString(1);

// 输出编码
String safeOutput = ESAPI.encoder().encodeForHTML(data);
response.getWriter().println("User name: " + safeOutput);

4.4 设置HttpOnly Cookie

Cookie cookie = new Cookie("sessionID", "123456789");
cookie.setHttpOnly(true);
response.addCookie(cookie);

5. 防御最佳实践

  1. 输入验证

    • 采用白名单而非黑名单策略
    • 根据业务需求定义严格的输入规则
    • 对特殊字符进行过滤或转义
  2. 输出编码

    • 根据输出上下文选择合适的编码方式
    • 使用成熟的编码库而非自行实现
    • 注意不同上下文的编码规则差异
  3. 安全配置

    • 设置HttpOnly和Secure标志的Cookie
    • 实施Content Security Policy (CSP)
    • 使用X-XSS-Protection响应头
  4. 安全开发

    • 将安全要求纳入开发规范
    • 进行安全代码审查
    • 使用静态分析工具检测漏洞

6. 检测与验证

6.1 手动测试方法

  1. 在输入字段尝试提交XSS测试向量:
    <script>alert(1)</script>
    
    "><script>alert(1)</script>
    
  2. 查看页面源代码确认是否被编码
  3. 访问其他页面验证攻击是否持续

6.2 自动化检测

使用工具如:

  • OWASP ZAP
  • Burp Suite
  • 360代码卫士
  • Fortify
  • Checkmarx

7. 相关CWE条目

  • CWE-79: Web页面生成期间输入中和不当(跨站脚本)
  • CWE-80: Web页面中脚本相关HTML标签中和不当(基本XSS)
  • CWE-81: 错误消息Web页面中脚本中和不当
  • CWE-82: Web页面IMG标签属性中脚本中和不当
  • CWE-83: Web页面属性中脚本中和不当

8. 总结

存储型XSS是Web应用中最危险的安全漏洞之一,其持久性特点使得攻击影响范围广、持续时间长。有效防御需要采取多层次的安全措施:

  1. 不信任任何用户输入 - 包括来自数据库的"已存储"数据
  2. 实施深度防御 - 结合输入验证、输出编码和安全配置
  3. 使用安全工具 - 借助自动化工具进行持续检测
  4. 提高安全意识 - 将安全融入开发生命周期

通过遵循这些原则和实践,可以显著降低存储型XSS漏洞的风险,保护用户和系统安全。

存储型XSS漏洞分析与防御指南 1. 存储型XSS概述 存储型XSS(Persistent XSS)是指应用程序通过Web请求获取不可信赖的数据,在未检验数据是否存在XSS代码的情况下,便将其存入数据库。当下一次从数据库中获取该数据时程序也未对其进行过滤,页面再次执行XSS代码,从而形成持续攻击。 1.1 基本特征 攻击载荷存储在服务器端(数据库、文件系统等) 每次访问受影响页面都会触发攻击 影响范围广,可攻击所有访问该页面的用户 常见于留言板、评论区、用户资料等需要持久化存储用户输入的场景 1.2 与反射型XSS的区别 | 特征 | 存储型XSS | 反射型XSS | |------|----------|----------| | 存储位置 | 服务器端 | 不存储 | | 触发方式 | 访问存储页面 | 点击恶意链接 | | 影响范围 | 所有访问用户 | 仅点击链接的用户 | | 持续时间 | 持久性 | 一次性 | 2. 存储型XSS的危害 存储型XSS可造成以下安全威胁: Cookie窃取 :攻击者可获取用户会话凭证 页面劫持 :篡改页面内容或重定向到恶意网站 键盘记录 :记录用户输入敏感信息 传播恶意软件 :利用浏览器漏洞传播恶意程序 钓鱼攻击 :伪造登录表单窃取凭证 2.1 实际漏洞案例 | CVE编号 | 受影响系统 | 攻击方式 | |---------|------------|----------| | CVE-2018-19178 | JEESNS 1.3 | 通过HTML EMBED元素攻击 | | CVE-2018-19170 | JPress v1.0-rc.5 | 通过设置模块输入字段攻击 | | CVE-2018-19089 | Tianti 2.3 | 通过用户角色名称参数攻击 | | CVE-2018-17369 | EasyCMS 1.3 | 通过文章标题、内容等字段攻击 | 3. 漏洞代码分析 3.1 缺陷代码示例 漏洞点分析 : 从数据库获取数据后未进行充分验证 仅过滤 <script> 标签,其他HTML标签和属性仍可造成XSS 直接输出未编码的内容到HTML响应中 3.2 攻击场景 攻击者可提交包含恶意脚本的用户名,如: 该载荷将: 被存储到数据库 每次用户访问页面时执行 绕过简单的 <script> 过滤 4. 修复方案 4.1 输入验证 对用户提交的数据进行严格验证: 4.2 输出编码 使用ESAPI进行上下文相关的输出编码: ESAPI提供的编码方法: encodeForHTML() - HTML内容编码 encodeForHTMLAttribute() - HTML属性编码 encodeForJavaScript() - JavaScript上下文编码 encodeForCSS() - CSS上下文编码 encodeForURL() - URL参数编码 4.3 综合修复代码 4.4 设置HttpOnly Cookie 5. 防御最佳实践 输入验证 : 采用白名单而非黑名单策略 根据业务需求定义严格的输入规则 对特殊字符进行过滤或转义 输出编码 : 根据输出上下文选择合适的编码方式 使用成熟的编码库而非自行实现 注意不同上下文的编码规则差异 安全配置 : 设置HttpOnly和Secure标志的Cookie 实施Content Security Policy (CSP) 使用X-XSS-Protection响应头 安全开发 : 将安全要求纳入开发规范 进行安全代码审查 使用静态分析工具检测漏洞 6. 检测与验证 6.1 手动测试方法 在输入字段尝试提交XSS测试向量: 查看页面源代码确认是否被编码 访问其他页面验证攻击是否持续 6.2 自动化检测 使用工具如: OWASP ZAP Burp Suite 360代码卫士 Fortify Checkmarx 7. 相关CWE条目 CWE-79 : Web页面生成期间输入中和不当(跨站脚本) CWE-80 : Web页面中脚本相关HTML标签中和不当(基本XSS) CWE-81 : 错误消息Web页面中脚本中和不当 CWE-82 : Web页面IMG标签属性中脚本中和不当 CWE-83 : Web页面属性中脚本中和不当 8. 总结 存储型XSS是Web应用中最危险的安全漏洞之一,其持久性特点使得攻击影响范围广、持续时间长。有效防御需要采取多层次的安全措施: 不信任任何用户输入 - 包括来自数据库的"已存储"数据 实施深度防御 - 结合输入验证、输出编码和安全配置 使用安全工具 - 借助自动化工具进行持续检测 提高安全意识 - 将安全融入开发生命周期 通过遵循这些原则和实践,可以显著降低存储型XSS漏洞的风险,保护用户和系统安全。