DOM Based Cross Site Scripting(XSS)DOM 型跨站脚本漏洞:前端 URL 解析缺陷与 DVWA 分级绕过实战
DOM Based Cross Site ScriptingXSSDOM 型跨站脚本漏洞前端 URL 解析缺陷与 DVWA 分级绕过实战前言1. DOM Based XSS 核心理论基础1.1 什么是 DOM 型 XSS1.2 DOM XSS 的本质1.3 DOM XSS 成立的必要条件1.4 DOM XSS、Reflected XSS 与 Stored XSS 的区别1.5 DOM XSS 可能造成什么危害2. DVWA DOM XSSLow2.1 页面黑盒探测2.2 黑盒漏洞利用实操2.3 源码审计漏洞核心不在 Low.php而在前端 DOM 拼接3. DVWA DOM XSSMedium3.1 页面黑盒探测3.2 黑名单绕过思路3.3 源码审计只拦截 script 关键字的缺陷4. DVWA DOM XSSHigh4.1 页面黑盒探测4.2 白名单防护为什么更可靠4.3 源码审计服务端语言白名单4.4 High级别绕过URL锚点#片段5. DVWA DOM XSSImpossible5.1 页面黑盒验证5.2 源码审计不解码 Query String阻断 DOM 注入6. DOM XSS 防护的正确姿势6.1 避免使用危险 DOM Sink6.2 Source 与 Sink 之间必须做安全处理6.3 不要依赖黑名单过滤6.4 白名单与 CSP 作为纵深防御7. DVWA DOM XSS 四个等级对比8. 总结免责声明前言今天继续 DVWA Web 漏洞靶场系列来学习 XSS 漏洞中最容易被初学者忽略、也最容易被“后端没问题”这种想法误判的一类漏洞——DOM Based Cross Site ScriptingDOM 型跨站脚本简称 DOM XSS。如果说反射型 XSS 是服务端把用户输入反射到页面存储型 XSS 是服务端把恶意内容保存后再展示那么 DOM XSS 的危险点更靠近浏览器本身。它甚至不一定需要服务端把恶意内容写进 HTML 响应。攻击者构造的内容到达浏览器后前端 JavaScript 再从 URL、location.hash、document.referrer、window.name等位置读取数据并使用不安全的 DOM API 拼接回页面。整个危险过程可能完全发生在浏览器端。如果说前两类 XSS 的典型链路是用户输入 → 服务端处理错误 → 浏览器执行那么 DOM XSS 的典型链路则是用户输入 → 前端 JavaScript 处理错误 → DOM 被污染 → 浏览器执行这也是 DOM XSS 最容易被忽略的原因服务端日志里可能看不到完整的危险内容后端源码也可能几乎没有漏洞逻辑但浏览器里的 JavaScript 仍然能把 URL 参数重新拼成可执行 HTML。DVWA 的 DOM XSS 靶场通过一个“语言选择”下拉框展示了 URL 中的default参数如何被前端脚本读取并动态写入页面。Low、Medium、High、Impossible 四个等级则分别展示了无防护、黑名单过滤、服务端白名单和客户端不解码的差异。本文前面的测试依旧严格采用黑盒思路先观察页面、先改参数、先看浏览器现象源码为什么会这样放到每个等级的最后再审计。1. DOM Based XSS 核心理论基础1.1 什么是 DOM 型 XSSDOM 型跨站脚本漏洞DOM Based Cross Site Scripting是一类主要由前端 JavaScript 操作 DOM 时引入的 XSS 问题。它的典型特征是攻击者控制 URL 参数、Hash、Referer 等浏览器侧数据前端 JavaScript 从这些位置读取内容JavaScript 使用document.write()、innerHTML、outerHTML等危险 API 将内容写回页面浏览器将攻击者输入当成 HTML 结构解析恶意脚本在当前站点上下文中执行。例如一个页面从 URL 中读取语言参数?defaultEnglish前端脚本本来只想让下拉框显示当前语言English但如果 JavaScript 没有把参数当作普通文本处理而是直接拼接进 HTML 字符串攻击者就可能通过构造特殊default参数让浏览器解析出额外的 HTML 标签。1.2 DOM XSS 的本质DOM XSS 的漏洞链路可以概括为攻击者可控 URL 参数 ↓ 浏览器加载目标页面 ↓ 前端 JavaScript 读取 location.href / location.hash 等数据源 ↓ 输入未编码就进入 document.write / innerHTML 等危险 DOM API ↓ 浏览器重新解析 DOM 结构 ↓ 恶意 HTML 或 JavaScript 执行最关键的问题不是“URL 里有没有script”而是前端代码把 URL 中的不可信字符串当成了可以直接拼接进 HTML 的内容。开发者本来只是想动态生成一个下拉框选项但如果使用字符串拼接配合document.write()浏览器最终接收到的就不再只是一个语言名称而可能是一段攻击者控制的 HTML。1.3 DOM XSS 成立的必要条件DOM XSS 一般需要满足以下几个条件。条件一存在客户端可控的数据源Source常见 Source 包括document.location.hreflocation.searchlocation.hashdocument.referrerwindow.namepostMessage接收的数据浏览器本地存储中的数据。条件二前端脚本将数据写入危险位置Sink常见危险 Sink 包括document.write()element.innerHTMLuserInputelement.outerHTMLuserInputeval(userInput)setTimeout(userInput,0)条件三Source 到 Sink 的数据流没有进行安全处理也就是说URL 中的参数被前端读取后没有使用安全 DOM API 作为普通文本写入而是直接参与 HTML 字符串拼接。1.4 DOM XSS、Reflected XSS 与 Stored XSS 的区别类型危险数据主要在哪里处理恶意内容是否持久化典型特点Reflected XSS服务端反射响应通常不保存服务端将参数回显到当前页面Stored XSS服务端保存后再输出保存后续访问者可能持续触发DOM XSS浏览器前端 JavaScript不一定保存前端读取 URL / Hash 后污染 DOM反射型 XSS 的常见流程恶意参数 → 服务端拼接响应 → 浏览器执行存储型 XSS 的常见流程恶意内容 → 服务端保存 → 后续页面回显 → 浏览器执行DOM XSS 的常见流程恶意参数 → 浏览器 URL → 前端脚本读取 → DOM 拼接 → 浏览器执行所以 DOM XSS 的审计重点不只是 PHP、Java、Python 等后端代码还必须看浏览器侧 JavaScript 的数据流。1.5 DOM XSS 可能造成什么危害DOM XSS 的最终执行环境依旧是受害者浏览器因此危害与其他 XSS 类型类似篡改页面内容伪造登录、支付或客服界面诱导用户输入敏感信息读取当前页面中可访问的 DOM 数据诱导用户执行站内操作影响使用同一页面的普通用户或管理员配合钓鱼、点击劫持等手段扩大影响。不同的是DOM XSS 很容易被开发者忽略。后端可能只看到一次看似正常的页面请求真正把恶意内容变成 HTML 的逻辑却发生在用户自己的浏览器里。2. DVWA DOM XSSLow2.1 页面黑盒探测进入 DVWAVulnerabilities → XSS (DOM)页面提供一个语言下拉框正常情况下可以选择English French Spanish German先选择English点击提交后观察浏览器地址栏。页面 URL 会出现类似参数?defaultEnglish此时下拉框顶部会显示当前传入的语言内容。接下来直接修改地址栏中的default参数?defaultTestLanguage如果刷新后下拉框中出现了一个新的TestLanguage选项而页面并没有提示“语言不存在”或“参数非法”说明前端正在读取 URL 参数并把它动态写进页面。这里已经出现了一个典型的 DOM XSS 信号URL 参数改变后页面结构跟着发生变化但并没有看到传统的服务端回显区域。2.2 黑盒漏洞利用实操DOM XSS 的关键在于确认default参数是否能从“普通文本”突破成“HTML 结构”。DVWA 这个页面的参数最终会出现在下拉框的option位置因此可以先构造一个闭合原有option标签、再插入新标签的无害测试内容?defaultEnglish%3C%2Foption%3E%3Cimg%20src%3D1%20onerror%3Dalert(document.domain)%3E为了方便理解URL 解码后的核心内容是English/optionimgsrc1onerroralert(document.domain)访问该 URL 后如果浏览器弹出当前站点域名说明 URL 参数已经被前端 JavaScript 拼接进页面并被浏览器当成 HTML 解析。这里没有提交表单没有写入数据库也没有在当前页面看到明显的 PHP 回显。攻击链完全发生在浏览器端攻击者构造 default 参数 ↓ 浏览器加载页面 ↓ 页面脚本读取当前 URL ↓ 脚本将参数拼接进下拉框 HTML ↓ 浏览器解析新增 HTML ↓ 事件属性触发 JavaScript这就是典型的 DOM Based XSS。2.3 源码审计漏洞核心不在 Low.php而在前端 DOM 拼接Low 级别的后端源码只有?php# No protections, anything goes?从 PHP 角度看Low.php 几乎没有任何处理逻辑。这正是 DOM XSS 容易让人误判的地方如果只盯着后端漏洞源码会发现“这里什么都没有”。真正的危险代码在页面主文件生成的前端 JavaScript 中if(document.location.href.indexOf(default)0){varlangdocument.location.href.substring(document.location.href.indexOf(default)8);document.write(option valuelangdecodeURI(lang)/option);}代码逻辑拆解document.location.href前端直接获取当前浏览器完整 URLURL 中的default参数完全由访问者控制。substring(... 8)代码从default后面开始截取内容并将剩余字符串保存到变量lang。例如?defaultEnglish最终langEnglishdocument.write()漏洞关键语句document.write(option valuelangdecodeURI(lang)/option);程序将lang直接拼接到 HTML 字符串中再使用document.write()写入页面。当输入是普通语言名称时最终结构类似optionvalueEnglishEnglish/option但当输入中包含可以闭合原标签、再插入新标签的内容时浏览器就会把它解析为新的 DOM 结构。漏洞根源概括用户可控的 URL 参数未经 HTML 编码直接进入document.write()这个危险 DOM Sink。虽然服务端没有直接输出 Payload但前端脚本重新把它拼成了 HTMLDOM XSS 成功触发。标准修复方式不要使用document.write()拼接不可信 HTML。应使用安全 DOM APIconstoptiondocument.createElement(option);option.valuelang;option.textContentlang;select.appendChild(option);textContent会把用户输入当成纯文本而不是 HTML 标签解析。3. DVWA DOM XSSMedium3.1 页面黑盒探测将 DVWA Security 切换为 Medium 后先重复 Low 级别的测试。直接在地址栏中传入经典脚本标签?default%3Cscript%3Ealert(document.domain)%3C%2Fscript%3E如果页面没有执行而是跳转回?defaultEnglish说明应用程序已经对包含script的输入增加了拦截。但这里必须明确“过滤了script”不等于“修复了 DOM XSS”。因为 DOM XSS 不只依赖script标签浏览器中还有大量可以进入脚本执行上下文的 HTML 结构。3.2 黑名单绕过思路Medium 级别的现象很明显包含script的输入会被拦截并跳回默认语言。但我们的 Low 级别 Payload?defaultEnglish%3C%2Foption%3E%3Cimg%20src%3D1%20onerror%3Dalert(document.domain)%3E并不包含script字符串。访问后如果浏览器仍然弹出当前域名说明当前防护只关注了script标签没有阻止 URL 参数继续进入document.write()。这就是黑名单防御的典型问题。开发者只要盯着script攻击者就可以放弃使用script转而寻找其他可被浏览器解析的标签和事件属性。从黑盒角度看Medium 的真实问题并不是“大小写写得不对”而是页面只过滤了一段关键字却没有阻断 URL 参数到危险 DOM API 的数据流。3.3 源码审计只拦截 script 关键字的缺陷Medium 级别后端 PHP 源码?php// Is there any input?if(array_key_exists(default,$_GET)!is_null($_GET[default])){$default$_GET[default];# Do not allow script tagsif(stripos($default,script)!false){header(location: ?defaultEnglish);exit;}}?代码逻辑拆解页面先检查 GET 请求中是否存在default参数然后将其保存到$default$_GET[default];接着调用stripos($default,script)stripos()是不区分大小写的字符串查找函数。只要输入中包含script函数就会返回匹配位置程序随即执行header(location: ?defaultEnglish);exit;也就是说页面不是对用户输入进行安全编码而是发现script后直接重定向回默认语言。防护存在的致命缺陷它只拦截script关键字if(stripos($default,script)!false)但真正的 DOM 漏洞点依旧存在于前端document.write(option valuelangdecodeURI(lang)/option);只要 Payload 不含script就能避开后端检查并继续到达浏览器的危险 Sink。正确修复方案不要依赖关键字黑名单。真正需要修复的是前端 DOM 拼接逻辑option.textContentlang;或者对写入 HTML 的内容做正确的上下文编码。4. DVWA DOM XSSHigh4.1 页面黑盒探测High 级别不再只是过滤某一个标签而是对default参数允许的语言范围进行限制。先正常访问?defaultEnglish页面可以正常显示 English。再尝试?defaultFrench?defaultGerman?defaultSpanish这些预期语言同样可以正常显示。但如果将default改成任意非语言值例如?defaultTestLanguage页面会被跳转回?defaultEnglish此时再提交 Low / Medium 中使用的 DOM XSS Payload也会在页面加载前被重定向到 English无法到达浏览器中的 DOM 拼接位置。4.2 白名单防护为什么更可靠High 级别没有继续扩大“危险标签黑名单”而是换了一个方向只允许已知安全的业务值对于语言选择功能来说业务真正需要接受的值本来就只有English French German Spanish既然合法输入范围非常有限就没有必要接收任意字符串再去猜测里面有没有危险标签。白名单逻辑是输入属于允许集合 → 正常处理 输入不属于允许集合 → 直接回到默认值相比黑名单输入中不能出现某些危险关键字白名单更贴合业务逻辑也更容易控制攻击面。4.3 源码审计服务端语言白名单High 级别后端 PHP 源码?php// Is there any input?if(array_key_exists(default,$_GET)!is_null($_GET[default])){# White list the allowable languagesswitch($_GET[default]){caseFrench:caseEnglish:caseGerman:caseSpanish:# okbreak;default:header(location: ?defaultEnglish);exit;}}?代码逻辑拆解High 级别使用switch对default参数进行白名单校验caseFrench:caseEnglish:caseGerman:caseSpanish:只有这四个固定字符串可以继续进入页面。其他任何值都会执行header(location: ?defaultEnglish);exit;也就是说攻击者构造的 DOM XSS 参数在服务端阶段就被拦截不会进入后续页面也就无法被前端 JavaScript 拼接到 DOM 中。安全逻辑总结High 级别的安全性来自业务白名单而不是对所有危险 XSS 语法逐个猜测。对于“语言选择”这种固定选项功能白名单是合理而有效的防护方式。但也要注意白名单拦住了当前default参数并不代表前端document.write()本身就变安全了。如果未来该前端代码复用到其他允许自由文本输入的业务场景危险 DOM Sink 依旧可能重新成为漏洞点。4.4 High级别绕过URL锚点#片段直接通过GET参数?default传入恶意载荷会被服务端白名单拦截无法利用。但DVWA的前端JS读取的是完整的document.location.href而非location.search。URL中#之后的锚点片段不会发送到后端服务器仅保存在浏览器本地。后端PHP只能接收到?defaultEnglish白名单校验直接放行不会触发重定向但前端JS会读取完整URL把#后面的恶意内容一并取出送入document.write()完成DOM‑XSS触发。构造Payload?vulnerabilities/dom_xss/?defaultEnglish#%3C%2Foption%3E%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E攻击流程HTTP请求携带参数defaultEnglish#后的payload不参与网络传输PHP白名单校验通过正常返回页面无重定向浏览器拿到页面JS读取完整location.href截取default后的全部内容包含#以及后面的恶意字符串恶意payload传入document.write()解析为HTML触发onerror执行JS。关键结论服务端对$_GET[default]的白名单防护本身完备漏洞根源是前端错误读取完整href服务端完全感知不到#后的本地片段服务端校验无法保护仅存于浏览器Hash片段的数据危险Sinkdocument.write()始终存在。5. DVWA DOM XSSImpossible5.1 页面黑盒验证切换到 Impossible 后继续访问 Low 级别使用的 URL 编码 Payload?defaultEnglish%3C%2Foption%3E%3Cimg%20src%3D1%20onerror%3Dalert(document.domain)%3E此时页面不会弹窗。参数中的%3C、%3E等 URL 编码字符不会再被还原成真正的、标签字符而是以编码文本的形式保留在页面中。这说明 Impossible 并没有继续增加更多后端过滤规则而是直接从客户端 URL 解码这一环下手阻断攻击者把编码内容转换成 HTML 标签。5.2 源码审计不解码 Query String阻断 DOM 注入Impossible 级别的漏洞源码文件本身只有?php# Dont need to do anything, protection handled on the client side?这句话已经说明了关键防护逻辑被放在客户端。页面主文件中会根据安全等级决定是否调用decodeURI()# For the impossible level, dont decode the querystring$decodeURIdecodeURI;if($vulnerabilityFileimpossible.php){$decodeURI;}页面写入下拉框时使用document.write(option valuelang$decodeURI(lang)/option);在 Low、Medium、High 中页面会调用decodeURI(lang)URL 中的%3C %3E会被还原为 于是编码后的 HTML 标签重新获得浏览器解析能力。而 Impossible 级别将$decodeURI置为空最终不再对 Query String 进行解码。攻击者传入的%3Cimg...%3E不会恢复为img...浏览器就不会把它当成真实 HTML 标签解析。Impossible 级别安全总结不再将 URL 编码字符恢复为 HTML 标签字符恶意内容无法从编码文本转换为真实 DOM 结构前端的 URL 数据流在进入document.write()前失去可执行 HTML 语义对当前 DVWA 页面和当前 Payload 而言DOM XSS 执行链被阻断。不过从真实开发角度看更推荐的做法仍然是不要把不可信数据拼接进document.write()而是使用textContent、createElement()等安全 DOM API。6. DOM XSS 防护的正确姿势6.1 避免使用危险 DOM Sink以下写法风险很高document.write(userInput);element.innerHTMLuserInput;element.outerHTMLuserInput;如果业务只是展示文字应优先使用element.textContentuserInput;或者constoptiondocument.createElement(option);option.valueuserInput;option.textContentuserInput;select.appendChild(option);textContent和createElement()不会把输入当成 HTML 标签解析。6.2 Source 与 Sink 之间必须做安全处理DOM XSS 审计时必须同时盯住两类位置Source数据从哪里来Sink数据最终写到哪里去例如location.href ↓ substring() ↓ lang ↓ document.write()只要可控数据从 Source 一路流入危险 Sink中间没有安全处理就可能形成 DOM XSS。6.3 不要依赖黑名单过滤以下思路都不推荐作为 DOM XSS 的根本修复方案if(userInput.indexOf(script)!-1){// 拦截}userInputuserInput.replace(/script/gi,);原因和反射型、存储型 XSS 一样黑名单无法覆盖浏览器所有可执行 HTML 语法。真正的修复方向应该是不让不可信输入进入 HTML 解析上下文而不是不断猜测攻击者会输入什么标签6.4 白名单与 CSP 作为纵深防御对于 DVWA 中的语言选择功能High 级别的业务白名单非常合适English French German Spanish如果业务输入本来就只允许固定选项应该优先限制为固定集合。另外Content Security PolicyCSP也可以限制脚本执行来源、减少部分 XSS 的影响。但必须注意CSP 和白名单属于纵深防御不能替代安全 DOM API 和正确的输出处理。7. DVWA DOM XSS 四个等级对比安全等级核心防护逻辑防护效果主要问题Low无服务端过滤前端直接document.write()完全可利用URL 参数直接进入危险 DOM SinkMedium拦截包含script的输入可拦截经典脚本标签只过滤关键字其他 HTML 结构仍可能绕过High服务端语言白名单当前参数可有效阻断前端document.write()仍是潜在危险代码Impossible客户端不再decodeURI()参数URL 编码标签不再恢复为 HTML更推荐彻底移除字符串拼接式 DOM 写入从 DVWA 的四个等级可以非常直观地看出防护演进无防护 ↓ 简单黑名单 ↓ 业务白名单 ↓ 客户端阻断解码链路真正可靠的安全方案不是把关键字过滤写得越来越复杂而是不要让 URL、Hash 等不可信数据以 HTML 字符串的形式进入document.write()、innerHTML等危险 DOM API。8. 总结DOM XSS 的漏洞原理并不复杂攻击者控制浏览器侧数据前端 JavaScript 又将这些数据直接拼接进 DOM浏览器最终把它们当成 HTML 或 JavaScript 解析执行。DVWA 的不同安全等级分别展示了Low没有任何防护URL 参数直接进入document.write()Medium只拦截script关键字无法覆盖其他 HTML 解析入口High通过固定语言白名单阻断非法default参数Impossible不再解码 Query String避免 URL 编码内容恢复成可执行 HTML 标签。最后记住 DOM XSS 最重要的一句话后端没有把 Payload 回显不代表没有 XSS只要前端 JavaScript 把 URL 中的不可信数据重新写进 DOM浏览器一样可能执行攻击者控制的内容。免责声明本文所有内容仅用于网络安全技术研究与教学演示所有测试均在本地授权搭建的 DVWA 靶场环境中进行旨在帮助读者理解 DOM XSS 漏洞原理与防御机制。请勿将本文涉及的技术与方法用于任何未经授权的真实系统测试。任何利用本文内容进行的非法攻击行为均与作者无关由使用者自行承担相应法律责任。网络安全学习与测试应严格遵守相关法律法规仅在获得明确授权的前提下开展安全测试。