DVWA从入门到精通(十二):XSS (DOM)(DOM型XSS)
2026/7/6 4:06:10 网站建设 项目流程

摘要:本文是《DVWA从入门到精通》系列的第十二篇,带你全面掌握XSS (DOM)(DOM型XSS)模块的攻防全流程。从DOM型XSS的核心原理出发,逐步讲解Low、Medium、High三个级别的攻击手法与源码分析,并深入探讨Impossible级别的终极防御方案。文章包含DOM型XSS与反射型XSS、存储型XSS的深度对比、document.write危险函数的利用、stripos()大小写不敏感过滤的绕过、<img>标签闭合注入、#号绕过服务端白名单、URL编码防御等核心技术,让你真正做到“知其然更知其所以然”。


一、什么是DOM型XSS?

1.1 DOM型XSS的核心原理

DOM型XSS(DOM-based Cross-Site Scripting)是跨站脚本攻击的一种特殊类型,它与传统的存储型XSS、反射型XSS最大的不同在于:它完全发生在客户端(浏览器),服务端不参与恶意代码的解析和返回

DOM型XSS是基于文档对象模型(Document Object Model)的一种漏洞。当页面到达浏览器时,浏览器会为页面创建一个顶级的Document对象,接着生成各个子文档对象,每个页面元素对应一个文档对象,每个文档对象包含属性、方法和事件。

用一个生活化的例子来理解

想象你在一个自助餐厅里,墙上挂着一块白板,上面写着“今日推荐菜品”。顾客可以随时擦掉白板上的内容,写上自己喜欢的菜品推荐——不需要经过餐厅后厨(服务器)的同意

有一天,一个坏人在白板上写了一行字:“所有看到这条消息的人,请把钱包交到前台。”其他顾客走进餐厅,看到白板上的内容,莫名其妙地照做了——因为白板上的内容是直接在餐厅里展示的,没有经过任何审查。

DOM型XSS就是如此:攻击者通过修改浏览器本地的DOM结构(比如URL中的参数),让页面中的JavaScript代码直接将这些恶意内容写入页面,整个过程不经过服务器

1.2 DOM型XSS vs 反射型XSS vs 存储型XSS

为了帮助理解,以下是三种XSS的详细对比:

对比维度反射型XSS存储型XSSDOM型XSS
数据流向用户输入 → 服务器 → 响应用户输入 → 数据库 → 所有用户用户输入 → 浏览器DOM操作
恶意代码位置URL参数中服务器数据库中浏览器的DOM中
是否经过服务器✅ 是✅ 是(纯前端)
持久性非持久(单次)持久(长期有效)非持久(单次)
触发方式需要点击恶意链接访问页面即触发需要点击恶意链接
防御重点服务端过滤与转义服务端过滤与转义客户端JavaScript安全编码

DOM型XSS其实是一种特殊类型的反射型XSS,它是基于DOM文档对象模型的一种漏洞,其触发不需要经过服务器端,也就是说,服务端的防御并不起作用

1.3 DOM型XSS的常见触发点

DOM型XSS的触发点主要在前端JavaScript代码中,以下是常见的危险数据源:

数据源示例风险说明
URL参数document.URLwindow.location.href直接从URL提取参数
URL锚点(hash)window.location.hash#号后的内容不发送到服务器
用户输入文本框、下拉菜单等用户可控制的内容
本地存储localStoragesessionStorage存储在浏览器中的数据

1.4 DOM型XSS的危害

DOM型XSS的危害与反射型XSS类似,但由于其不经过服务器的特点,使得服务端的安全措施(如WAF、输入过滤)完全失效

危害说明
窃取Cookie获取用户的登录凭证,劫持用户会话
劫持用户行为以用户身份执行恶意操作
配合CSRF攻击结合CSRF进行针对性攻击
键盘记录记录用户在页面上的所有输入
内网探测利用受害者浏览器探测内网信息

二、准备工作

2.1 靶场环境

确保DVWA已部署并正常运行:

  • 访问地址:http://你的服务器IP/dvwa/login.php

  • 使用admin/password登录

2.2 必备工具

工具用途
浏览器(Chrome/Firefox)访问靶场,F12开发者工具查看页面源码和DOM结构
Burp Suite抓包分析请求和响应

