DVWA靶机学习——XSS (Stored)

这个系列是学习DVWA靶机的。今天学习XSS (Stored),即存储型XSS的Low、Medium、High、Impossible级别。

Posted by K4ys0n on November 22, 2020

0x00 XSS (Stored)

反射型的XSS一般会直接echo输出到html文件里,而存储型XSS则是先保存到数据库,然后再在某个页面查询显示出来。

0x01 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();
}
?>

这里只使用mysqli_real_escape_string函数对输入进行SQL注入的过滤,但没有过滤XSS,直接insert到数据库中。然后我们尝试随便输入name和message提交,会在当前页面输出,所以可以进行存储型XSS利用。

解题思路

直接向name或message输入框中输入以下代码,提交即可构成存储型XSS。

<script>alert(1);</script>

注意name输入框有前端字数限制,F12打开开发者工具进行修改即可。

注入XSS之后即使点击了其他页面,再点回来,还是会有XSS弹窗,这就是反射型XSS和存储型XSS的区别。所以实验完成后需要删除该数据,以便后续实验。

0x02 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();
}
?>

源码中对message变量做了几个操作:

  • addslashes()函数对’、”、\、NULL等前面添加反斜杠进行转义,以防SQL注入。
  • strip_tags(string,allow)函数剥离string中的HTML标签,但允许allow字符串中的标签存在。
  • mysqli_real_escape_string()函数转义SQL语句中使用的特殊符号,防止SQL注入。
  • htmlspecialchars()函数将HTML特殊符号,如<,转为HTML实体。

对name变量只做了str_replace()替换script标签,然后mysqli_real_escape_string()函数防SQL注入,那么就可以利用双写或大小写的方式绕过替换函数。

解题思路

修改前端name输入框的maxlength为10000,然后向name输入框中输入以下代码,提交即可构成存储型XSS。

<scr<script>ipt>alert(1);</script>
或者
<Script>alert(1);</scrIpt>

提交出现弹窗,成功!当然,实验完成后还是需要删除该数据,以便后续实验。

0x03 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();
}
?> 

源码中message变量和Medium级别中的一样无法绕过。

而name变量做了preg_replace正则匹配大小写的script标签,所以已经无法使用script标签进行XSS攻击了,但是可以使用img等其他标签。

解题思路

修改前端name输入框的maxlength为10000,然后向name输入框中输入以下代码,提交即可构成存储型XSS。

<img src='1' onerror='alert(1);'/>

提交出现弹窗,成功!当然,实验完成后还是需要删除该数据,以便后续实验。

0x04 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();
?> 

源码中对message和nmae变量都做了几个操作:

  • addslashes()函数对’、”、\、NULL等前面添加反斜杠进行转义,以防SQL注入。
  • strip_tags(string,allow)函数剥离string中的HTML标签,但允许allow字符串中的标签存在。
  • mysqli_real_escape_string()函数转义SQL语句中使用的特殊符号,防止SQL注入。
  • htmlspecialchars()函数将HTML特殊符号,如<,转为HTML实体。
  • PDO技术对用户输入变量做了预编译,以防SQL注入。

经过上述这些操作基本就无法进行存储型XSS注入和SQL注入了。

解题思路

无。

0x05 小结

防御方法:

  • 过滤用户输入,如addslashes转义、strip_tags剥离HTML标签、preg_replace黑名单匹配、htmlspecialchars转HTML实体等过滤用户输入。
  • 使用OWASP等安全XSS处理API。
  • 同时存储数据前还要进行mysqli_real_escape_string转义、PDO预编译等手段,防止SQL注入。

0x06 参考

https://www.freebuf.com/articles/web/123779.html