DVWA Medium Stored XSS安全测试与防护分析
注意:有关Stored XSS漏洞的基础知识与原理已在上篇文章指出,此处不再解释。
可参考该文章https://www.cnblogs.com/lky-sec/articles/21412117
本文仅用于网络安全学习与防御研究。所有测试均在本人本地部署、合法授权的 DVWA 靶场中进行,禁止将相关方法用于任何未授权系统。
一 测试范围与环境
本次测试目标为本地DVWA靶场中的XSS(Stored)模块。
| 项目 | 配置 |
|---|---|
| 测试环境 | 本地 DVWA 靶场 |
| 安全等级 | Medium |
| 测试系统 | Kali Linux |
| 测试工具 | Firefox、Burp Suite |
| 测试方式 | 黑盒测试、源码审计 |
| 授权情况 | 本人本地靶场,合法授权 |
二 页面与请求分析
1. 设置难度等级
进入 DVWA Security,将安全等级设置为 Medium,随后进入 XSS(Stored)模块。

2. 观察模块正常功能
页面提供 Name 和 Message 两个输入位置,用于向留言板提交用户名和留言内容。分别输入正常内容:
Name:test Message:hello
点击Sign Guestbook提交后,可以看到输入内容被保存并显示在留言板中。

与反射型XSS不同,Stored XSS提交的数据会被存储在服务器端,之后再次访问页面时,已经保存的内容仍会被重新输出。
3. 使用Burp Suite进行抓包分析
开启 Burp Suite Proxy 模块进行拦截,提交一次正常留言。

从请求中可以观察到 Name、Message以及提交按钮对应的参数。
其中:
txtName:用户提交的名称。mtxMessage:用户提交的留言内容。btnSign:表示触发留言提交操作。
三 手工黑盒测试
1. 测试Low难度Payload
在Message处输入<script>alert("x")</script> 没有弹窗,留言板内容发现有转义和<script>消失现象。

接下来测试Name处发现限制输入长度,利用Burp Suite进行提交<script>alert("x")</script> 没有弹窗。

2.尝试绕过
在Message处输入大小写混合<ScRiPt>alert("x")</ScRiPt> 未弹窗。

Name处输入大小写混合<ScRiPt>alert("x")</ScRiPt> 弹窗成功。

3.验证持久性
页面刷新重新访问页面后发现依旧弹窗。


四 黑盒测试结果分析
| 测试位置 | 测试Payload | 测试结果 | 结果分析 |
|---|---|---|---|
| Message | <script>alert("x")</script> |
未弹窗,标签被去除且内容发生转义 | Message 参数存在较严格的 HTML 标签过滤与编码处理 |
| Name | <script>alert("x")</script> |
未弹窗 | 小写 <script> 受到过滤,Low 难度 Payload 失效 |
| Message | <ScRiPt>alert("x")</ScRiPt> |
未弹窗 | 大小写变化仍无法绕过 Message 参数的标签过滤 |
| Name | <ScRiPt>alert("x")</ScRiPt> |
成功弹窗 | Name 参数的过滤存在大小写敏感问题,可绕过过滤 |
五 代码审计
1. 点击view source 查看源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
2. 源码分析
Medium 等级首先获取 Message 和 Name 两个用户输入:
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
其中,Message 参数经过了较为严格的处理:
$message = strip_tags( addslashes( $message ) );
$message = mysqli_real_escape_string(...);
$message = htmlspecialchars( $message );
strip_tags() 会去除 HTML 标签,htmlspecialchars() 会对特殊字符进行 HTML 实体编码,因此 Message 中提交的<script>等 HTML 标签通常无法被浏览器作为脚本执行。
而 Name 参数只使用:
$name = str_replace( '<script>', '', $name );
删除完全匹配的小写<script>字符串。该过滤方式存在大小写敏感问题,例如:<ScRiPt>alert("x")</ScRiPt>无法被匹配并删除。
随后 $name 被直接写入数据库:
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
当留言内容再次从数据库读取并输出到页面时,未被有效过滤的恶意标签可能被浏览器解析执行,从而形成存储型 XSS。
因此,该漏洞的主要原因是:Name 参数采用不完整的黑名单方式过滤<script>标签,且缺少可靠的HTML输出编码,导致过滤规则可以通过大小写变化等方式绕过。
3. 修复方案
1)对 Name 和 Message 参数统一进行严格的输入长度与格式限制。
2)避免使用 str_replace() 等黑名单方式过滤特定 HTML 标签。
3)对页面输出的 Name 和 Message 内容统一进行 HTML 实体编码。
4)根据数据所在的 HTML、属性或 JavaScript 上下文采用对应的输出编码方式。
5)使用参数化查询或预编译语句处理留言数据的数据库写入。
6)禁止用户可控内容直接作为 HTML 或 JavaScript 代码解析。
7)配置合理的 Content Security Policy(CSP),限制脚本执行来源。
8)对异常 XSS Payload 和高频留言请求进行日志记录与安全监控。
六 Low与Medium差异
| 对比项 | Low | Medium |
|---|---|---|
| 参数获取方式 | $_POST['mtxMessage']、$_POST['txtName'] |
$_POST['mtxMessage']、$_POST['txtName'] |
| Message基础处理 | trim() |
trim() |
| Message标签过滤 | 无 | 使用 strip_tags() 去除HTML标签 |
| Message特殊字符处理 | 使用 stripslashes() |
使用 addslashes()、htmlspecialchars() |
| Message数据库转义 | mysqli_real_escape_string() |
mysqli_real_escape_string() |
| Name基础处理 | trim() |
trim() |
| Name标签过滤 | 无 | 使用 str_replace() 删除固定小写 <script> |
| Name数据库转义 | mysqli_real_escape_string() |
mysqli_real_escape_string() |
| HTML输出编码 | 无 | Message进行了 htmlspecialchars() 处理,Name未进行 |
<script>在Message中 |
可形成XSS | 被 strip_tags() 去除 |
| 大小写混合标签在Message中 | 可形成XSS | 仍会被 strip_tags() 去除 |
<script>在Name中 |
可形成XSS | 固定小写形式被过滤 |
| 大小写混合标签在Name中 | 可形成XSS | 可绕过 str_replace() 过滤 |
| 存储型XSS漏洞 | 存在 | 仍然存在 |
| 主要防护措施 | 仅进行数据库相关转义,缺少XSS防护 | Message增加标签过滤和HTML编码,Name增加固定字符串过滤 |
| 主要安全问题 | 用户输入缺少有效HTML安全处理 | Name参数黑名单过滤不完整,且缺少可靠的输出编码 |
| 根本修复方式 | 对输出内容进行上下文相关编码 | 对所有用户可控输出统一进行上下文相关编码,避免依赖黑名单过滤 |

浙公网安备 33010602011771号