116 thinkphp 3.1 sql注入
2015-12-12 05:58
1076 查看
http://www.wooyun.org/bugs/wooyun-2010-086737
ThinkPHP框架本身缺陷导致SQL注入漏洞,基本影响所有使用ThinkPHP开发的应用,包括thinksns、onethink等,这里以thinkphp自家的OneThink为例。
这个猛料,希望能加精呀~
很多人天真的以为,使用了框架提供的数据库查询方法,不再进行SQL语句拼接,就能完美避免SQL注入。那么你就错了,有时候框架反而成为带你进入陷阱的人。
我们翻开最新版thinkphp框架文档,其中的“表达式查询”章节:http://document.thinkphp.cn/manual_3_2.html#express_query
WTF,如果where语句的条件是数组,而且数组的第一个值是'exp',那么第二个值就可以直接写SQL语句?
WTF,那岂不是一个完美的SQL注入?
可能有些人不明白。我说细一点,很多站长写查询语句会这样:
code 区域
这应该是一个ThinkPHP对数据库查询的基础方法。那么,如果我传入的参数是这样:
username[0]=exp&username[1]=aa'or 1=1%23&password=1,那么,是不是就是一个完美的万能密码?
这个特性在thinkphp3.1、3.2版本中均存在,通用性比较广,危害很大。
有的同学可能觉得有一定局限性,因为thinkphp的I函数中有如下代码:
code 区域
is_array($data) && array_walk_recursive($data,'filter_exp');有个简单的过滤,看看filter_exp函数:
code 区域
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://document.thinkphp.cn/manual_3_2.html#action_bind,这样传递的参数是不会经过I函数的,所以I函数里的过滤也没有效果。漏洞证明里的OneThink就是因为这个原因被注入的。
三、thinkphp老版本并不是使用I函数获取变量,但exp这个特性确实一直存在的。包括thinksns中,也是直接使用$_POST[xxx]获取的变量值,所以这个安全隐患会一直存在。在Thinksns中我也找到了实例验证。
以OneThink 1.1为例说明吧,详见漏洞证明。
OneThink是ThinkPHP自家出的一个内容管理系统,方便开发者进行二次开发。
官网:http://www.onethink.cn/, 最新版为1.1。
安装好,直接向后台登录处POST如下数据包即可发现报错(为了方便操作,我注释了验证码检查部分,实际操作中带上验证码发送数据包即可):
爆出数据库用户名:
究其原因,我们看到login处的代码,/Application/Admin/Controller/PublicController.class.php:
code 区域
获取了$username和$password后传入login函数,跟进:
code 区域
又传入$this->model->login函数,跟进:
code 区域
如我之前说的,直接带入where语句。于是我们只需要让username是一个数组,第一个值为exp,第二个值为注入语句即可。
thinkphp的这个注入与mongodb注入类似,虽然处于框架之中,但因为框架设计不合理导致注入的产生。
不知道。
简要描述:
ThinkPHP框架本身缺陷导致SQL注入漏洞,基本影响所有使用ThinkPHP开发的应用,包括thinksns、onethink等,这里以thinkphp自家的OneThink为例。这个猛料,希望能加精呀~
详细说明:
很多人天真的以为,使用了框架提供的数据库查询方法,不再进行SQL语句拼接,就能完美避免SQL注入。那么你就错了,有时候框架反而成为带你进入陷阱的人。我们翻开最新版thinkphp框架文档,其中的“表达式查询”章节:http://document.thinkphp.cn/manual_3_2.html#express_query
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://document.thinkphp.cn/manual_3_2.html#action_bind,这样传递的参数是不会经过I函数的,所以I函数里的过滤也没有效果。漏洞证明里的OneThink就是因为这个原因被注入的。
三、thinkphp老版本并不是使用I函数获取变量,但exp这个特性确实一直存在的。包括thinksns中,也是直接使用$_POST[xxx]获取的变量值,所以这个安全隐患会一直存在。在Thinksns中我也找到了实例验证。
以OneThink 1.1为例说明吧,详见漏洞证明。
漏洞证明:
OneThink是ThinkPHP自家出的一个内容管理系统,方便开发者进行二次开发。官网:http://www.onethink.cn/, 最新版为1.1。
安装好,直接向后台登录处POST如下数据包即可发现报错(为了方便操作,我注释了验证码检查部分,实际操作中带上验证码发送数据包即可):
爆出数据库用户名:
究其原因,我们看到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@乌云
相关文章推荐
- php功能 = 》1-1简介-单独的内容
- php + mysql 调用数据库内容 =》 简介
- 【TP5】thinkphp5初体验1
- LAMP 2.9 php扩展模块如何安装
- 配置php的curl模块问题
- Yii2隐藏frontend/web和backend/web的方法
- PHP底层的运行机制与原理
- PHP-什么是PHP?为什么用PHP?有谁在用PHP?
- 升级到 PHP 7.0
- 【转】ThinkPHP命令行工具Tptool2.0使用教程
- 21天学会PHP【第1天】
- php--关于函数(1)的基本形式
- LAMP 2.8 php.ini配置文件详解
- Windows 7资源管理器打开FTP出错情况总结
- php中文汉字正则验证
- 三、Ubuntu下编译安装PHP5.6.16
- thinkphp使用阿里大鱼短信接口
- #PHP行注释
- Php Fatal error: Allowed memory size of 33554432 bytes exhausted 的解决办法
- 解决 PHPExcel 长数字串显示为科学计数