Thinkphp <3.2.4SQL注入 分析复现

    科技2025-07-26  15

    文章目录

    闲扯安装复现分析踩坑开始

    闲扯

    准备把Thinkphp几个大版本的漏洞都分析复现一下,发现除了之前复现的一个rce外,3.x版本的大多都是SQL注入,随便选一个看起来有意思一点的。

    安装

    下载链接: (3.2.2版本) http://www.thinkphp.cn/down/543/.html (3.2.3版本) http://www.thinkphp.cn/extend/610.html

    复现

    Application\Home\Controller\IndexController.class.php (搭建完成后,需要先访问一下,才会出现home目录) (users是pikachu数据库里的一个表,因为我配置的是pikachu的数据库) 访问一下index2,发现不能连接数据库: ThinkPHP\Conf\convention.php 55行 配置一下

    我这里直接使用了pikachu的数据库,但是继续访问,发现不支持mysql???(3.2.3版本可以直接mysql…什么鬼) 然后我在官网找到解答: ok,现在可以了

    分析

    (网上的payload我都没有打成功,网上都是3.2.3,我尝试的是3.2.2) 迷惑的我。

    搭建3.23尝试,成功: payload:index.php?m=Home&c=Index&a=index2&id[where]=1 and updatexml(1,concat(0x7e,user(),0x7e),1)--+ 那就先分析3.2.3吧,看懂了再去分析3.2.2的不同点。

    先把数据流写出来:(多余的都删了)

    //接收参数I() ThinkPHP\Common\functions.php 370行 //实体编码array_map_recursive() ThinkPHP\Common\functions.php 405行 //过滤函数think_filter() ThinkPHP\Common\functions.php 1542行 //实例化类M() M()函数跳过 //查询函数find() ThinkPHP\Library\Think\Model.class.php 720行 //解析参数_parseOptions() ThinkPHP\Library\Think\Model.class.php 627行 //空的不管_options_filter() //回到find()进入select() ThinkPHP\Library\Think\Db\Driver.class.php 942行 //建立sql语句buildSelectSql() ThinkPHP\Library\Think\Db\Driver.class.php 956行 //拼接sql语句parseSql() ThinkPHP\Library\Think\Db\Driver.class.php 975行 //拼接where语句parseWhere() ThinkPHP\Library\Think\Db\Driver.class.php 489行 //回到select()进入query()查询 ThinkPHP\Library\Think\Db\Driver.class.php 139行 //执行报错,进入error() ThinkPHP\Library\Think\Db\Driver\Mysqli.class.php 309行 //用errorInfo()函数,接收到了报错的查询结果

    对I()函数跟踪一下,一直往下,到第一个过滤点看了下,这里存在htmlspecialchars,但是对单引号和数字型没影响: ThinkPHP\Common\functions.php 370行 I() 发现双引号和 > 都被实体编码了: 具体是在ThinkPHP\Common\functions.php的405 行 array_map_recursive()函数里调用了htmlspecialchars: 然后到了第二个过滤点: ThinkPHP\Common\functions.php 1542行 think_filter()函数

    可以看到,过滤的东西非常少= =

    I函数部分这就已经跟完了。 M函数没必要跟了,跟过去看看find函数: ThinkPHP\Library\Think\Model.class.php 720 find() 因为options不是数组,直接往下,进入_parseOptions函数: ThinkPHP\Library\Think\Model.class.php 627 _parseOptions()

    在这个里面又继续进入_options_filter()函数,就在它下面= =是空函数.略略略 然后回到find函数里面的select函数: ThinkPHP\Library\Think\Db\Driver.class.php 942 select() 然后进入buildSelectSql()函数,一听就是拼接数据库语句的: ThinkPHP\Library\Think\Db\Driver.class.php 956 buildSelectSql() 然后进入了parseSql()函数里面,好吧,这才是拼接的地方: ThinkPHP\Library\Think\Db\Driver.class.php 975 parseSql() 这里就是用替换的方式,把sql语句一句一句拼接起来…因为我们的payload是where的,所以重点跟进parseWhere()函数咯: ThinkPHP\Library\Think\Db\Driver.class.php 489 parseWhere() 略略略,因为是字符串,所以直接进入了第一个if,下面一大串都没用了,然后直接返回: 然后回到之前的select()函数,随之进入query()函数: ThinkPHP\Library\Think\Db\Driver.class.php 139 query() 然后在223行执行的时候就报错进入error()函数了: 跟踪来到error函数,用errorInfo()函数,接收到了报错的查询结果: ThinkPHP\Library\Think\Db\Driver\Mysqli.class.php 309 error() 理清思路,再来一次数据流图:

    //接收参数I() ThinkPHP\Common\functions.php 370行 //实体编码array_map_recursive() ThinkPHP\Common\functions.php 405行 //过滤函数think_filter() ThinkPHP\Common\functions.php 1542行 //实例化类M() M()函数跳过 //查询函数find() ThinkPHP\Library\Think\Model.class.php 720行 //解析参数_parseOptions() ThinkPHP\Library\Think\Model.class.php 627行 //空的不管_options_filter() //回到find()进入select() ThinkPHP\Library\Think\Db\Driver.class.php 942行 //建立sql语句buildSelectSql() ThinkPHP\Library\Think\Db\Driver.class.php 956行 //拼接sql语句parseSql() ThinkPHP\Library\Think\Db\Driver.class.php 975行 //拼接where语句parseWhere() ThinkPHP\Library\Think\Db\Driver.class.php 489行 //回到select()进入query()查询 ThinkPHP\Library\Think\Db\Driver.class.php 139行 //执行报错,进入error() ThinkPHP\Library\Think\Db\Driver\Mysqli.class.php 309行 //用errorInfo()函数,接收到了报错的查询结果

    从头到尾可以看到,对我们的payload没有进行什么严格的过滤,在拼接的时候,有很多部位,不止where一处可以拼接,再去看几个常见可控的点: (其实这里并不是重点,因为过滤都是在前面部分完成的) parseGroup()函数直接返回: parseOrder()函数如果不是数组也是直接返回: parseLimit()也是:

    踩坑开始

    再去看看3.2.4是怎么修复的: (PS:本来是想在github看commit的,奈何记录实在太多了…)

    先去看了下3.2.4版本的think_filter,只多了一个bind… parseWhere也没变化: 再去看find函数: 首先在第一个if里,把 $options = array(); 去掉了

    可以看到,这一块也变了,加上了$this: 我们学着它的变化改一下find函数: 为了方便,我把查询结果输出了,判断sql语句是否正常执行了: 现在不报错了,而且还能正常执行了。

    懂了,之前 的$options是我们传进来的,现在他不让_parseOptions()对我们传入的payload解析了,我们的payload直接不能进入拼接语句了。我们在_parseOptions()前后输出一下$options看看: 哦吼?

    原因知道了,再去看看我的3.2.2为啥子不行: 比较了一下find函数:

    发现就少了一段根据复合主键查找记录啊: 我们在3.2.3里面,把这一段注释掉,发现还是可以正常爆数据:

    重新搭建一下3.2.2看看,记得改调试文件的配置:

    我吐了,从头到尾,payload并没有被删减或者其他的,但是在执行的时候,error函数里面就是不报错??? 那好吧,我再去比较一下error()函数的区别: 3.2.2的: 3.2.3 的: 我直接傻掉好吧,打印了一下,发现的确还是报错了,只是没有输出回显而已,我还搞了这么久… 发现3.2.3是在trace()里面拼接了print_r,具体不细跟了= =原因差不多懂了: 这里只是没有回显,我们使用盲注也是可以的…

    感觉这个漏洞,跟parseWhere这些函数的关系不大,主要还是应该增强 think_filter()函数,使用的时候也可以加上强制转换。

    Processed: 0.010, SQL: 8