2.3 基础知识储备

  • 理解JavaScript的基本语法

  • 了解DOM(文档对象模型)的基本概念

  • 了解document.write()document.URLwindow.location等API

  • 了解URL的组成部分(特别是#锚点部分)


三、Low级别:毫无防护的“裸奔”状态

3.1 安全级别设置

将DVWA Security设置为Low级别,然后进入XSS (DOM)模块。

3.2 界面观察

XSS (DOM)模块的界面包含一个下拉选择框和一个“Select”按钮。下拉框中默认显示“English”,用户可以选择不同的语言选项。

观察URL的变化:选择不同的语言后,URL中会出现default参数,例如:

http://靶机IP/dvwa/vulnerabilities/xss_d/?default=English

3.3 源码分析

关键点:DOM型XSS的漏洞代码在前端JavaScript中,而不是在服务器端PHP代码中。

查看Low级别的服务器端PHP代码

<?php # No protections, anything goes ?>

服务器端完全没有做任何防护

查看前端页面源码(在浏览器中按F12查看):

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

这段代码存在致命的DOM型XSS漏洞

缺陷说明
直接从URL提取数据document.location.href获取当前URL,未经验证
直接写入DOMdocument.write()直接将用户可控的数据写入页面
无任何过滤或转义用户输入被原样写入HTML
服务器端无防护整个攻击在客户端完成,服务器不参与

3.4 攻击方法:直接注入恶意脚本

由于Low级别没有任何防护,攻击者可以直接在URL中注入JavaScript代码。

第一步:构造恶意URL

在浏览器地址栏中输入:

第二步:观察结果

页面立即弹出弹窗,显示“/XSS/”——证明DOM型XSS攻击成功。

第三步:分析攻击原理

  • 用户访问恶意URL

  • 页面中的JavaScript代码执行document.location.href.indexOf("default=")提取参数值

  • lang变量的值为<script>alert(/XSS/)</script>

  • document.write()将这个值写入页面

  • 浏览器将<script>解析为HTML标签并执行其中的JavaScript代码

第四步:查看页面源码

在页面中右键选择“查看页面源代码”,看不到注入的<script>标签——因为恶意代码是通过JavaScript动态写入DOM的,不在原始的HTML源码中。

但是,在浏览器开发者工具的Elements面板中,可以看到DOM中确实插入了恶意代码:

<option value='<script>alert(/XSS/)</script>'><script>alert(/XSS/)</script></option>

3.5 更隐蔽的攻击:利用#

由于document.location.href会获取完整的URL,攻击者也可以利用URL中的#号(锚点)部分来注入Payload。#号后面的内容不会发送到服务器,因此可以绕过部分服务端检测。

http://靶机IP/dvwa/vulnerabilities/xss_d/#<script>alert(/XSS/)</script>

但由于DVWA的Low级别代码是查找default=参数,所以这种方式在此场景下不会触发。在后续的High级别中,#号将成为关键的绕过手段。

3.6 Low级别总结

缺陷说明
直接从URL提取数据未经验证的用户输入
document.write()写入DOM危险操作,直接插入HTML
无任何过滤或转义恶意代码被原样执行
服务器端无防护服务端防御措施完全无效

四、Medium级别:stripos()的“黑名单过滤”

4.1 安全级别设置

将DVWA Security切换为Medium级别。

4.2 观察变化

在Medium级别下,尝试Low级别的Payload<script>alert(/XSS/)</script>,发现弹窗没有出现——页面被重定向到了?default=English

4.3 源码分析

服务器端PHP代码(Medium级别):

<?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; } } ?>

Medium级别的变化

  • 增加了服务端过滤:使用stripos()函数检查default参数中是否包含<script

  • 不区分大小写stripos()是大小写不敏感的,<Script<SCRIPT等都会被检测到

  • 重定向防护:如果检测到<script,将用户重定向到?default=English

4.4stripos()的局限

Medium级别的过滤相比Low级别有了进步,但仍然存在缺陷

问题说明
仅过滤<script只检查是否包含<script字符串
黑名单思维其他HTML标签(如<img>)不受影响
前端代码未修改前端的document.write()仍然存在漏洞

4.5 攻击方法一:使用<img>标签闭合绕过

由于服务端只过滤了<script,攻击者可以使用其他HTML标签来执行JavaScript代码。

第一步:构造Payload

关键在于闭合前面的<select><option>标签,使注入的标签能够独立执行:

</option></select><img src=1 onerror=alert(/XSS/)>

第二步:构造完整URL

http://靶机IP/dvwa/vulnerabilities/xss_d/?default=</option></select><img src=1 onerror=alert(/XSS/)>

第三步:分析攻击原理

  • 服务端检查default参数,不包含<script,通过验证

  • 前端JavaScript从URL提取参数值:</option></select><img src=1 onerror=alert(/XSS/)>

  • document.write()将内容写入页面

  • </option></select>关闭了原有的下拉菜单标签

  • <img src=1 onerror=alert(/XSS/)>作为一个独立的标签被解析

  • 图片加载失败,onerror事件触发,执行JavaScript代码

