前言
存储型XSS, 即Stored Cross Site Scripting (XSS), 也可以称为持久性的XSS, 顾名思义就是恶意代码存储在服务器或者数据库中, 恶意代码一般伴随着用户的评论发表、个人信息或者文章发表的地方, 如果没有过滤或者过滤不严格, 这些代码将会被存储在服务器中, 每当用户访问的时候, 恶意代码都会被触发, 造成大量cookie泄露, 甚至蠕虫。
下面对四种不同等级的漏洞进行分析
-
Low
服务端核心代码:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $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)) ? "" : ""));
// Sanitize name input
$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();
}
?>
- trim() 函数
移除字符串两侧的空白字符或其他预定义字符,字符包括\t、\n、\x0B、\r以及空格,可选参数charlist支持添加额外需要删除的字符。
- mysql_real_escape_string()函数
对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
- stripslashes() 函数
删除字符串中的反斜杠。
首先服务端根据用户是否点击了 btnSign 来发表评论, 若有, 则将参数mtxMessage和txtName的值POST到服务端处理。处理过程中没有作任何过滤。
漏洞利用
虽然用了一些对特殊字符的过滤(转义)函数, 但对输入并没有做XSS方面的过滤与检查,且 (评论) 存储在数据库中 ,因此这里存在明显的存储型XSS漏洞。
在评论中注入:
<script>alert(4)</script>
成功弹窗:
- 危害:
当其他用户(甚至管理员)访问被xss攻击的页面的时候, 攻击者注入的所有语句会被执行, 由于是存储在数据库中的, 只要不断有用户访问, 这些恶意语句就会不断被执行, 导致用户的cookie被利用、信息泄露等。
-
Medium
服务端核心代码:
<?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();
}
?>
- strip_tags() 函数
剥去字符串中的HTML、XML以及PHP的标签,但允许使用<b>标签。
- addslashes() 函数
返回在预定义字符(' 、" 、\、NULL)之前添加反斜杠的字符串。
- htmlspecialchars() 函数
把一些预定义的字符转换为 HTML 实体。
预定义的字符是:
& (和号) 成为 &
" (双引号) 成为 "
' (单引号) 成为 '
< (小于) 成为 <
> (大于) 成为 >
使用htmlspecialchars函数把预定义的字符&、”、 ’、<、>转换为HTML实体,防止浏览器将其作为HTML元素。
由此可见, 服务端对评论信息(mtxMessage)做了严格的过滤, 但是并没有对用户名进行同样的过滤。
漏洞利用
服务端对用户名仅仅做了script标签的过滤, 绕过有很多这里同样介绍三种:
- 方法一: 大小写绕过
这里需要明白的一点是
- HTML中对大小写不敏感
- JS中对大小写敏感
那么我们在name处的注入语句就有:
<Script>alert(5)</sCript>
输入的时候, 发现前段在name的表单限制了输入字符字数:
那么我们可以抓包修改:
forward之后弹窗成功, 并且可以看到在name处注入了:
- 方法二: 双写绕过
由于对name的内容只用str_place()过滤了一次, 对于多次出现的无法全部匹配完, 所以这里可以用双写绕过:
<s<script>cript>alert('You are attacked!')</script>
- 方法三: 错误事件img标签绕过
<img src="" onerror="alert(6)">
可以看到, img标签也同样注入了:
-
High
服务端核心代码:
<?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 = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $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();
}
?>
可以看到, 服务端对name的内容进一步用了正则式并且不区分大小写地过滤所有script标签。
漏洞利用
虽然过滤了script标签, 但是还是可以进行其他的标签注入, 比如Medium等级的方法三。
-
Impossible
服务端核心代码:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $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 = stripslashes( $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)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
可以看到,通过使用htmlspecialchars函数,解决了XSS,但是要注意的是,如果htmlspecialchars函数使用不当,攻击者就可以通过编码的方式绕过函数进行XSS注入,尤其是DOM型的XSS。
打个比方,
虽然应用了htmlspecialchars函数, 但忽略了一点, 由于编码后处于html标签中, 所以当解析代码的时候, 被过滤编码的字符仍然会被还原来执行。