欢迎光临散文网 会员登陆 & 注册

一文速通thinkphp3.2.3代码审计

2023-02-16 14:21 作者:J_Chanra  | 我要投稿

ThinkPHP是一个快速、简单的基于MVC和面向对象的轻量级PHP开发框架,遵循Apache2开源协议发布,从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,尤其注重开发体验和易用性,并且拥有众多的原创功能和特性,为WEB应用开发提供了强有力的支持。

3.2版本则在原来的基础上进行一些架构的调整,引入了命名空间支持和模块化的完善,为大型应用和模块化开发提供了更多的便利。

详细见:https://www.kancloud.cn/manual/thinkphp/1679

目录结构

其中框架目录Thinkphp结构如下:

开始使用

整个网站的入口文件就是www/index.php,index.php包含了框架的入口文件,所以访问后可以直接加载thinkphp框架

访问成功运行,框架会自动在www/Application/Home/Controller/目录下生成IndexController.class.php,这里修改内容如下:

再次访问

配置文件

thinkphp的配置文件在www/ThinkPHP/Conf/convention.php

URL模式

入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作:

这是3.2版本的标准URL格式。

url默认是大小写敏感的,也可以通过修改convertion.php,达到url不区分大小写的目的

在访问入口文件时,如果没有指定模块、控制器、方法,默认会访问HOME模块下面的Index控制器的index方法,所以下面访问是等效的,这也是为什么修改了index方法后可以直接体现出来

这种URL模式就是系统默认的PATHINFO模式,不同的URL模式获取模块和操作的方法不同,ThinkPHP支持的URL模式有四种:普通模式、PATHINFO、REWRITE和兼容模式,可以在convertion.php设置URL_MODEL参数改变URL模式。


实战练习,ctfshow569

提示flag在Admin模块,Login控制器,ctfshowLogin方法。前面看的配置文件可知默认url模式为pathinfo,所以构造url:

路由

要使用路由功能,前提是你的URL支持PATH_INFO(或者兼容URL模式也可以,采用普通URL模式的情况下不支持路由功能),并且在应用(或者模块)配置文件中开启路由:

然后就是配置路由规则了,在模块的配置文件中使用URL_ROUTE_RULES参数进行配置,配置格式是一个数组,每个元素都代表一个路由规则,例如:

规则路由

规则路由是一种比较容易理解的路由定义方式,采用ThinkPHP设计的规则表达式来定义。

实战ctfshow570

下载好源码,看到common内的全局路由配置如下:

因为url里不方便传斜杠,使用post传命令,访问url:

Thinkphp的渲染机制

Thinkphp的控制器中有show函数,可以将文本渲染成html的网页

如果渲染时传入的变量未经过滤,则会导致代码执行,跟进show函数,调用了display函数

继续跟进,代码在fetch函数中,完成了解析渲染

fetch函数中重点关注输出缓冲区中的代码

如果引擎类型是php,则使用eval渲染,会直接执行代码。但是thinkphp的默认渲染引擎是Think,所以重点关注listen函数,listen函数中,exec存在输出的可能居高

跟进exec发现是一个类的动态创建

模板的代码传递给了这个类,跟进这个类

run内没有其他输出的地方,而且模板内容和模板的缓存位置被传递进load,所以跟进load函数,有call_user_func_array,可以动态的调用系统函数,这里调用的就是load函数

load里有include包含了缓存代码,之前传入的php就是在这里执行,然后包含结果存入输出缓冲区中

后面被读取到后,返回给了浏览器。

实战ctfshow571

题目拿到文件目录,发现Index控制器存在无过滤参数:

读取flag

信息泄露

1、ThinkPHP在开启DEBUG的情况下会在Runtime目录下生成日志,而且debug很多网站都没有关
2、ThinkPHP默认安装后,也会在Runtime目录下生成日志

THINKPHP3.2 结构:Application\Runtime\Logs\Home\16_09_09.log
THINKPHP3.1结构:Runtime\Logs\Home\16_09_09.log
日志存储结构是 :项目名\Runtime\Logs\Home\年份_月份_日期.log

参考链接:https://juejin.cn/post/7028872177424269343

