thinkphp
ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布/销售。
漏洞分析
thinkphp/library/think/Request.php:504 Request类的method方法
可以通过POST数组传入__method改变this->{this->method}($_POST);达到任意调用此类中的方法。
然后我们再来看这个类中的__contruct方法
protected function __construct($options = [])
{
foreach ($options as $name => $item) {
if (property_exists($this, $name)) {
$this->$name = $item;
}
}
if (is_null($this->filter)) {
$this->filter = Config::get('default_filter');
}
// 保存 php://input
$this->input = file_get_contents('php://input');
}
重点是在foreach中,可以覆盖类属性,那么我们可以通过覆盖Request类的属性
这样filter就被赋值为system()了,在哪调用的呢?我们要追踪下thinkphp的运行流程 thinkphp是单程序入口,入口在public/index.php,在index.php中
require DIR . '/../thinkphp/start.php';
引入框架的start.php,跟进之后调用了App类的静态run()方法
image
看下run()方法的定义
public static function run(Request $request = null)
{
...省略...
// 获取应用调度信息
$dispatch = self::$dispatch;
if (empty($dispatch)) {
// 进行URL路由检测
$dispatch = self::routeCheck($request, $config);
}
// 记录当前调度信息
$request->dispatch($dispatch);
// 记录路由和请求信息
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');
}
...省略...
switch ($dispatch['type']) {
case 'redirect':
// 执行重定向跳转
$data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
break;
case 'module':
// 模块/控制器/操作
$data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
break;
case 'controller':
// 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']);
break;
case 'method':
// 执行回调方法
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function':
// 执行闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response':
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
}
首先是经过dispatch = self::routeCheck(request, config)检查调用的路由,然后会根据debug开关来选择是否执行Request::instance()->param(),然后是一个switch语句,当dispatch等于controller或者method时会执行Request::instance()->param(),只要是存在的路由就可以进入这两个case分支。
而在 ThinkPHP5 完整版中,定义了验证码类的路由地址?s=captcha,默认这个方法就能使$dispatch=method从而进入Request::instance()->param()。
我们继续跟进Request::instance()->param()
执行合并参数判断请求类型之后return了一个input()方法
将被__contruct覆盖掉的filter字段回调进filterValue(),这个方法我们需要特别关注了,因为 Request 类中的 param、route、get、post、put、delete、patch、request、session、server、env、cookie、input 方法均调用了 filterValue 方法,而该方法中就存在可利用的 call_user_func 函数
call_user_func调用system造成rce。
梳理一下:this->method可控导致可以调用__contruct()覆盖Request类的filter字段,然后App::run()执行判断debug来决定是否执行request->param(),并且还有dispatch['type'] 等于controller或者 method 时也会执行request->param(),而$request->param()会进入到input()方法,在这个方法中将被覆盖的filter回调call_user_func(),造成rce。
实战
xray扫c段的时候扫到一个thinkphp的rce
放到burpsuite里面发包
挂马
蚁剑连接