环境搭建
第一次用composer安装源码,记录一下配置过程:
用brew安装composer:brew install composer
更新一下composer:composer self-update
安装国内镜像源:composer config -g repo.packagist composer https://packagist.laravel-china.org
安装漏洞环境,thinkphp 5.0.15:composer create-project --prefer-dist topthink/think=5.0.15 tp5.0.15
将 composer.json 文件的 require 字段设置成如下:1
2
3
4"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.15"
}
将数据库文件,报错信息开启后,在控制器中index.php写:1
2
3
4
5
6
7
8
9
10
11
12<?php
namespace app\index\controller;
class Index
{
public function index()
{
$username = request()->get('username/a');
db('users')->insert(['username' => $username]);
return 'Update success';
}
}
paylaod:1
https://tp5.0.15/?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
分析
从demo代码入手:1
2
3$username = request()->get('username/a'); // 强制类型转换成数组
db('users')->insert(['username' => $username]);
return 'Update success';
对其动态调试过程中,request()->get()
,在thinkphp/library/think/Request.php 1093行对其数组的每个参数过滤:1
2
3
4
5
6
7
8public function filterExp(&$value)
{
// 过滤查询特殊字符
if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i', $value)) {
$value .= ' ';
}
// TODO 其他安全过滤
}
显然这个过滤规则是形同虚设的。
再来跟入: db('users')->insert(['username' => $username]);
从上面payload可以发现数组的第一个值为inc
, 是tp5新增的链式查询操作:https://www.kancloud.cn/manual/thinkphp5/135178
,Auto-increment
即自动创建主键字段值。
thinkphp/library/think/db/Builder.php 726行insert 数据处理后进行拼接1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public function insert(array $data, $options = [], $replace = false)
{
// 分析并处理数据
$data = $this->parseData($data, $options);
if (empty($data)) {
return 0;
}
$fields = array_keys($data);
$values = array_values($data);
$sql = str_replace(
['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'],
[
$replace ? 'REPLACE' : 'INSERT',
$this->parseTable($options['table'], $options),
implode(' , ', $fields),
implode(' , ', $values),
$this->parseComment($options['comment']),
], $this->insertSql);
return $sql;
}
thinkphp/library/think/db/Builder.php 118行选择了 inc模式 这里只是对数据进行清洗并没有对其进行过滤
1 | switch ($val[0]) { |
数据清洗完后,value值被拼接进去:
接着在对其一番预处理参数绑定后再进行查库,thinkphp/library/think/db/Connection.php 456行:
同样的,从thinkphp/library/think/db/Builder.php
可以知道,对Inc
模式和对dec
处理模式一样,所以同样也存在这个问题:
修复过程,将composer.json
版本修改成5.0.17
,执行composer update
:
1 | switch ($val[0]) { |
可以看到改为拼接数组的键了:
看到这里,其实有几个疑问的:
- 为什么使用框架提供的参数方法过滤这么简单
- 将值换成键拼接还能注入吗?
对于第一个问题,因为自己对thinkphp开发流程不熟悉原因,查阅文档之后发现:
显然,就像上文分析的那样,默认情况下过滤就只有那些简单的关键字。
对于第二个问题,之前以为是客户端可控的传入变量的键,后来调试的时候发现是:
最后
总结一下利用条件:
- 过滤规则为默认,即无过滤
- 获取参数为array类型, 传入的三个参数,第一个为查询模式inc/dec,第二个未过滤为payload,第三个会进行
floatval()
类型转换,功能为递增的step - 最后就是存在一个可控的insert操作了
- 开启报错才能报错注入
补更
后来继续跟了一下tp5其他的注入,感觉都很鸡肋,甚至觉得不是洞..
利用思路都大同小异,传入数据默认不过滤,底层解析数据的也不过滤,最后进行一次字符串拼接。
参考: