0x00 前言
Thinkphp5.x insert位置的注入。
0x01 漏洞简述
跟踪TP insert 方法的调用,发现传入的数据到 Builder 类的 parseData 方法中,并未对插入数据进行过滤。通过构造恶意SQL语句即可造成注入。
影响版本:
5.0.13<=ThinkPHP<=5.0.15 、 5.1.0<=ThinkPHP<=5.1.5
0x02 环境搭建
mbp
phpstorm+xdebug
ubuntu+lnmp 192.168.207.6
项目代码拉取:
1 | composer create-project --prefer-dist topthink/think=5.0.15 tp |
将 composer.json 文件的 require 字段设置成如下:
1 | "require": { |
然后执行 composer update
,并将 application/index/controller/Index.php 文件代码设置如下:
1 |
|
在 application/database.php 文件中配置数据库相关信息,并开启 application/config.php 中的 app_debug 和 app_trace 。创建数据库信息如下:
1 | create database test_is; |
然后我们去访问http://192.168.207.6/index.php?username=1 即可。
0x03 分析
这里使用了phpstorm+xdebug来辅助分析。
我们直接从入口开始跟方法。
首先在当前项目index控制器下跟进insert方法。
通过command + b 跟进insert方法。
来到**/thinkphp/library/think/db/Query.php中的第2079行**insert方法。
这里可以看到通过数组形式传入$data。通过这里可以尝试打几个断点直接来跟一下数据插入过程。
可以看到第2085行是生成sql语句,这里直接在**/thinkphp/library/think/db/Query.php的第2085行**打一个断点,跟一下数据传输过程有没有发生变化。
我们访问的url是:
1 | http://192.168.207.6/?XDEBUG_SESSION_START=11955&username=1 |
之后我们来到2085行,直接F8,来到了**/thinkphp/library/think/db/Builder.php的insert方法,通过前面可以看到$this->builder对象调用insert,也就是/thinkphp/library/think/db/Builder.php**中的insert方法。
这里可以再打一个断点,在第721行,跟进处理数据的方法parseData。
$result是一个定义为空的数组,最后返回的结果也是$result。
这里可以单步一步一步跟到parseData方法第114行,可以看到如果$val[0]不是下列case中的任意的值,$result是会返回为空的。
那么$result为0的话,到生成$sql也为0,其实是没有意义的。
这里如果想构造sql 注入,需要传入一个数组。
我们继续来看**/thinkphp/library/think/db/Builder.php中的parseData方法,再次debug来到第114行**。
为什么要传一个数组?
如果传入的是一个数组,形如:
1 | http://192.168.207.6/?XDEBUG_SESSION_START=11955&username[0]=exp&username[1]=123 |
那么会进入到这个elseif分支,通过判断val[0]进入到不同的case分支,这里拿$val[0]=exp 来举例子,按道理来讲会进入,exp
分支,$val[1]=123被赋给$result,最终执行字符串语句。
但是在这里并没有进入case分支,可以细节的发现,exp后面多了一个空格。而是进入了下面这个elseif分支。这里可以看到进行了参数绑定,结果肯定不能造成sql注入。
那么我们可以跟一下exp为什么会多了一个空格。
exp在request中的处理
这里重新打了一个断点在get这里(感觉是数据被做了处理,要不然不可能平白无故多个空格)
继续传入参数:
1 | http://192.168.207.6/?XDEBUG_SESSION_START=19607&username[0]=exp&username[1]=123123 |
单步调试跟踪到数组value被一个filterExp方法进行了处理。
可以看到代码,很明显exp字符在请求中被加了一个空格,所以不会进入case ‘exp’分支。
1 | /** |
如果想进行正常的非参数绑定拼接sql语句,那么就可以进到inc、dec分支,这里可以看到filterExp方法中没有对这两个进行处理。
当我们继续传入数组数据作为username参数值,这里拿inc作为数组第一个元素举例。
请求:
1 | http://192.168.207.6/?XDEBUG_SESSION_START=19607&username[0]=inc&username[1]=123123 |
单步调试跟进到parseKey方法,具体位置在**/thinkphp/library/think/db/builder/Mysql.php**
1 | /** |
这个方法也只是处理数据,去空格这些的,在114行,return 打断点可以看到数据被原样处理。
到此数据通过数组传入的方式没有被处理,那么我们就可以结束整个parseData方法继续分析下面的,还是在**/thinkphp/library/think/db/Builder.php**的insert方法。
data数据被取出在733行734行被拼接,直接造成sql注入。
0x04 验证
请求1:
1 | http://192.168.207.6/?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1 |
请求2:
1 | http://192.168.207.6/?username[0]=dec&username[1]=updatexml(1,concat(0x7,database(),0x7e),1)&username[2]=1 |
成功。
0x05 总结
这里打断点太多太乱了,其实不嫌麻烦的话可以单步调试一步一步跟,这样还能多少体会到tp框架的一些设计思路,总的来说复习了下tp的审计,自我感觉还行,就是分几天来看断点打的有点多。