ThinkPHP 5.x RCE漏洞分析

路由控制不严谨导致的rce(5.0.23-5.1.31)

Posted by SEVENTEEN on May 23, 2021

前言

   继续审计一下TP框架rce,加深一下对框架运作的了解,以及POC的原理。

配置环境

   使用composer create-project topthink/think thinkphp5.1.24 5.1.24之后总会自动update为5.1的最新版本, 手动修改一下版本进行更新。

   这里将lock文件中到thinkphp版本改成5.1.24。

   这里也改成5.1.24,然后执行composer update,使版本更新恢复到5.1.24。

利用条件

   5.0.23 <= 5.x <= 5.1.31

利用链1

   利用点位于 thinkphp/library/think/Container.php::invokeFunction(), 这个利用链其实需要利用一个反射的点去实例化url参数中的对象并调用该方法。

   接下来需要构造实例化这个类的路由,先从入口文件开始分析。

   跟进App::run(),进入routeCheck()方法检查路由。

   随后进入$this->request->path()获取pathinfo,赋值给path变量。

   可以看到从$this->pathinfo()获取$pathinfo的值, thinkphp有四种路由解析方式,$this->pathinfo()其实就是去配置文件中找到相应的解析方式,这里获取的是传给s的参数。

   返回后带着上面获取到的$path参数进入$this->route->check($path, $must)进行路由检测。

   跟进实例化后的UrlDispatch对象查看路由解析,注意看这里传入check()方法中的$url参数传给了UrlDispatch的类, 即刚刚传入的$path参数。

   可以看到UrlDispatch其实是Url类。

   而Url类是继承于Dispatch类的。

   $path的值赋给Dispatch类的$dispatch。

   再来到Url类,调用了$this->parseUrl($this->dispatch)赋值给$result.

   在parseUrl()方法中返回分割后的路由,这里其实就是将传入的s参数进行路由解析前的分割。

   接着,实例化Module类调用init()方法,然后init()方法返回一个实例化对象。

   回到刚开始的App::run()继续往下走,走到return is_null($data) ? $dispatch->run() : $data, 因为$data为null,所以会进入$dispatch->run(),而这个$dispatch已经在前面赋值为实例化后的Module类。

   在进入Module类之前,先进入它的抽象父类Dispatch,在run()方法中调用Module::exec()方法。

   进入Module::exec()方法后,在此处进入App::controller()方法。

   从注释中也可以得到此处将实例化类的信息,这里先调用了$this->parseModuleAndClass($name, $layer, $appendSuffix)。

   $this->parseModuleAndClass()方法会返回模块跟类名,返回后会判断是否存在回调, 再用__get()跟make()方法进行实例化。

   返回Module::exec()方法,看99行跟113行注释也可以得出下半部分获取了操作名跟请求变量,即要实例化的类的方法以及绑定参数。 接着,再调用$this->app->invokeReflectMethod()方法进行反射。即能够实例化任意对象跟调用其方法,并且参数可控。

   反射调用Container::invokeFunction()方法,并且传入绑定的参数,来到触发点进行命令执行。

POC-1

GET: s=index/think\App/invokeFunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls

利用链2

   既然这里通过路由可以实例化类跟调用方法,又可以控制参数。那么只要找到其它触发点即可构成POP链。 回到TP5.0.x的rce链,找到thinkphp/library/think/Request.php::input()。 这里实例化input()方法,再控制其参数data数组和filter。

   带着控制的参数data数组和filter进入filterValue()方法后,触发call_user_func($filter, $value)命令执行。

POC-2

GET: s=index/\think\request/input?data[]=ls&filter=system

利用链3

   在thinkphp/library/think/template/driver/File.php::write()也有一个file_put_contents()可以用来写shell。 也是通过路由实例化类跟调用方法,并且控制参数。

POC-3

GET: s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php+phpinfo();?>

There Is Nothing Below

   

Turn at the next intersection.