DVWA从入门到精通(十二):XSS (DOM)(DOM型XSS)
摘要本文是《DVWA从入门到精通》系列的第十二篇带你全面掌握XSS (DOM)DOM型XSS模块的攻防全流程。从DOM型XSS的核心原理出发逐步讲解Low、Medium、High三个级别的攻击手法与源码分析并深入探讨Impossible级别的终极防御方案。文章包含DOM型XSS与反射型XSS、存储型XSS的深度对比、document.write危险函数的利用、stripos()大小写不敏感过滤的绕过、img标签闭合注入、#号绕过服务端白名单、URL编码防御等核心技术让你真正做到“知其然更知其所以然”。一、什么是DOM型XSS1.1 DOM型XSS的核心原理DOM型XSSDOM-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.URL、window.location.href直接从URL提取参数URL锚点hashwindow.location.hash#号后的内容不发送到服务器用户输入文本框、下拉菜单等用户可控制的内容本地存储localStorage、sessionStorage存储在浏览器中的数据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.URL、window.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/?defaultEnglish3.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 disableddisabled----/option); } document.write(option valueEnglishEnglish/option); document.write(option valueFrenchFrench/option); document.write(option valueSpanishSpanish/option); document.write(option valueGermanGerman/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变量的值为scriptalert(/XSS/)/scriptdocument.write()将这个值写入页面浏览器将script解析为HTML标签并执行其中的JavaScript代码第四步查看页面源码在页面中右键选择“查看页面源代码”看不到注入的script标签——因为恶意代码是通过JavaScript动态写入DOM的不在原始的HTML源码中。但是在浏览器开发者工具的Elements面板中可以看到DOM中确实插入了恶意代码option valuescriptalert(/XSS/)/scriptscriptalert(/XSS/)/script/option3.5 更隐蔽的攻击利用#号由于document.location.href会获取完整的URL攻击者也可以利用URL中的#号锚点部分来注入Payload。#号后面的内容不会发送到服务器因此可以绕过部分服务端检测。http://靶机IP/dvwa/vulnerabilities/xss_d/#scriptalert(/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级别的Payloadscriptalert(/XSS/)/script发现弹窗没有出现——页面被重定向到了?defaultEnglish。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: ?defaultEnglish); exit; } } ?Medium级别的变化增加了服务端过滤使用stripos()函数检查default参数中是否包含script不区分大小写stripos()是大小写不敏感的Script、SCRIPT等都会被检测到重定向防护如果检测到script将用户重定向到?defaultEnglish4.4stripos()的局限Medium级别的过滤相比Low级别有了进步但仍然存在缺陷问题说明仅过滤script只检查是否包含script字符串黑名单思维其他HTML标签如img不受影响前端代码未修改前端的document.write()仍然存在漏洞4.5 攻击方法一使用img标签闭合绕过由于服务端只过滤了script攻击者可以使用其他HTML标签来执行JavaScript代码。第一步构造Payload关键在于闭合前面的select和option标签使注入的标签能够独立执行/option/selectimg src1 onerroralert(/XSS/)第二步构造完整URLhttp://靶机IP/dvwa/vulnerabilities/xss_d/?default/option/selectimg src1 onerroralert(/XSS/)第三步分析攻击原理服务端检查default参数不包含script通过验证前端JavaScript从URL提取参数值/option/selectimg src1 onerroralert(/XSS/)document.write()将内容写入页面/option/select关闭了原有的下拉菜单标签img src1 onerroralert(/XSS/)作为一个独立的标签被解析图片加载失败onerror事件触发执行JavaScript代码4.6 攻击方法二使用svg标签?default/option/selectsvg onloadalert(/XSS/)4.7 Medium级别总结改进局限性服务端过滤script只过滤单一标签stripos()大小写不敏感其他标签img、svg不受影响重定向防护前端document.write()漏洞仍然存在有一定防护效果黑名单思维总有遗漏五、High级别白名单的“服务端限制”与#号的“客户端绕过”5.1 安全级别设置将DVWA Security切换为High级别。5.2 观察变化在High级别下尝试Medium级别的Payload/option/selectimg src1 onerroralert(/XSS/)发现被阻止了——页面被重定向到了?defaultEnglish。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: ?defaultEnglish); exit; } } ?High级别的变化使用白名单机制只允许French、English、German、Spanish四个值其他值被重定向任何不在白名单中的值都会被重定向到?defaultEnglish前端代码未修改前端的document.write()仍然存在漏洞5.4 白名单机制的强度High级别的白名单机制在服务端层面非常严格——任何不在白名单中的字符串都会被拒绝。这使得通过default参数直接注入Payload变得不可能。5.5 攻击方法利用#号绕过服务端白名单核心原理URL中#号锚点后面的内容不会发送到服务器但可以被前端JavaScript读取。第一步构造恶意URLhttp://靶机IP/dvwa/vulnerabilities/xss_d/?defaultEnglish#scriptalert(/XSS/)/script第二步分析攻击原理服务端收到请求只看到defaultEnglish#号后面的内容不被发送服务端检查白名单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 # Dont 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 disableddisabled----/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()、innerHTML、eval()等可解析 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 最高发风险 APItextContent、createElement()、appendChild()element.innerHTML解析并执行传入的 HTML 字符串可注入标签、事件属性、脚本textContent、innerTexteval()直接执行传入的字符串为 JavaScript 代码任意代码执行风险完全禁止使用无安全替代setTimeout()/setInterval()若传入字符串参数会被隐式执行 eval存在代码执行风险仅传入函数类型参数禁止传入字符串location.href/location.assign()可被javascript:伪协议劫持执行任意 JS 代码严格校验 URL 协议仅允许http:///https://禁止伪协议8.4 常见误区在实际开发中以下做法不能有效防御DOM型XSS❌仅在服务端进行过滤DOM型XSS不经过服务器服务端过滤完全无效❌仅依赖WAFWAF无法检测到客户端的DOM操作❌仅使用黑名单过滤总有遗漏的标签或属性❌依赖浏览器内置XSS防护X-XSS-Protection对DOM型XSS效果有限九、DOM型XSS的实战检测思路在实际的渗透测试中如何快速发现DOM型XSS漏洞9.1 检测步骤分析页面JavaScript查看页面源码寻找document.write()、innerHTML、eval()等危险API追踪数据来源确认这些API操作的数据是否来自用户可控的来源URL参数、location.hash、表单输入等注入测试Payload在URL参数或#号后注入scriptalert(1)/script观察执行结果弹窗是否出现分析防护机制服务端是否有过滤前端是否有编码尝试绕过根据防护机制选择合适的绕过方法9.2 常见DOM数据源数据源示例代码核心风险document.URLdocument.URL.indexOf(default)获取浏览器完整 URL包含#锚点内容完全可控window.location.hrefwindow.location.href.substring(...)同document.URL可读写完整 URLDOM XSS 最常用数据源window.location.hashwindow.location.hash.substring(1)仅获取#锚点后的内容完全不经过服务端白名单绕过核心利用点document.referrerdocument.referrer获取来源页面 URL可被攻击者伪造存储型 XSS 可利用localStorage/sessionStoragelocalStorage.getItem(userData)本地存储数据若存在恶意内容读取后写入 DOM 会触发 XSS9.3 常见绕过手法汇总防护类型绕过方法适用 DVWA 级别生效前提服务端仅过滤script标签替换为img srcx onerroralert(1)、svg onloadalert(1)等其他可执行事件的标签Medium服务端仅做了简单的 script 标签过滤无其他防护服务端语言白名单校验使用#锚点绕过#后内容不发送到服务端前端 JS 可读取完整 URLHigh后端仅校验 Query 参数未处理锚点内容前端存在decodeURI()解码逻辑前端无任何编码 / 过滤直接注入闭合标签的 Payload?defaultscriptalert(1)/scriptLow前端直接拼接 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漏洞。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。