文章目錄

##参考资料
http://www.freebuf.com/articles/web/53018.html
http://seclists.org/fulldisclosure/2014/Nov/20

##代码分析

根据之前的漏洞分析可知,入口在interface/ipsconnect/ipconnect.php文件。查看这个文件可以得知,这个文件主要定义了一个ipsConnect类,剩余的逻辑如下:

/**
 *
 * Map - can modify to add additional parameters, but the IPS Community Suite will only send the defaults
 *
 */
$map = array(
    'login'        => array( 'idType', 'id', 'password', 'key', 'redirect', 'redirectHash' ),
    'logout'    => array( 'id', 'key', 'redirect', 'redirectHash' ),
    'register'    => array( 'key', 'username', 'displayname', 'password', 'email', 'revalidateurl' ),
    'cookies'    => array( 'data' ),
    'check'        => array( 'key', 'id', 'username', 'displayname', 'email' ),
    'change'    => array( 'id', 'key', 'username', 'displayname', 'email', 'password', 'redirect', 'redirectHash' ),
    'validate'    => array( 'id', 'key' ),
    'delete'    => array( 'id', 'key' )
    );

/**
 *
 * Process Logic - do not modify
 *
 */ 
$ipsConnect = new ipsConnect();
if ( isset( $_REQUEST['act'] ) and isset( $map[ $_REQUEST['act'] ] ) )
{
    $params = array();
    foreach ( $map[ $_REQUEST['act'] ] as $k )
    {
        $params[ $k ] = $_REQUEST[ $k ];
    }

    call_user_func_array( array( $ipsConnect, $_REQUEST['act'] ), $params );
}

exit;

$map是定义一个数组,先不管它。后面的逻辑也比较简单,一个主要的知识点就是call_user_func_array的作用。他是调用回调函数,并把一个数组参数作为回调函数的参数。使用示例如下:

<?php
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
    function bar($arg, $arg2) {
        echo __METHOD__, " got $arg and $arg2\n";
    }
}


// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));

// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?> 

所以这里$_REQUEST[‘act’]为login的时候就是调用$ipsConnect对象的login方法,根据公开的poc:

act=login&idType=id&id[]=-1&id[]=-1)and 1!="'" and extractvalue(1,concat(0x3a,(SELECT
COUNT(*) FROM members)))#'

可以得之,漏洞出在login方法中。从interface/ipsconnect/ipconnect.php 748行可知,login方法可以接收6个参数。

'login'        => array( 'idType', 'id', 'password', 'key', 'redirect', 'redirectHash' )

从poc中也可以知道存在注入的参数是’id’这个参数。然后再分析login方法,可以发现,可能存在问题的只有

$member = IPSMember::load( $identifierValue, 'none', $identifier );

这里的调用了。这个在之前的分析文章里已经说明了。但是这里load函数里面其实还有一些别的逻辑。我们可以跟下去看看。

load方法是/admin/sources/base/ipsMember.php文件中,IPSMember类的一个静态方法。load的函数原型为:

static public function load( $member_key, $extra_tables='all', $key_type='' )

从之前的分析可以知道$member_key这个参数都是用户可控的,也就是poc中id参数,而$key_type这个参数在login方法中有个白名单的限制,

if ( in_array( $identifier, array( 'id', 'email', 'username' ) ) )

只能是id,email,username3个写死的字符串中的一个。

分析load方法,在1059行,可以看到

$multiple_ids = array_map( 'intval', $member_key ); // Bug #20908
$member_field = 'member_id';

传进来的$member_key被intval了,还有个注释bug fixed 的注释。那这样子就不应该存在问题啊。仔细一看,原来前面有个if条件,

if ( ! $key_type )

前面说过,这个$key_type只能是白名单的字符串,所以这个条件判断是false。不会进入到这个分支中。从这里也可以看出来,这个load以前应该是也出过问题,但是仅仅修复了这一个点。

查看if的else分支,1082行:

else
{
    switch( $key_type )
    {
        default:
        case 'id':
            if ( is_array( $member_key ) )
            {
                $multiple_ids = $member_key;
            }
            else

看到$member_key赋值给了$multiple_ids,没有经过其他处理。在1368行,$multiple_ids拼接到了sql语句中。

if ( count( $joins ) )
    {
        ipsRegistry::DB()->build( array( 'select'   => 'm.*, m.member_id as my_member_id',
                                         'from'     => array( 'members' => 'm' ),
                                         'where'    => ( is_array( $multiple_ids ) AND count( $multiple_ids ) ) ?  'm.'. $member_field . ' IN (' . implode( ',', $multiple_ids ) . ')' : 'm.'. $member_field . '=' . $member_value,
                                         'add_join' => $joins ) );

这里就是最后进入sql的地方了。

这个过程中有意思的一点就是前面提到过,在load方法中有这样一句。

$multiple_ids = array_map( 'intval', $member_key ); // Bug #20908

这里有个bug 修改的标记。说明以前可能出过漏洞。我没有细查ipb的漏洞历史,如果这一句在之前的版本是没有过滤的话,直接

$multiple_ids = $member_key;  

也是存在SQL注入漏洞的。跟这个漏洞的利用基本一样。在interface/ipsconnect/ipconnect.php522行

public function check( $key, $id, $username, $displayname, $email )
{
    $return = array( 'username' => NULL, 'displayname' => NULL, 'email' => NULL );

    $member = array();
    if ( $id )
    {
        $member = IPSMember::load( $id );

这个check方法也调用了IPSMember::load,而这里由于只传了一个参数,所以load方法的后面几个参数都是默认值,刚好会进入这个分支。

11

##漏洞检测

poc里写死了表明

个人微信公众号

文章目錄