实战ctfshow web572


burp抓包爆破日志文件

发现后门

SQL注入

1.使用find查询

在执行用户传入的参数前,会先执行

获取列名和类型,然后再生成用户的sql语句。实例化M的时候,查询列的语句不可控,这里重点看生成用户sql语句的具体流程:

获取了GET提交的id参数,然后传进find函数

这里查询的列是id,实例化M实际上是创建一个Model类,pk的值在Model类里已经预定义为id,定义位置在/tp3.2.3/ThinkPHP/Library/Think/Model.class.php

thinkphp把碎片化的参数组合成一个sql语句大体原理,其存在语句模板,执行查询操作就使用查询模板。获取全部参数后,再替换模板原本的字段。

获取参数的最后一步是解析参数,即分析用户提交的参数。具体的做法是把用户提交的参数传入_parseOptions函数:

在_parseOptions函数里会获取用户查询的目标表、列的信息,后面就会看到为什么获取这些信息了:


在这里进入了_parseType函数,解析用户提交参数的类型

这里依据之前获得的表、列信息,对参数类型进行转换,如果数据库中的id列是int类型,则进行intval转换,float则floatval

所以如果数据库中的id是int类型(mysql的id列默认是int(3)),在这里payload会被转换成纯数字,这里我把数据库id类型改成了varchar(255),后面没有什么操作,返回了参数

再传进select函数,生成sql语句:

buildSelectSql函数生成了sql语句,query函数执行了语句,读取结果。先进buildSelectSql函数

没有操作,进parseSql函数看看:

这里就是之前提到的模板替换,如果不是要查询的参数,就替换成空,这里id是在where后面,所以进入parseWhere函数

在判断完参数名后,进入parseWhereItem函数检查参数值,然后进入了parseValue函数

进入parseValue后,进入了escapeString函数,在这里执行了addslashes函数,所以直接单引号闭合是不行的。

网上addslashes绕过方法没成功,所以想办法让程序跳过过滤。在最开始看到如果传入的参数是数组,就会直接跳过获取列名,列值这一步:

这样就可以操控parseSql内替换的值

原本where的是一个键值对的数组,我们令他是一个字符串,这样进入parseWhere函数后会跳过过滤

这样就直接在where后面拼接了值,所以构造payload思路就来了:

实战ctfshow 573

题目源码:

https://blog.csdn.net/miuzzx/article/details/119424071

payload:

2.使用where查询(传入字符串)

生成的sql语句是:

直接闭合即可造成sql注入

实战ctfshow web574

3.反序列化注入

thinkphp3.2.3反序列化用户参数的时候,会导致sql注入。

参考:https://www.freebuf.com/articles/web/329045.html

寻找可以直接触发的魔术方法,首选__destruct,因为触发门槛最低,看网上大佬做法是全局搜索所有析构函数,再逐个去看,这里搜索到了12个:

但是大部分参数直接传入了ftp_close,fclose这类函数,或者调用了一个不可控参数的方法,所以都不去看,只剩下了一个/tp3.2.3/ThinkPHP/Library/Think/Image/Driver/Imagick.class.php,里面调用了destory

但是找不到这个destory的来源,只能全局搜索

调用destroy方法的时候没有传参,所以这个delete语句注入不了,查看其他方法

在/tp3.2.3/ThinkPHP/Library/Think/Session/Driver/Memcache.class.php调用了delete方法

同样是找不到来源,继续全局搜索delete,存在8个结果

这里排除掉2个抽象函数,剩下的6个类文件,其中4个是继承了Driver类和Model类,Driver类是一个抽象类,所以也跳过,先看Model类

1.变量pk是预定义的列名,这里可以操控。

2.由于调用destory方法的时候没有参数,变量options是字符串类型。

3.这里假设payload里没有逗号,这样就直接创建了where的sql条件数组(列名=>列值),列名和列值都可控。

4._parseOptions上面sql注入提到过,会对比列的类型,由于列名和列值可控,先不考虑。

5.最后调用了delete方法,这个delete方法执行了sql语句,最后返回了结果,delete后面调试定位。

动态调试一下,poc:

控制器:

