频道栏目
首页 > 安全 > 网站安全 > 正文
ThinkPHP最新版本SQL注入漏洞
2015-01-28 10:10:38         来源:phith0n  
收藏   我要投稿

你们可以说上一个SQL注入漏洞有鸡肋性,确实TP的I函数会进行一定处理。
但这个洞,I函数也奈何不了了,通用到你没话说。
与上几个洞没有关系,非补丁造成。也是影响到3.1~3.2版本的。继续用onethink做演示。

如下controller即可触发SQL注入:

public function test()
    {
    $uname = I('get.uname');
    $u = M('user')->where(array(
    'uname' => $uname
    ))->find();
    dump($u);
    }



为什么?

我们看看代码。我从github下载的最新源码:https://github.com/liu21st/thinkphp

/ThinkPHP/Library/Think/Db/Driver.class.php 531行:

// where子单元分析
    protected function parseWhereItem($key,$val) {
        $whereStr = '';
        if(is_array($val)) {
            if(is_string($val[0])) {
                if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT)$/i',$val[0])) { // 比较运算
                    $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]);
                }elseif(preg_match('/^(NOTLIKE|LIKE)$/i',$val[0])){// 模糊查找
                    if(is_array($val[1])) {
                        $likeLogic  =   isset($val[2])?strtoupper($val[2]):'OR';
                        if(in_array($likeLogic,array('AND','OR','XOR'))){
                            $likeStr    =   $this->comparison[strtolower($val[0])];
                            $like       =   array();
                            foreach ($val[1] as $item){
                                $like[] = $key.' '.$likeStr.' '.$this->parseValue($item);
                            }
                            $whereStr .= '('.implode(' '.$likeLogic.' ',$like).')';                          
                        }
                    }else{
                        $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]);
                    }
                }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];
                    }else{
                        if(is_string($val[1])) {
                             $val[1] =  explode(',',$val[1]);
                        }
                        $zone      =   implode(',',$this->parseValue($val[1]));
                        $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')';
                    }
                }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算
                    $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{
                    E(L('_EXPRESS_ERROR_').':'.$val[0]);
                }
            }else {
                $count = count($val);
                $rule  = isset($val[$count-1]) ? (is_array($val[$count-1]) ? strtoupper($val[$count-1][0]) : strtoupper($val[$count-1]) ) : '' ; 
                if(in_array($rule,array('AND','OR','XOR'))) {
                    $count  = $count -1;
                }else{
                    $rule   = 'AND';
                }
                for($i=0;$i<$count;$i++) {
                    $data = is_array($val[$i])?$val[$i][1]:$val[$i];
                    if('exp'==strtolower($val[$i][0])) {
                        $whereStr .= $key.' '.$data.' '.$rule.' ';
                    }else{
                        $whereStr .= $this->parseWhereItem($key,$val[$i]).' '.$rule.' ';
                    }
                }
                $whereStr = '( '.substr($whereStr,0,-4).' )';
            }
        }else {
            //对字符串类型字段采用模糊匹配
            $likeFields   =   $this->config['db_like_fields'];
            if($likeFields && preg_match('/^('.$likeFields.')$/i',$key)) {
                $whereStr .= $key.' LIKE '.$this->parseValue('%'.$val.'%');
            }else {
                $whereStr .= $key.' = '.$this->parseValue($val);
            }
        }
        return $whereStr;
    }



这就是处理where条件的函数,我们看到如下片段:

}elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算
    $data = is_string($val[1])? explode(',',$val[1]):$val[1];
    $whereStr .=  $key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]);
}



当匹配/BETWEEN/i和$val[0]时,则将strtoupper($val[0])直接插入了SQL语句。

这个匹配:preg_match('/BETWEEN/i',$val[0]),明显是有问题的。因为这个匹配没加^$也就是首尾限定,所以只要我们的$val[0]中含有between时,这个匹配就可以成立,就产生了一个SQL注入。

为了防止I函数对我们输入的过滤影响,我们看看I函数:

