一、Stored XSS概述
前文 DVWA之跨站脚本攻击漏洞测试01-反射型XSS 对Reflected XSS漏洞进行测试分析,本文则关注Stored XSS(存储型XSS)漏洞的分析:
- Reflected XSS作为非持久XSS攻击,其脚本注入的攻击并不存储在应用程序数据库中,而是存储在浏览器端;当更换浏览器or清除原浏览器中的缓存之后,对应的攻击脚本即会失效
- Stored XSS除了可以在浏览器端展示所提交的数据之外,主要是直接将数据存储到数据库中,因此是持久性的,直至数据库被重置or攻击的负载(XSS恶意脚本)被手动删除;只要数据库中的数据未失效,则在不同浏览器端不同用户访问被Stored XSS脚本注入的页面时,都将执行注入的脚本,触发XSS攻击,获取cookie等敏感信息
二、全等级存储型XSS漏洞测试
1. Level:Low
low.php 服务端代码:
<?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() 函数移除字符串两侧的空白字符或其他预定义字符,如:"\0"-NULL、"\t"-制表符、"\n"-换行、"\r"-回车、" "-空格
stripslashes() 函数实现删除字符串中的反斜杠\
,取消转义;可用于清理从数据库中or从 HTML 表单中取回的数据
isset() 函数用于检测变量是否已设置并且非 NULL
is_object() 函数用于检测变量是否是一个对象
mysqli_real_escape_string() 函数转义在 SQL 语句中使用的字符串中的特殊字符,从SQL注入角度进行了防范
trigger_error() 函数创建用户自定义的错误消息
die() 函数输出一条消息,并退出当前脚本
mysqli_query() 函数执行某个针对数据库的查询
执行SQL:INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );
1)功能分析
low级别的代码实现将前端输入的Name、Message字段内容提交到数据库服务端保存,并在浏览器端展示,过程中浏览器界面会进行解析过滤敏感字符的展示,但在HTML源码中是会保留有敏感字符
前端提交到后端的数据,从服务端代码来分析,并没有作存储时的字符过滤和判断机制,很可能存在有XSS漏洞
Name字段被限制的字符长度maxlength=10,Message字段限制字符长度maxlength=50。因此若通过浏览器界面输入的方式提交字段内容,则可能产生XSS漏洞的位置为Message字段;若通过拦截数据表的方式修改Name字段再作提交,服务端并没有对提交的字符长度作限制,则Name和Message字段都需要一起考虑漏洞检查
2)漏洞测试
构造测试脚本:
方式①:在浏览器界面输入提交
Message:
<script>alert(document.cookie)</script>
以上,访问该XSS注入的页面时,将会读取到所登录用户的Cookie信息
方式②:拦截提交到服务端的数据包,修改后再提交
即便前端界面中Name字段输入限制了不能超过10个字符,利用输入框构造XSS不太现实,但若将拦截数据包中的Name字段内容修改为带有XSS脚本,通过拦截工具重放数据包提交到服务端后,也会注入XSS脚本,如下:
2. Level:Medium
medium.php 服务端代码:
<?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();
}
?>
1)功能分析
medium级别的代码,在low级别基础之上新增项如下:
①strip_tags() 函数:剥去字符串中的 HTML、XML 以及 PHP 的标签
②addslashes() 函数:返回在预定义字符之前添加反斜杠的字符串;添加反斜杠进行转义,可用于字符串数据存储到数据库中。预定义字符:单引号(')、双引号(")、反斜杠(\)、NULL
③htmlspecialchars()函数:实现将Message字段输入内容中的特殊字符(逻辑与符号&
、双引号"
、单引号'
、小于号<
、大于号>
)转换为 HTML 实体,从而使得浏览器不将解其析为html元素构造的脚本来执行,只作为普通输入的字符来显示
④str_replace()函数:$name = str_replace( '<script>', '', $name );
实现将Name字段输入内容中的字符串"<script>"替换为空字符,字符区分大小写
语法:
str_replace(find,replace,string,count)
Message字段进行了XSS防范相对严格的限制,而Name字段从前端提交到后端的数据,只作了简单的字符串替换,若通过修改字符串的大小写变换亦可绕过被替换
2)漏洞测试
方式①:大写绕过
Name测试脚本:
<Script>alert(document.cookie)</script>
方式②:双写绕过
Name测试脚本:
<sc<script>ript>alert(document.cookie)</script>
3. Level:High
high.php 服务端代码:
<?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();
}
?>
1)功能分析
high级别新增了preg_replace()函数,实现将Name字段输入内容中字符串"<script>"中的每个字符拆分,字符之间不论包含任意字符都会被匹配到,正则主体末尾的i
表示大小写不敏感,即大小写都会匹配到
preg_replace()函数:执行一个正则表达式的搜索和替换,搜索 subject中匹配 pattern的部分, 以 replacement进行替换
语法:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
$count: 可选,为替换执行的次数
以上,相当于服务端已对Name字段作为<script>标签的严格限制,此时需要考虑调整为其他标签来构造XSS脚本
2)漏洞测试
测试脚本:
<img src="http://attacker/xss.js" onerror=alert(document.cookie) />
<img src="http://attacker/xss.js" onmouseover=alert(document.cookie) />
<img src="http://attacker/xss.js" onmouseover=prompt(document.cookie) />
脚本01:
脚本02:
脚本03:
4. Level:Impossible
impossible.php 服务端代码:
<?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();
?>
impossible级别,进行了更加严格的字符处理和验证机制:
(1)Name和Message字段都采用htmlspecialchars()函数进行处理,实现将字段输入内容中的特殊字符(逻辑与符号&、双引号"、单引号'、小于号<、大于号>)转换为 HTML 实体,从而使得浏览器不将解其析为html元素构造的脚本来执行,只作为普通输入的字符来显示
(2)加入了Token校验机制,将存储在服务端session中的session_token变量的值返回一份给浏览器端form表单中隐藏域hidden中的user_token变量来接收存放。等下一次通过浏览器表单提交数据到服务端时就会一起带着上一次下发的user_token值与服务端存储的session_token值进行比对校验是否一致,两者一致时才会执行接收请求,此时服务端则会更新session_token值为新的随机数值,若再以上一次提交的完全相同的url和user_token向服务器发起请求则会被判定为是重复的提交而执行失败,可以有效防止表单重复多次提交,以及CSRF(跨站请求伪造)的攻击
(3)采用了PDO技术(可参看:PHP之详解PDO),划清了代码与数据的界限,有效防御SQL注入
网友评论