浅谈 ThinkPHP 中的注入
2019-10-09 10:45:49 Author: xz.aliyun.com(查看原文) 阅读量:178 收藏

CMS 中难免会调用到 SQL 执行(废话),百密总有一疏,今天跟师傅们分享一些平时审计时遇到的注入,可能不全面,希望和师傅们一起学习。

这绝对是能拿来探讨探讨的,因为我看到的版本就有两三种了,这里给出两个例子。

ThinkPHP3.2.3

首先我们举个最简单的例子作为开场:

public function login(){
    $User = D('User');
    $map = array('username' => $_POST['username']);
    $user = $User->where($map)->find();
}

首先调用了 where 函数:

public function where($where,$parse=null){

    ....

    if(isset($this->options['where'])){
        $this->options['where'] =   array_merge($this->options['where'],$where);
    }else{
        $this->options['where'] =   $where;
    }

    return $this;
}

这里简单的将 where 放进了 options[where] 里。

然后第二步是调用 find 函数,find 的内部又调用了 ->db->select,这是具体的实现,可以进去看看(查询时可能会查找三个,通常是 Driver.class.php):

public function select($options=array()) {
    $this->model  =   $options['model'];
    $this->parseBind(!empty($options['bind'])?$options['bind']:array());
    $sql    = $this->buildSelectSql($options); // 生成 sql 语句
    $result   = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
    return $result;
}

继续跟入 buildSelectSql 函数,会发现里面调用了 parseSql 函数,继续跟:

public function parseSql($sql,$options=array()){
    $sql   = str_replace(
        array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),
        array(
            $this->parseTable($options['table']),
            $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
            $this->parseField(!empty($options['field'])?$options['field']:'*'),
            $this->parseJoin(!empty($options['join'])?$options['join']:''),
            $this->parseWhere(!empty($options['where'])?$options['where']:''),
            $this->parseGroup(!empty($options['group'])?$options['group']:''),
            $this->parseHaving(!empty($options['having'])?$options['having']:''),
            $this->parseOrder(!empty($options['order'])?$options['order']:''),
            $this->parseLimit(!empty($options['limit'])?$options['limit']:''),
            $this->parseUnion(!empty($options['union'])?$options['union']:''),
            $this->parseLock(isset($options['lock'])?$options['lock']:false),
            $this->parseComment(!empty($options['comment'])?$options['comment']:''),
            $this->parseForce(!empty($options['force'])?$options['force']:'')
        ),$sql);
    return $sql;
}

这里替换了一些信息后成了最终的 sql 语句,我们可控的是 where ,所以我们跟入 parseWhere

protected function parseWhere($where) {
    $whereStr = '';
    if(is_string($where)) {
        ....
    }else{ // 使用数组表达式
        $operate  = isset($where['_logic'])?strtoupper($where['_logic']):'';
        if(in_array($operate,array('AND','OR','XOR'))){
            ....
        }else{
            ....
        }
        foreach ($where as $key=>$val){
            ....
            if(0===strpos($key,'_')) {
                ....
            }else{
                $multi  = is_array($val) &&  isset($val['_multi']);
                $key    = trim($key);
                if(strpos($key,'|')) { 
                   ...
                }elseif(strpos($key,'&')){
                    .....
                }else{
                    $whereStr .= $this->parseWhereItem($this->parseKey($key),$val);
                }
            }
            $whereStr .= $operate;
        }
        $whereStr = substr($whereStr,0,-strlen($operate));
    }
    return empty($whereStr)?'':' WHERE '.$whereStr;
}

如果传入一个数组,最终会进入到,最后我留下的这个 parseWhereItem

这个函数是审计 TP 注入时的关键函数。

这里的 $val 即使是一个数组也是可以的,所以我们可以传入一个数组进 parseWhereItem 函数,继续跟进:

protected function parseWhereItem($key,$val) {
    $whereStr = '';
    if(is_array($val)) {
        if(is_string($val[0])) {
            $exp = strtolower($val[0]);
            if(...) {
                ....
            }elseif(....){
               .....
            }elseif('bind' == $exp ){
                ...
            }elseif('exp' == $exp ){ //看这里~~~~
                $whereStr .= $key.' '.$val[1];
            }elseif(preg_match('/^(notin|not in|in)$/',$exp)){
                .....
            }elseif(preg_match('/^(notbetween|not between|between)$/',$exp)){ 
                .....
            }else{
                E(L('_EXPRESS_ERROR_').':'.$val[0]);
            }
        }else {
            ...
        }
    }else {
        ....
    }
    return $whereStr;
}

这里省略了大部分代码,在留下的一个逻辑中可以看到,当 $val 为数组且第一位是 exp 时,直接将 $val 的第二位拼接进去了,没有过滤。。。我们可以传入个 payload 试试。

我们可以在 parseSql 函数 return 前加入一行 print_r($sql);

然后传入:

成功注入了,至于利用就不过多叙述了。

ThinkPHP5.0

TP5parseWhereItemDb.class.php,但其实也有一段和 3.2 中差不多的代码:

if(isset($val[2]) && 'exp'==$val[2]) {
    $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1];
}

但是在 TP5.0 却加入了一个全局过滤:

他在我们的 EXP 后面给多加了一个空格,导致这里不能用了,贴一下这个在 5.0 中这个函数的代码:

protected function parseWhereItem($key,$val) {
    $whereStr = '';
    if(is_array($val)) {
        if(is_string($val[0])) {
            if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT|NOTLIKE|LIKE)$/i',$val[0])) { 
                ...
            }elseif('exp'==strtolower($val[0])){ 
                ...
            }elseif(preg_match('/^(NOTIN|NOT IN|IN)$/i',$val[0])){ 
                ...
            }elseif(preg_match('/(NOTBETWEEN|NOT BETWEEN|BETWEEN)/i',$val[0])){ // 看这里~~~
                $data = is_string($val[1])? explode(',',$val[1]):$val[1];
                $whereStr .=  ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )';
            }else{
                throw_exception(L('_EXPRESS_ERROR_').':'.$val[0]);
            }
        }else {
            ...
        }
    }else {
        ...
    }
    return $whereStr;
}

可以前面两个正则都使用了 ^$ 来限定正则表达式的开始和结束。但是 BETWEEN 这里却没有,只要 $val[0] 里带有 between 即可,在下面中也没有对 val[0] 带入 parseValue 进行过滤,那么这里我们能就可以插入单引号。

举个例子:

我们传入:id[0]=BETWEEN '1234&id[1]=abcd

调试图:

可以看到 abcd 被我们逃逸出来了。到这里注入就算完成了。具体的利用得看实际情况中。

ThinkPHP5.1

TP5.1 中全部参数都被绑定了,弟弟没有 0day 在手,所以无法分享这个版本。。233

关于 TP 的总结

其实其他函数可能还有注入,但是这里就不再去深入了,只分析了三个不同版本的 parseWhereItem 函数。其中可能有什么有误或者不清楚的地方,欢迎师傅们提出问题一起探讨。

这里就不得不提一手 红日团队 的:https://github.com/hongriSec/PHP-Audit-Labs

里面也有一些关于 TP


文章来源: http://xz.aliyun.com/t/6480
如有侵权请联系:admin#unsafe.sh