function I($name,$default='',$filter=null,$datas=null) {
if(strpos($name,'/')){ // 指定修饰符
list($name,$type) =explode('/',$name,2);
}
    if(strpos($name,'.')) { // 指定参数来源
        list($method,$name) =   explode('.',$name,2);
    }else{ // 默认为自动判断
        $method =   'param';
    }
    switch(strtolower($method)) {
        case 'get'     :   $input =& $_GET;break;
        case 'post'    :   $input =& $_POST;break;
        case 'put'     :   parse_str(file_get_contents('php://input'), $input);break;
        case 'param'   :
            switch($_SERVER['REQUEST_METHOD']) {
                case 'POST':
                    $input  =  $_POST;
                    break;
                case 'PUT':
                    parse_str(file_get_contents('php://input'), $input);
                    break;
                default:
                    $input  =  $_GET;
            }
            break;
        case 'path'    :   
            $input  =   array();
            if(!empty($_SERVER['PATH_INFO'])){
                $depr   =   C('URL_PATHINFO_DEPR');
                $input  =   explode($depr,trim($_SERVER['PATH_INFO'],$depr));            
            }
            break;
        case 'request' :   $input =& $_REQUEST;   break;
        case 'session' :   $input =& $_SESSION;   break;
        case 'cookie'  :   $input =& $_COOKIE;    break;
        case 'server'  :   $input =& $_SERVER;    break;
        case 'globals' :   $input =& $GLOBALS;    break;
        case 'data'    :   $input =& $datas;      break;
        default:
            return NULL;
    }
    if(''==$name) { // 获取全部变量
        $data       =   $input;
        $filters    =   isset($filter)?$filter:C('DEFAULT_FILTER');
        if($filters) {
            if(is_string($filters)){
                $filters    =   explode(',',$filters);
            }
            foreach($filters as $filter){
                $data   =   array_map_recursive($filter,$data); // 参数过滤
            }
        }
    }elseif(isset($input[$name])) { // 取值操作
        $data       =   $input[$name];
        $filters    =   isset($filter)?$filter:C('DEFAULT_FILTER');
        if($filters) {
            if(is_string($filters)){
                $filters    =   explode(',',$filters);
            }elseif(is_int($filters)){
                $filters    =   array($filters);
            }
            
            foreach($filters as $filter){
                if(function_exists($filter)) {
                    $data   =   is_array($data) ? array_map_recursive($filter,$data) : $filter($data); // 参数过滤
                }elseif(0===strpos($filter,'/')){
                // 支持正则验证
                if(1 !== preg_match($filter,(string)$data)){
                return   isset($default) ? $default : NULL;
                }
                }else{
                    $data   =   filter_var($data,is_int($filter) ? $filter : filter_id($filter));
                    if(false === $data) {
                        return   isset($default) ? $default : NULL;
                    }
                }
            }
        }
        if(!empty($type)){
        switch(strtolower($type)){
        case 's':   // 字符串
        $data =(string)$data;
        break;
        case 'a':// 数组
        $data =(array)$data;
        break;
        case 'd':// 数字
        $data =(int)$data;
        break;
        case 'f':// 浮点
        $data =(float)$data;
        break;
        case 'b':// 布尔
        $data =(boolean)$data;
        break;
        }
        }
    }else{ // 变量默认值
        $data       =    isset($default)?$default:NULL;
    }
    is_array($data) && array_walk_recursive($data,'think_filter');
    return $data;
}



较前些版本有些改进:

1.加了类型强制转换$type,但在默认情况下$type是空的,强制类型转换是不存在的。

2.将is_array($data) && array_walk_recursive($data,'think_filter');放在最后一行。我们看看think_filter这个过滤函数:

function think_filter(&$value){
// TODO 其他安全过滤

// 过滤查询特殊字符
    if(preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|LIKE|NOTLIKE|BETWEEN|IN)$/i',$value)){
        $value .= ' ';
    }
}



这个实际上就是对我之前那个漏洞的一个解决方案,将一些关键词后面加空格。但我们看到,这个正则是存在“^$”首尾限定符的。所以只有传入参数完全“等于”BETWEEN的时候才会被加上空格,而且这里加上空格也不会影响漏洞的产生,因为漏洞位置的正则没有加^$首尾限定符。



还有一个说明:之前thinkphp出了个“错误”的补丁,这个补丁已经被官方去掉了,所以不用考虑那个补丁造成的一些干扰。

那我们回到最初那段代码:

public function test()
    {
    $uname = I('get.uname');
    $u = M('user')->where(array(
    'uname' => $uname
    ))->find();
    dump($u);
    }



这个代码,我们来测试一下:
 

01.jpg



果然是有漏洞的,我们看看具体执行的SQL语句:
 

02.jpg



就在BETWEEN处。除了BETWEEN外还有IN,我就一块说明了。

Onethink演示:

 

03.jpg

 

解决方案:

正则一定要写明确:

/^BETWEEN$/i

 

点击复制链接 与好友分享!回本站首页
相关TAG标签 漏洞 版本
上一篇:THEOL网络教学综合平台通用型任意文件上传
下一篇:某网上自助平台通用型漏洞打包
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站