DVWA 存储型XSS实战:从Low到Impossible的攻防博弈

📅 发布时间:2026/7/12 13:08:22 👁️ 浏览次数:
DVWA 存储型XSS实战:从Low到Impossible的攻防博弈
1. 初识存储型XSS为什么它比“一次性”攻击更可怕大家好我是老张在安全圈里摸爬滚打了十来年今天咱们不聊那些虚头巴脑的理论直接上手开干。咱们的目标是DVWA这个经典的靶场来一场从“入门”到“放弃”的存储型XSS攻防实战。你可能听说过XSS知道它能弹个窗但存储型XSS才是真正让开发者头疼的“牛皮癣”——一旦沾上后患无穷。简单来说XSS就是攻击者想方设法在别人的网页里插入并执行自己的恶意JavaScript代码。而存储型XSS顾名思义就是这个恶意代码会被存到网站的数据库里。比如一个论坛的留言板、一个博客的评论系统或者一个用户昵称的展示处。攻击者提交一段带毒的留言这段毒代码就被存进了服务器。之后任何一个普通用户只要打开这个留言页面他的浏览器就会自动执行那段恶意代码。想想看这可比那种需要诱骗用户点击特定链接的“反射型XSS”厉害多了它是一次投毒持续感染所有访客自动中招。在DVWA里存储型XSS的关卡设计得非常经典从Low到Impossible完美模拟了一个网站安全防护从“裸奔”到“铜墙铁壁”的进化过程。我们作为攻击方要做的就是利用每一关的弱点把我们的“炮弹”恶意脚本打进去。而作为防御方或者说学习防御我们更要明白每一关的防护为什么会被绕过以及如何才能真正堵上漏洞。这就像一场博弈攻防双方的技术都在对抗中不断升级。准备好了吗咱们先从最简单的“新手村”开始。2. Low级别门户大开脚本长驱直入2.1 环境准备与目标确认首先你得把DVWA环境搭起来。这个过程我就不赘述了网上教程一大堆无非是配个PHP环境把DVWA源码放进去修改一下数据库配置。记得把DVWA的安全级别调到Low这是我们所有实验的起点。进入DVWA左侧菜单的“XSS (Stored)”模块。映入眼帘的是一个非常简单的留言板界面有两个输入框一个是“Name”一个是“Message”下面有个“Sign Guestbook”的提交按钮。提交之后你的名字和留言内容就会显示在页面上方。这个场景太常见了几乎所有的用户交互功能都可能存在类似的问题。我们的攻击目标是什么最直接的证明就是让浏览器执行我们输入的JavaScript代码。最经典的测试语句就是弹出一个警告框。这不仅能证明漏洞存在还能通过alert(document.cookie)来演示如何窃取用户的会话Cookie这是XSS攻击中最危险的后果之一——拿到Cookie攻击者很可能就能直接登录你的账户。2.2 第一次攻击毫无过滤的“裸奔”现场在Low级别下我们不需要任何技巧。直接在“Message”框里输入以下代码scriptalert(Hey你的网站有漏洞)/script点击提交。瞬间一个弹窗就会出现在你面前。这说明什么说明我们输入的script标签被浏览器当成真正的HTML脚本标签给解析并执行了。服务器端没有对我们的输入做任何检查、过滤或转义原封不动地存进数据库又原封不动地输出到网页上。我们再来点更“危险”的演示在“Name”框里输入scriptalert(document.cookie)/script提交后弹窗里显示的就是你当前浏览器的Cookie信息。如果这是一个真实的网站并且这个Cookie是登录凭证那么攻击者就可以利用这个漏洞把这段窃取Cookie的代码存进去。之后每个查看留言的用户他们的Cookie都会被悄无声息地发送到攻击者指定的服务器上。防御方在这一关可以说是完全失守代码就像不设防的城市任由攻击者书写。这种级别的漏洞现在在正规网站上已经很少见了但它是一切学习的起点让我们明白最原始的漏洞形态是什么样的。3. Medium级别初设防线但漏洞百出3.1 防护升级与初步探测将DVWA的安全级别切换到Medium再次进入存储型XSS页面。直觉告诉我们事情没那么简单了。我们重复Low级别的操作在“Message”框里提交同样的弹窗脚本。你会发现这次脚本没有执行页面上老老实实地把我们输入的scriptalert(x)/script当作普通文本显示出来了。这时候有经验的安全测试者第一反应就是查看网页源代码。按F12打开开发者工具找到我们提交的留言部分。你很可能会看到类似这样的输出Name: 张三br / Message: lt;scriptgt;alert(#x27;x#x27;)lt;/scriptgt;br /注意看“Message”部分我们输入的尖括号和被转换成了lt;和gt;单引号也被转换成了#x27;。这是PHP中htmlspecialchars()函数的典型效果。这个函数会把HTML中的特殊字符如, , “, ‘, 转换成HTML实体这样浏览器就不会把它们解析为HTML标签或属性而是当作纯文本显示。至此“Message”输入框的XSS漏洞基本被堵死了。3.2 绕过策略寻找薄弱点与前端限制但是防御往往不是均匀的。我们再看“Name”字段的源代码发现它只是被简单地输出没有看到明显的转义。尝试在“Name”框输入scriptalert(document.cookie)/script。哎怎么输不完页面提示字符数超了。原来开发者在“Name”输入框加了一个前端的长度限制比如maxlength10。这是很多新手开发者会犯的错误在前端做校验却在后端不做或做不全校验。前端的一切限制对于攻击者来说都是形同虚设。我们有两种经典的绕过思路。第一种针对代码过滤。查看Medium级别的后端源码DVWA提供了源码查看功能你会发现它对“Name”的处理用了str_replace(‘script’, ‘’, $input)。这行代码的意思是把输入中的script这个字符串替换成空字符串。很天真对不对它只匹配了小写。那我们直接用大写的SCRIPT标签不就绕过了吗提交SCRIPTalert(document.cookie)/SCRIPT成功弹窗第二种针对这种简单的字符串替换还有一个好玩的技巧嵌套混淆。比如输入scrscriptiptalert(document.cookie)/script。当后端代码执行时它首先查找script并删除于是中间的script被删掉剩下的部分恰好又拼接成了一个完整的script标签这就是“拆字法”绕过。3.3 实战突破使用代理工具修改数据包不过我们刚才遇到了前端长度限制。怎么突破这就需要请出渗透测试中的瑞士军刀——Burp Suite这类代理工具了。它的原理很简单让你的浏览器流量都经过它转发你就能在中间截获、查看并修改任何发送给服务器的请求。具体操作步骤配置浏览器代理比如127.0.0.1:8080指向Burp Suite。在Burp Suite中开启代理拦截Intercept is on。回到DVWA页面在“Name”框随便输入个短名字比如test在“Message”框输入任意内容点击提交。此时请求会被Burp Suite截获。你会在Raw标签页看到一串HTTP请求数据其中就有txtNametestmtxMessagehello这样的参数。把txtName的值修改为我们精心构造的Payload比如scrscriptiptalert(document.cookie)/script。注意这里直接改完全不受浏览器前端10个字符的限制。点击“Forward”放行这个被修改过的数据包。回到浏览器页面你会发现攻击成功了弹窗如约而至。这一关告诉我们防御不能只做一半。后端虽然做了过滤但逻辑太简单大小写敏感、简单替换前端做了限制却忘了攻击者根本可以不走前端。真正的安全必须建立在服务器端对用户输入进行严格的、彻底的、上下文相关的验证和净化之上。4. High级别道高一尺魔高一丈4.1 更严格的过滤与新的攻击向量将安全级别调到High。我们故技重施首先尝试在“Name”框使用大写SCRIPT标签。发现失败了。查看后端源码发现防护升级了它使用了preg_replace(‘/script/i’, ‘’, $input)。这里的/i修饰符代表正则表达式匹配时不区分大小写。也就是说无论SCRIPT、Script还是sCrIpT都会被匹配并删除。看来单纯靠改变script标签的大小写已经行不通了。前端依然有长度限制所以我们依然需要Burp Suite。但script标签的路被堵死了我们该怎么办这就需要拓宽思路XSS不一定非要script标签。HTML里能触发JavaScript执行的地方多了去了。一个非常常用且经典的替代品是img标签的onerror事件。原理是这样的我们插入一个图片标签img src”…”但是把图片的地址src设成一个肯定不存在的路径比如1。浏览器尝试加载这个图片时必然会失败一旦加载失败就会触发onerror这个事件处理器。而onerror后面可以跟JavaScript代码于是一个完美的XSS Payload就诞生了。4.2 构造非脚本标签Payload我们在Burp Suite里截获提交留言的请求然后将txtName参数修改为img src1 onerroralert(document.cookie)这个Payload的意思是插入一张图片图片地址是“1”一个无效地址当图片加载错误时执行alert(document.cookie)。放行数据包后回到浏览器你会看到弹窗再次出现而页面上可能会显示一个破碎的图片图标。这里有几个技术细节值得深究。首先为什么可以不用引号在HTML中如果属性值不包含空格或特殊字符引号经常是可选的。写成src1和src”1″效果一样。这种写法有时能绕过一些基于字符串匹配的简单过滤。其次事件处理器是XSS的宝库除了onerror还有onload、onmouseover、onclick等等它们都可以在特定条件下执行JS代码。最后能执行JS的标签也不止img还有svg、iframe、body、input等等甚至一些非常冷门的标签这为攻击提供了广阔的“攻击面”。High级别的防护试图通过更完善的正则表达式封死script标签但它犯了一个关键错误只防一点不防一面。它没有对用户输入进行全面的HTML标签和属性白名单过滤也没有对事件处理器这样的危险属性进行特殊处理。这给了攻击者利用其他HTML特性进行绕过空间。防御开始变得复杂需要考虑到各种可能的HTML和JS注入场景。5. Impossible级别真正的铜墙铁壁是如何炼成的5.1 终极防御方案剖析终于我们来到了Impossible级别。顾名思义在这一级别开发者试图实现一种近乎不可能被绕过的防御。我们直接查看源码会发现关键所在无论是“Name”还是“Message”字段在输出到页面之前都毫无例外地使用了htmlspecialchars()函数并且使用了正确的标志位。在PHP中htmlspecialchars($string, ENT_QUOTES, ‘UTF-8’)是最安全的用法之一。ENT_QUOTES标志意味着同时转换单引号和双引号防止它们在HTML属性中被利用。UTF-8编码指定可以避免一些编码绕过问题。经过这个函数处理用户输入中的所有特殊字符都会被转换为对应的HTML实体变成lt;变成gt;”变成quot;’变成#039;(或apos;)变成amp;这样一来无论用户输入什么到了浏览器那里都只是一段无害的纯文本。script会原样显示为“