Skip to content

Latest commit

 

History

History
76 lines (45 loc) · 3.67 KB

ThinkPHP5之任意方法调用RCE(六).md

File metadata and controls

76 lines (45 loc) · 3.67 KB
  1. 利用method的任意方法调用,调用构造函数__construct,且调用时会传入$_POST数据,那么组合起来就是执行method方法可以控制类的成员变量的值。

  2. filterValue中存在 call_user_func函数可以恶意利用,关注其参数$filters是否可控。

  3. getFilter方法存在这样$filter = $filter ?: $this->filter;一条语句获取类成员变量filter(由1知可控)。

  4. input方法中存在:先调用getFilter获取到filter,再使用array_walk_recursive递归调用filterValue并传入filter的情况。

综上,如果找到一处先调用method再调用input的调用点,就能够利用call_user_func造成RCE。


method方法调用链:

(调用时必须要$this->method为空或false)

程序启动时:

  • run()::thinkphp\library\think\App.php#116->routeCheck
  • routeCheck::thinkphp\library\think\App.php#639->check
  • check::thinkphp\library\think\Route.php#848->$request->method()

在开启debug模式时,这条method调用链会在调用任意方法后,执行$request->param()方法,而这个方法里就有调用input()。然后就有:

  • 调用__construct,需传入两个参数filter[]=system(filter=system也行)和xxx=whoami即可

image-20220416131554673

  • 不调用__construct,直接调用filter方法直接设置过滤器

image-20220415174257578

注意第二种payload必须要按照上面的顺序,不然filterValue会进入我们不期望的流程,==直接break了。==

为什么第一种payload不需要考虑传参顺序呢?

因为两种payload注册过滤器的方法不一样:

  1. 构造函数注册过滤器使用类属性覆盖,在传入的参数中只有类中之前声明过的filter会被覆盖掉(因为有property_exists($this, $name))。
  2. 而filter方法注册过滤器的时候是直接将传入的$_POST赋值给$this->filter导致filterValue遍历取过滤器名的时候,不仅仅是可以通过is_callable判断的system,比如上图例子中的filter传入后会经过is_callable('filter'),结果为false。

后门技巧

  1. 允许'app_debug' => false

    修改application\config.php#default_filter

    设置默认过滤器 system,直接请求,参数名xx=whoami即可执行命令(业务影响未知,大概率影响)

  2. 5.x的RCE漏洞修复较简单,只是用if校验了一下方法白名单。直接删掉不影响业务。

  3. 手写一个有漏洞的控制器代码。可以利用TP自带的很多处回调函数比如Request::filterValue中的,怎么写可以参考P牛老文。也是不影响业务。

  4. ...


不开debug也能调用到input方法。

  • 路由报错ThinkPHP V5.0.22 改进Log类支持json日志格式,添加了parseLog方法。该方法的处理过程会调用到filterValue。(需要构造错误,例如在route.php中不use think\Route 直接写路由规则)
调用堆栈:
think\Request->filterValue
think\Request->input
think\Request->server
think\Request->host
think\log\driver\File->parseLog 
think\log\driver\File->write 
think\log\driver\File->save 
think\Log::save
think\Error::appShutdown