网络安全:存储型XSS

书接上文(有没有一种熟悉的味道),来看存储型XSS,这个最常见,也最好理解。

数据库里的一条数据显示了出来。

我们输入一些东西,下方又会多出来一条数据:

如果重新加载页面,仍旧有这两条数据。这就是存储型,输入数据会被存入数据库,网页每次被打开,都会从数据库里调出显示。

扫描二维码关注公众号,回复: 9141009 查看本文章

这个像什么?对,就是我们文章下面的评论系统,文章所有的评论会被存进数据库,每一个浏览此文章的人,都能看到所有的评论。

试想,如果有人使坏,写了一段脚本存进数据库了呢?

 

先点击ClearGuestbook,清空一下。

 

在message中注入:<script>alert("This is low level")</script>

sign之后:

内容成功显示。

 

我们先点击一下其他页面,再点回来:

成功被触发了。试想如果我们的评论全是这东西,每个点进来的人,都要先被来一番“信息轰炸”,才能看到想要的界面,搞不好这之前自己的个人信息已经被泄露了出去。

 

来查看下后台源码:

<?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()函数,删除掉一些字符,如果第二个参数不指定。则删去:\n, \t, \0, \X0B, \r, space。

stripslashes(),去除字符串中预定义字符之前的反斜杠。对应的,还有一个addslashes,对于一个string = stripslashes( addslashes( string ) )。

 

其实上面的代码只是对接收到的两个参数删除了一些不必要的信息,加上去除转义的效果。并没有对数据内容进行检测,然后直接进行了insert into:

$query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";

 

这就是问题所在,来看看medium等级的。

 

view source:

<?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变量的处理:

strip_tags()函数。去除标签,包括html,xml,php标签。

htmlspecialchars()函数。前面谈到过,对html中一些预定义字符进行实体化,包括&,',",<,>。

 

(PS:这就是对XSS的预防,一定要对用户输入进行检测。上面两个函数使用到的话,基本就没戏了。)

 

message没戏了,看下name参数的处理。

 

Nice,只是对输入数据过滤了“<script>”字符串。(替换为空)

 

通过前面我们的绕过姿势,应该没什么问题了吧。基本的两种方法:1.调整大小写;2.复写。

 

我这里选择第一种演示:

数据输入过程中,你会发现一个问题。Name只能输入10个字符,再多输不进去了。急了个满头大汗,以为键盘坏了,砸了个粉碎。过了会发现冤枉键盘了,因为message还可以继续输入。

 

(上面的是扯皮)好多地方提到过一句话:“一切在前端的限制,都不是限制。”

 

因为这个东西是可以修改的。一般有两种通常使用的做法:1.抓包(俚语,专业一点,或许应该称为“截获报文”),修改参数;2.直接在前端修改限制。

 

我挑个操作简单的,选第二种。

 

在Name输入框旁边,右键检查。找到限制属性maxlength,直接右键Edit attribute,改成100

修改完毕后按回车:

 

继续注入:

Name不再受限制了。

 

sign进数据库。

 

现在有了两条数据,点击目录中的其他页面,再点击回来(或者直接右键,重新加载页面。但是会又一次的注入)。

低等级提示。

确定后,中等级提示。

 

致此,知道为何会被称为存储型了吧。

 

再来看看高等级的呗,也很简单,前面我们见过:

<?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的处理没变,所以还是没戏。

 

看name,换成了preg_replace()来正则匹配包含<script的内容(如果了解正则表达式,很好看懂),且不区分大小写。换句话说,对字符串的复写和调整大小,全部没用了。

 

我们前面遇到过呀。不让用“script”,我们还有img和body。

 

清空一下。

 

img注入:<img src=1 οnerrοr=alert("high") />(当然不要忘记,解除name输入长度的限制)

 

sign进数据库:

显然,成功了。

 

重新加载页面:

                        

脚本被激活。

 

impossible等级呢?不说应该也猜到了,对name进行和message同样的处理。

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

?>

在update database阶段还进行了预处理,感兴趣的小盆友可以去查询了解下。

 

连先前high等级注入的XSS,也被实体化后显示了出来:

         

 

完。(虽然很突兀,但是真的完了。)

发布了53 篇原创文章 · 获赞 80 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41500251/article/details/100587668