ThinkPHP框架架构上存在SQL注入大屏查看

发布于:2016年07月15日 已被阅读

漏洞概要关注数(363关注此漏洞

缺陷编号: WooYun-2014-86737

漏洞标题: ThinkPHP框架架构上存在SQL注入  

相关厂商: ThinkPHP

漏洞作者: phith0n认证白帽子

提交时间: 2014-12-11 08:58

公开时间: 2015-03-11 09:00

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: php源码分析 白盒测试

106人收藏 收藏

分享漏洞:

1


漏洞详情

披露状态:


2014-12-11: 细节已通知厂商并且等待厂商处理中
2014-12-11: 厂商已经确认,细节仅向厂商公开
2014-12-14: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2015-02-04: 细节向核心白帽子及相关领域专家公开
2015-02-14: 细节向普通白帽子公开
2015-02-24: 细节向实习白帽子公开
2015-03-11: 细节向公众公开

简要描述:

ThinkPHP框架本身缺陷导致SQL注入漏洞,基本影响所有使用ThinkPHP开发的应用,包括thinksns、onethink等,这里以thinkphp自家的OneThink为例。
这个猛料,希望能加精呀~

详细说明:

很多人天真的以为,使用了框架提供的数据库查询方法,不再进行SQL语句拼接,就能完美避免SQL注入。那么你就错了,有时候框架反而成为带你进入陷阱的人。

我们翻开最新版thinkphp框架文档,其中的“表达式查询”章节:http://**.**.**.**/manual_3_2.html#express_query

01.jpg



WTF,如果where语句的条件是数组,而且数组的第一个值是'exp',那么第二个值就可以直接写SQL语句?

WTF,那岂不是一个完美的SQL注入?

可能有些人不明白。我说细一点,很多站长写查询语句会这样:

code 区域
$data = array();
$data['user'] = $_POST['username'];
$data['pass'] = md5($_POST['password']);
M('user')->where($data)->find();



这应该是一个ThinkPHP对数据库查询的基础方法。那么,如果我传入的参数是这样:

username[0]=exp&username[1]=aa'or 1=1%23&password=1,那么,是不是就是一个完美的万能密码?

这个特性在thinkphp3.1、3.2版本中均存在,通用性比较广,危害很大。



有的同学可能觉得有一定局限性,因为thinkphp的I函数中有如下代码:

code 区域
}elseif(isset($input[$name])) { // 取值操作
       $data       =   $input[$name];
       is_array($data) && array_walk_recursive($data,'filter_exp');
       $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); // 参数过滤
               }else{
                   $data   =   filter_var($data,is_int($filter)?$filter:filter_id($filter));
                   if(false === $data) {
                       return   isset($default)?$default:NULL;
                   }
               }
           }
       }
   }else{ // 变量默认值
       $data       =    isset($default)?$default:NULL;
   }



is_array($data) && array_walk_recursive($data,'filter_exp');有个简单的过滤,看看filter_exp函数:

code 区域
function filter_exp(&$value){
   if (in_array(strtolower($value),array('exp','or'))){
       $value .= ' ';
   }
}



exp后面会加个空格。不过有几个很严重的问题:

一、filter_exp在I函数的fiter之前,所以如果开发者这样写I('get.school', '', 'trim'),那么会直接清除掉exp后面的空格,导致过滤无效。而这个写法是很普遍的,包括我自己都经常这样写。

二、thinkphp的MVC架构中,Controller函数的变量也作为GET/POST传参的方式,如http://serverName/index.php/Home/Blog/archive/year/2013/month/11我们即可访问到public function archive($year='2013',$month='01')。而这个URL同样可以写为http://serverName/index.php?c=Blog&a=archive&year=2013&month=11,那么同样可以写为http://serverName/index.php?c=Blog&a=archive&year=2013&month[0]=exp&month[1]=sqli

这是文档里自己的例子:http://**.**.**.**/manual_3_2.html#action_bind,这样传递的参数是不会经过I函数的,所以I函数里的过滤也没有效果。漏洞证明里的OneThink就是因为这个原因被注入的。

三、thinkphp老版本并不是使用I函数获取变量,但exp这个特性确实一直存在的。包括thinksns中,也是直接使用$_POST[xxx]获取的变量值,所以这个安全隐患会一直存在。在Thinksns中我也找到了实例验证。



以OneThink 1.1为例说明吧,详见漏洞证明。

漏洞证明:

