浅谈 ThinkPHP 中的注入

2019-10-15 约 398 字 预计阅读 2 分钟

声明:本文 【浅谈 ThinkPHP 中的注入】 由作者 这是一个睿智 于 2019-10-09 09:45:49 首发 先知社区 曾经 浏览数 4624 次

感谢 这是一个睿智 的辛苦付出!

前言

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

关于 ThinkPHP 中的 注入

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

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

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now