跟进去看运行时的变量值,在每个类调用处打上断点:

前面推测的没有问题,重点看下面delete的方法做了什么

抛出异常了,因为db未定义,想办法构造出一个db变量,db变量修改的位置

继续找_db变量修改的位置

这里实例化了数据库连接

config变量如下:

配置传入了一个动态类

所以db变量实际上就是创建的Mysql对象,Mysql类继承了Driver类,所以delete方法是来自Driver类,把这个Mysql类加入poc。

成功执行

现在想办法sql注入,刚才的流程里产生的参数会经过addslashes转换,所以不方便注入。回到Model的delete方法,看到开头实际上有一个分支可以令options是一个数组

我们令pk变量的值和data数组的键名相同,就可以通过data传入payload,根据上面find函数的sql注入我们知道,只要传入的数组满足以下关系,就可以绕过大量检查

所以修改一下我们的poc,第一次传入delete方法的options直接为空,同时Model类里的options也为空,直接进入条件分支

重新运行一下

再次运行到这里执行删除操作的时候,我们已经可以控制表名和条件

ctfshow 575题目试一下

题目部分源码

这里可以用show方法的代码执行打,但是这里直接用反序列化打,poc如下,在当前目录写入chanra.txt

把payload复制到cookie,运行:

写入成功

尝试写shell

访问成功

4.comment注入

控制器代码如下:

这个方法没有过滤,可以直接执行payload。简单追踪下

在comment打上断点,跟进去

直接赋值给了options,返回this,之后继续执行find方法,find方法之前看到过,后面会构造一个sql语句,跟进去看看:

用options中的comment值替换原本的%comment%字段,跟进去看看有没有过滤

没有过滤,直接拼接了

实战ctfshow web576

题目源码

发送payload

访问webshell

5.使用where查询(传入数组)

之前传入where字符串时,没有经过过滤就被数据库执行了,这次看下传入数组会怎样,先看看where方法做了什么

源码:

打上断点运行

直接在options的where键创建了一个数组,在find方法里我们讲到过,如果where的值是数组,那么会有addslashes转义的,进行跟进看看怎么绕过。中间过程和find方法重复,就略过了,直接到构造sql语句的方法来看

跟进

这里面的条件分支我们都不可控,只能再去其他函数看

parseKey是用反引号包裹列名的,没想出怎么利用,略过

正常情况下参数在这里是字符串,直接addslashes转义后就返回了,但是这里条件分支可以控制,控制了exp变量,可以做的事情就多了

这里令id的值是一个数组

成功进入这个分支

又有一个分支

最后还是逃不出addslashes的命运,当时我想着这不是没漏洞了,后来看了别人文章才反应过来,没有引号的paylaod不就可以执行了。。。。

明显这个分支更容易满足,构造一个二维数组传进去

传进去之后就绕过了单引号包裹,且payload里没有引号

所以解析后没有引号包裹

成功爆出数据

上题目 ctfshow web577

当成数字型注入

其他漏洞

6.php原生引擎下,assign方法变量覆盖导致的RCE

默认情况下tp框架使用的是THINK引擎渲染网页,但是开发者可以手动设置/tp3.2.3/ThinkPHP/Conf/convention.php的TMPL_ENGINE_TYPE值为PHP,来使用原生引擎。

漏洞代码:

传入assign的如果是数组,则会创建一个数组array($name[key]=>$name[value])。

传入assign的如果是字符串,则会创建一个数组array($name=>$from)。

漏洞代码

display方法是渲染用的,没有参数就渲染自身名称的html文件模板,我这里已经创建了/tp3.2.3/Application/Admin/View/Login/index.html文件,内容是123。

我们跟进几个方法后就会看到渲染位置

关于extract函数的说明

把数组里的键值对提取成变量,flags为EXTR_OVERWRITE时,会覆盖已有变量。

修改url

再跟踪到这里,看到已经覆盖了变量

poc有两种写法

上题目 ctfshow 578

篇幅过长,超出上限,发不出图片了,大家自己试下。。。。


一文速通thinkphp3.2.3代码审计的评论 (共 条)

分享到微博请遵守国家法律