OneThink是ThinkPHP自家出的一个内容管理系统,方便开发者进行二次开发。

官网:http://**.**.**.**/, 最新版为1.1。

安装好,直接向后台登录处POST如下数据包即可发现报错(为了方便操作,我注释了验证码检查部分,实际操作中带上验证码发送数据包即可):

02.jpg



爆出数据库用户名:

03.jpg



究其原因,我们看到login处的代码,/Application/Admin/Controller/PublicController.class.php:

code 区域
public function login($username = null, $password = null, $verify = null){
       if(IS_POST){
           /* 检测验证码 TODO: */
           if(!check_verify($verify)){
               //$this->error('验证码输入错误!');
           }

           /* 调用UC登录接口登录 */
           $User = new UserApi;
           $uid = $User->login($username, $password);
           if(0 < $uid){ //UC登录成功
               /* 登录用户 */
               $Member = D('Member');
               if($Member->login($uid)){ //登录用户
                   //TODO:跳转到登录前页面
                   $this->success('登录成功!', U('Index/index'));
               } else {
                   $this->error($Member->getError());
               }

           } else { //登录失败
               switch($uid) {
                   case -1: $error = '用户不存在或被禁用!'; break; //系统级别禁用
                   case -2: $error = '密码错误!'; break;
                   default: $error = '未知错误!'; break; // 0-接口参数错误(调试阶段使用)
               }
               $this->error($error);
           }
       } else {
           if(is_login()){
               $this->redirect('Index/index');
           }else{
               /* 读取数据库中的配置 */
               $config = S('DB_CONFIG_DATA');
               if(!$config){
                   $config = D('Config')->lists();
                   S('DB_CONFIG_DATA',$config);
               }
               C($config); //添加配置
               
               $this->display();
           }
       }
   }



获取了$username和$password后传入login函数,跟进:

code 区域
public function login($username, $password, $type = 1){
       return $this->model->login($username, $password, $type);
   }



又传入$this->model->login函数,跟进:

code 区域
public function login($username, $password, $type = 1){
$map = array();
switch ($type) {
case 1:
$map['username'] = $username;
break;
case 2:
$map['email'] = $username;
break;
case 3:
$map['mobile'] = $username;
break;
case 4:
$map['id'] = $username;
break;
default:
return 0; //参数错误
}

/* 获取用户数据 */
$user = $this->where($map)->find();
if(is_array($user) && $user['status']){
/* 验证用户密码 */
if(think_ucenter_md5($password, UC_AUTH_KEY) === $user['password']){
$this->updateLogin($user['id']); //更新用户登录信息
return $user['id']; //登录成功,返回用户ID
} else {
return -2; //密码错误
}
} else {
return -1; //用户不存在或被禁用
}
}



如我之前说的,直接带入where语句。于是我们只需要让username是一个数组,第一个值为exp,第二个值为注入语句即可。

thinkphp的这个注入与mongodb注入类似,虽然处于框架之中,但因为框架设计不合理导致注入的产生。

修复方案:

不知道。

版权声明:转载请注明来源 phith0n@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:12

确认时间:2014-12-11 17:02

厂商回复:

该漏洞在系统没有对用户数据做严谨判断的情况下会导致SQL注入的产生。目前的github版本TP和OT均已修正~

最新状态:

2014-12-13:感谢作者的反馈,在上一版的基础上改进了修正方法,包括I函数和全局过滤机制。


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值


最新发布
linux下svn提交忽略某些文件... (173)
使用批处理来批量更新、提交SVN... (136)
linux查看目录文件大小命令 (145)
linux tar打包压缩排除某个... (134)
Linux tar压缩和解压 (192)
SVN子命令add用法浅析 (130)
热门博文
网友FBI探案:马蓉iPad惊人发现... (43345)
霍金携手俄罗斯富豪耗资1亿美元寻找外... (4747)
如何才能查看PHP内置函数源代码... (1209)
微信支付开发当前URL未注册的解决方... (573)
《谁为爱情买单》中的经典面试 ... (441)
让虚拟主机也用上SVN:适用于个人的... (395)
精华博文
[推荐]Centos7 安装配置 SVN (159)
easyswoole框架安装 (174)
php开启pecl的支持(推荐) (157)
1-10个恋爱表现:男朋友爱你程度到... (164)
女生喜欢你的10个程度,到第六个就可... (141)
Eclipse 没有Server选项... (211)
友情链接
我来忙 (110)