环境搭建
第一次用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 | "require": { |
将数据库文件,报错信息开启后,在控制器中index.php写:
1 | <?php |
paylaod:
1 | https://tp5.0.15/?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1 |
分析
从demo代码入手:
1 | $username = request()->get('username/a'); // 强制类型转换成数组 |
对其动态调试过程中,request()->get()
,在thinkphp/library/think/Request.php 1093行对其数组的每个参数过滤:
1 | public function filterExp(&$value) |
显然这个过滤规则是形同虚设的。
再来跟入: 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 | public function insert(array $data, $options = [], $replace = false) |
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其他的注入,感觉都很鸡肋,甚至觉得不是洞..
利用思路都大同小异,传入数据默认不过滤,底层解析数据的也不过滤,最后进行一次字符串拼接。
参考:
v1.5.2