4.6 攻击方法二:使用<svg>标签

?default=</option></select><svg onload=alert(/XSS/)>

4.7 Medium级别总结

改进局限性
服务端过滤<script只过滤单一标签
stripos()大小写不敏感其他标签(<img><svg>)不受影响
重定向防护前端document.write()漏洞仍然存在
有一定防护效果黑名单思维,总有遗漏

五、High级别:白名单的“服务端限制”与#号的“客户端绕过”

5.1 安全级别设置

将DVWA Security切换为High级别。

5.2 观察变化

在High级别下,尝试Medium级别的Payload(</option></select><img src=1 onerror=alert(/XSS/)>),发现被阻止了——页面被重定向到了?default=English

5.3 源码分析

服务器端PHP代码(High级别):

<?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; } } ?>

High级别的变化

  • 使用白名单机制:只允许FrenchEnglishGermanSpanish四个值

  • 其他值被重定向:任何不在白名单中的值都会被重定向到?default=English

  • 前端代码未修改:前端的document.write()仍然存在漏洞

5.4 白名单机制的强度

High级别的白名单机制在服务端层面非常严格——任何不在白名单中的字符串都会被拒绝。这使得通过default参数直接注入Payload变得不可能

5.5 攻击方法:利用#号绕过服务端白名单

核心原理:URL中#号(锚点)后面的内容不会发送到服务器,但可以被前端JavaScript读取

第一步:构造恶意URL

http://靶机IP/dvwa/vulnerabilities/xss_d/?default=English#<script>alert(/XSS/)</script>

第二步:分析攻击原理

  • 服务端收到请求,只看到default=English#号后面的内容不被发送)

  • 服务端检查白名单,English通过验证

  • 页面返回给浏览器,前端JavaScript执行

  • JavaScript从document.URL读取完整URL,包括#号后面的内容

  • document.write()#号后面的内容写入页面

  • 恶意代码被执行

5.6 High级别总结

防御机制作用绕过方法
服务端白名单(4个值)严格限制default参数的值#号后的内容不发送到服务器
重定向机制拒绝非白名单值前端document.URL读取完整URL
服务端防护强有效防御服务端注入客户端DOM漏洞仍然存在

六、Impossible级别:终极防御方案

6.1 安全级别设置

将DVWA Security切换为Impossible级别。

6.2 源码分析

服务器端PHP代码(Impossible级别):

<?php # Don't need to do anything, protection handled on the client side // 不需要在服务端做任何事情,防御在客户端处理 ?>

前端JavaScript代码(Impossible级别):

if (document.location.href.indexOf("default=") >= 0) { var lang = document.location.href.substring(document.location.href.indexOf("default=")+8); // 关键区别:没有 decodeURI(),直接写 lang document.write("<option value='" + lang + "'>" + lang + "</option>"); document.write("<option value='' disabled='disabled'>----</option>"); }

6.3 Impossible级别的防御机制

核心改动为移除危险解码逻辑,采用浏览器原生编码 + 前端不解码的客户端防御方案:

  • 关键差异:Low/High 使用decodeURI(lang)解码输出,Impossible 直接原样输出lang,无任何解码操作;

  • 浏览器原生行为:URL 锚点#后的单引号、尖括号、井号等特殊字符,会被浏览器自动转为 URL 百分号编码;

  • 渲染效果:

    原始恶意字符浏览器自动编码后页面渲染结果
    <%3C纯文本<,无法被识别为 HTML 起始标签,无法注入新标签
    >%3E纯文本>,无法闭合现有 HTML 标签,无法跳出<option>结构
    '%27纯文本单引号,无法闭合 value 属性的引号,无法篡改属性值
    #%23普通文本字符,无 URL 锚点语义,不会触发页面跳转 / 定位

防御原理: 攻击者构造含闭合符号与脚本的锚点 payload 时,浏览器先自动完成 URL 编码;前端 JS 截取编码后的字符串直接写入 DOM,不存在原生< > '等 HTML 控制字符,浏览器只会将内容解析为普通文本,不会识别、执行任何 JS 脚本。

6.4 为什么Impossible级别无法被绕过?

DOM XSS 成功的必要条件是拥有可用于闭合 HTML 结构的原生特殊字符,Impossible 从源头消除该条件:

攻击条件Impossible 防护逻辑是否可实现
闭合 value 单引号单引号被自动编码为 %27,无原生'
跳出<option>插入<script>< > 全部编码为 %3C、%3E,无法生成标签
#锚点绕过后端白名单虽然 #内容不传给后端,但字符全部编码,前端无法利用
注入 img/svg 等事件标签所有 HTML 特殊符号均为编码文本,不触发解析

防护本质:DOM XSS 漏洞点完全在前端 JS 处理逻辑,取消解码操作后,无论 Query 参数还是锚点内的恶意字符,都会以编码文本形式渲染,不存在可执行的 HTML 注入载体。

6.5 Impossible级别总结

防御层级实现手段核心作用
第一层(核心)删除 decodeURI (),不做 URL 解码阻止编码字符还原为 HTML 控制符号
第二层(浏览器原生)#锚点特殊字符自动 URL 编码攻击者输入的恶意符号默认转为编码串
第三层(后端兜底)服务端语言白名单校验普通 GET 参数携带恶意内容直接拦截

七、DOM型XSS与反射型XSS的本质区别

很多初学者容易混淆DOM型XSS和反射型XSS,这里做一次彻底的对比:

7.1 从“浏览器与服务器交互”的角度理解

反射型XSS的流程

用户点击恶意URL → 浏览器发送请求到服务器 → 服务器读取URL参数 → 服务器将恶意代码拼接到响应HTML中 → 浏览器接收响应 → 执行恶意代码

关键特征:恶意代码经过了服务器,服务器参与了恶意代码的“生产”。

DOM型XSS的流程

用户点击恶意URL → 浏览器发送请求到服务器 → 服务器返回正常页面(不含恶意代码) → 浏览器解析页面,执行JavaScript → JavaScript从URL读取参数 → JavaScript将恶意代码写入DOM → 恶意代码被执行

关键特征:恶意代码不经过服务器,服务器返回的页面本身是“干净”的,恶意代码是在客户端被JavaScript“制造”出来的。

7.2 防御策略的差异

防御对象反射型XSSDOM型XSS
防御位置服务端客户端(JavaScript代码中)
防御手段服务端过滤、htmlspecialchars()安全DOM操作
WAF有效性✅ 有效❌ 无效(不经过服务器)
服务端转义有效性✅ 有效❌ 无效(服务器不接触恶意代码)

核心启示:DOM型XSS的防御必须在客户端JavaScript代码中完成,服务端的任何防御措施都无法触及漏洞的根本。


八、防御DOM型XSS的最佳实践

通过DVWA四个级别的对比,我们可以总结出防御DOM型XSS的最佳实践

8.1 必须实施的防御措施

措施详细说明优先级
避免危险 DOM 操作禁止直接使用document.write()innerHTMLeval()等可解析 HTML / 执行代码的 API,从根源消除注入风险⭐⭐⭐⭐⭐
上下文感知输出编码数据写入 DOM 前,根据插入位置选择对应编码:・HTML 正文 / 标签内:使用 HTML 转义(< > " ')・URL 参数 / 属性值:使用encodeURIComponent()编码⭐⭐⭐⭐⭐
使用安全 DOM API优先使用textContent/innerText替代innerHTML,仅写入纯文本,不解析 HTML 标签;使用createElement()/setAttribute()动态创建节点,避免字符串拼接 HTML⭐⭐⭐⭐⭐
输入白名单校验仅允许符合预期格式的输入(如仅允许固定语言名称、数字、邮箱格式),非法输入直接拦截,拒绝处理⭐⭐⭐⭐

8.2 推荐的辅助措施

措施详细说明优先级
内容安全策略(CSP)配置严格的 CSP 规则,限制页面可加载 / 执行的脚本来源,禁止内联脚本、eval 执行,即使存在注入漏洞也无法执行恶意代码⭐⭐⭐⭐
禁止从 URL 直接读取原始数据不直接从document.URL/window.location.href截取参数,使用URLSearchParams规范读取 Query 参数,自动忽略#锚点内容⭐⭐⭐⭐
服务端辅助校验对 GET/POST 参数做服务端白名单 / 格式校验,作为纵深防御的兜底(注意:无法完全防御 DOM 型 XSS,仅能拦截常规恶意参数)⭐⭐⭐
禁用危险 JS 特性关闭eval()new Function()等动态代码执行能力,禁止使用javascript:伪协议跳转⭐⭐⭐

8.3 危险API清单

以下是DOM型XSS中最危险的JavaScript API,应谨慎使用:

危险 API核心风险说明安全替代方案
document.write()直接将字符串解析为 HTML 写入 DOM,可插入任意标签、脚本,是 DOM XSS 最高发风险 APItextContentcreateElement()appendChild()
element.innerHTML解析并执行传入的 HTML 字符串,可注入标签、事件属性、脚本textContentinnerText
eval()直接执行传入的字符串为 JavaScript 代码,任意代码执行风险完全禁止使用,无安全替代
setTimeout()/setInterval()若传入字符串参数,会被隐式执行 eval,存在代码执行风险仅传入函数类型参数,禁止传入字符串
location.href/location.assign()可被javascript:伪协议劫持,执行任意 JS 代码严格校验 URL 协议,仅允许http:///https://,禁止伪协议

8.4 常见误区

在实际开发中,以下做法不能有效防御DOM型XSS:

  • 仅在服务端进行过滤:DOM型XSS不经过服务器,服务端过滤完全无效

  • 仅依赖WAF:WAF无法检测到客户端的DOM操作

  • 仅使用黑名单过滤:总有遗漏的标签或属性

  • 依赖浏览器内置XSS防护X-XSS-Protection对DOM型XSS效果有限


九、DOM型XSS的实战检测思路

在实际的渗透测试中,如何快速发现DOM型XSS漏洞?

9.1 检测步骤

  • 分析页面JavaScript:查看页面源码,寻找document.write()innerHTMLeval()等危险API

  • 追踪数据来源:确认这些API操作的数据是否来自用户可控的来源(URL参数、location.hash、表单输入等)

  • 注入测试Payload:在URL参数或#号后注入<script>alert(1)</script>

  • 观察执行结果:弹窗是否出现?

  • 分析防护机制:服务端是否有过滤?前端是否有编码?

  • 尝试绕过:根据防护机制选择合适的绕过方法

9.2 常见DOM数据源

数据源示例代码核心风险
document.URLdocument.URL.indexOf("default=")获取浏览器完整 URL,包含#锚点内容,完全可控
window.location.hrefwindow.location.href.substring(...)document.URL,可读写完整 URL,DOM XSS 最常用数据源
window.location.hashwindow.location.hash.substring(1)仅获取#锚点后的内容,完全不经过服务端,白名单绕过核心利用点
document.referrerdocument.referrer获取来源页面 URL,可被攻击者伪造,存储型 XSS 可利用
localStorage/sessionStoragelocalStorage.getItem("userData")本地存储数据,若存在恶意内容,读取后写入 DOM 会触发 XSS

9.3 常见绕过手法汇总

防护类型绕过方法适用 DVWA 级别生效前提
服务端仅过滤<script>标签替换为<img src=x onerror=alert(1)><svg onload=alert(1)>等其他可执行事件的标签Medium服务端仅做了简单的 script 标签过滤,无其他防护
服务端语言白名单校验使用#锚点绕过,#后内容不发送到服务端,前端 JS 可读取完整 URLHigh后端仅校验 Query 参数,未处理锚点内容,前端存在decodeURI()解码逻辑
前端无任何编码 / 过滤直接注入闭合标签的 Payload:?default='><script>alert(1)</script>Low前端直接拼接 URL 参数写入 DOM,无任何转义 / 过滤
服务端大小写过滤无有效绕过(DVWA Medium 级使用stripos()不区分大小写匹配,大小写混合无法绕过)服务端使用不区分大小写的匹配过滤,无其他漏洞
前端移除decodeURI()解码无有效绕过(Impossible 级核心防御,浏览器自动编码特殊字符,前端不解码,无法还原 HTML 控制符号)Impossible前端无解码逻辑,所有特殊字符均以 URL 编码形式渲染,无法闭合标签

十、总结

本文作为《DVWA从入门到精通》系列第十二篇,系统学习并复现了DOM型XSS漏洞,明确了其区别于反射型、存储型XSS的核心特征:攻击完全发生在客户端,利用前端危险DOM API读取可控URL数据并动态渲染,全程不经过服务器,导致传统服务端过滤与WAF防护失效;我们依次完成全级别实战测试,Low级别无任何过滤可直接注入脚本弹窗,Medium级别仅黑名单拦截script标签可通过闭合标签替换事件标签绕过,High级别依靠服务端语言白名单防护但可利用URL锚点#特性绕过,Impossible级别通过移除前端decodeURI()解码逻辑,配合浏览器对锚点特殊字符的自动URL编码,使恶意载荷无法还原为可解析的HTML特殊符号,彻底杜绝标签闭合与脚本执行风险;最后本文总结了DOM XSS攻防要点与生产防御最佳实践,明确通过规避document.write()、innerHTML等危险API、使用textContent安全写入、严格输出编码、配置CSP策略及多层纵深防御,可从根源上有效防护DOM型XSS漏洞。


重要声明:本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。

如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询