学习笔记-ThinkPHP5之任意方法调用RCE(六)
书接上文
- 利用method的任意方法调用,调用构造函数__construct,且调用时会传入$_POST数据,那么组合起来就是执行method方法可以控制类的成员变量的值。
- filterValue中存在 call_user_func函数可以恶意利用,关注其参数$filters是否可控。
- getFilter方法存在这样$filter = $filter ?: $this->filter;一条语句获取类成员变量filter(由1知可控)。
- input方法中存在:先调用getFilter获取到filter,再使用array_walk_recursive递归调用filterValue并传入filter的情况。
综上,如果找到一处先调用method再调用input的调用点,就能够利用call_user_func造成RCE。
method方法调用链:
(调用时必须要$this->method为空或false)
程序启动时:
- run()::thinkphp.php#116->routeCheck
- routeCheck::thinkphp.php#639->check
- check::thinkphp.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注册过滤器的方法不一样:
- 构造函数注册过滤器使用类属性覆盖,在传入的参数中只有类中之前声明过的filter会被覆盖掉(因为有property_exists($this, $name))。
- 而filter方法注册过滤器的时候是直接将传入的$_POST赋值给$this->filter导致filterValue遍历取过滤器名的时候,不仅仅是可以通过is_callable判断的system,比如上图例子中的filter传入后会经过is_callable(‘filter’),结果为false。
后门技巧
- 允许’app_debug’ => false
- 修改application.php#default_filter
- 设置默认过滤器 system,直接请求,参数名xx=whoami即可执行命令(业务影响未知,大概率影响)
- 5.x的RCE漏洞修复较简单,只是用if校验了一下方法白名单。直接删掉不影响业务。
- 手写一个有漏洞的控制器代码。可以利用TP自带的很多处回调函数比如Request::filterValue中的,怎么写可以参考P牛老文。也是不影响业务。
- …
不开debug也能调用到input方法。
- 路由报错:ThinkPHP V5.0.22 改进Log类支持json日志格式,添加了parseLog方法。该方法的处理过程会调用到filterValue。(需要构造错误,例如在route.php中不use think直接写路由规则)
调用堆栈:
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
-
点击关注,共同学习!
[安全狗的自我修养](https://mp.weixin.qq.com/s/E6Kp0fd7_I3VY5dOGtlD4w)
[github haidragon](https://github.com/haidragon)
https://github.com/haidragon