Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

ThinkPHP5 远程命令执行漏洞分析

0
0
前言

Thinkphp官方最近修复了一个严重的远程代码执行漏洞。这个主要漏洞原因是由于框架对控制器名没有进行足够的校验导致在没有开启强制路由的情况下可以构造恶意语句执行远程命令,受影响的版本包括5.0和5.1版本。

测试环境:

ThinkPHP 5.1 beta + win10 64bit + wamp

漏洞分析

网上已经有些分析文章了,我就正向分析下这次漏洞过程。不同版本的ThinkPHP调用过程和代码会稍有差异,本文分析的是ThinkPHP 5.1 beta的代码,其他版本的可以类似的分析。

首先会加载thinkphp/library/think/App.php ,运行run函数

public function run() { // 初始化应用 $this->initialize(); try { if (defined('BIND_MODULE')) { // 模块/控制器绑定 BIND_MODULE && $this->route->bindTo(BIND_MODULE); } elseif ($this->config('app.auto_bind_module')) { // 入口自动绑定 $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); if ($name && 'index' != $name && is_dir($this->appPath . $name)) { $this->route->bindTo($name); } } $this->request->filter($this->config('app.default_filter')); // 读取默认语言 $this->lang->range($this->config('app.default_lang')); if ($this->config('app.lang_switch_on')) { // 开启多语言机制 检测当前语言 $this->lang->detect(); } $this->request->langset($this->lang->range()); // 加载系统语言包 $this->lang->load([ $this->thinkPath . 'lang/' . $this->request->langset() . '.php', $this->appPath . 'lang/' . $this->request->langset() . '.php', ]); // 获取应用调度信息 $dispatch = $this->dispatch; if (empty($dispatch)) { // 进行URL路由检测 $dispatch = $this->routeCheck($this->request); } // 记录当前调度信息 $this->request->dispatch($dispatch); // 记录路由和请求信息 if ($this->debug) { $this->log('[ ROUTE ] ' . var_export($this->request->routeinfo(), true)); $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); } // 监听app_begin $this->hook->listen('app_begin', $dispatch); // 请求缓存检查 $this->request->cache( $this->config('app.request_cache'), $this->config('app.request_cache_expire'), $this->config('app.request_cache_except') ); // 执行调度 $data = $dispatch->run(); } catch (HttpResponseException $exception) { $data = $exception->getResponse(); } // 输出数据到客户端 if ($data instanceof Response) { $response = $data; } elseif (!is_null($data)) { // 默认自动识别响应输出类型 $isAjax = $this->request->isAjax(); $type = $isAjax ? $this->config('app.default_ajax_return') : $this->config('app.default_return_type'); $response = Response::create($data, $type); } else { $response = Response::create(); } // 监听app_end $this->hook->listen('app_end', $response); return $response; }
ThinkPHP5 远程命令执行漏洞分析

跟进这个路由检测的routeCheck函数

public function routeCheck() { $path = $this->request->path(); $depr = $this->config('app.pathinfo_depr'); // 路由检测 $files = scandir($this->routePath); foreach ($files as $file) { if (strpos($file, '.php')) { $filename = $this->routePath . DIRECTORY_SEPARATOR . $file; // 导入路由配置 $rules = include $filename; if (is_array($rules)) { $this->route->import($rules); } } } $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must'); // 路由检测(根据路由定义返回不同的URL调度) return $this->route->check($path, $depr, $must); }

routeCheck函数又调用了path函数,跟进这里的path函数


ThinkPHP5 远程命令执行漏洞分析

在 thinkphp/library/think/Request.php 中定义

public function path() { if (is_null($this->path)) { $suffix = $this->config->get('url_html_suffix'); $pathinfo = $this->pathinfo(); if (false === $suffix) { // 禁止伪静态访问 $this->path = $pathinfo; } elseif ($suffix) { // 去除正常的URL后缀 $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); } else { // 允许任何后缀访问 $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); } } return $this->path; }

这里的pathinfo也是在Request.php中定义的

public function pathinfo() { if (is_null($this->pathinfo)) { if (isset($_GET[$this->config->get('var_pathinfo')])) { // 判断URL里面是否有兼容模式参数 $_SERVER['PATH_INFO'] = $_GET[$this->config->get('var_pathinfo')]; unset($_GET[$this->config->get('var_pathinfo')]); } elseif ($this->isCli()) { // CLI模式下 index.php module/controller/action/params/... $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; } // 分析PATHINFO信息 if (!isset($_SERVER['PATH_INFO'])) { foreach ($this->config->get('pathinfo_fetch') as $type) { if (!empty($_SERVER[$type])) { $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; break; } } } $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); } return $this->pathinfo; } 分析可知 $this->config->get('var_pathinfo') 默认值是s(var_pathinfo是在config/app.php里硬编码的),所以我们可利用$_GET['s']来传递路由信息。

回到 thinkphp/library/think/App.php , 运行到执行调度


ThinkPHP5 远程命令执行漏洞分析

这个就是 thinkphp/library/think/route/dispatch/Module.php 函数run的实例

class Module extends Dispatch { public function run() { $result = $this->action; if (is_string($result)) { $result = explode('/', $result); } if ($this->app->config('app.app_multi_module')) { // 多模块部署 $module = strip_tags(strtolower($result[0] ?: $this->app->config('app.default_module'))); $bind = $this->app['route']->getBind(); $available = false; if ($bind && preg_match('/^[a-z]/is', $bind)) { // 绑定模块 list($bindModule) = explode('/', $bind); if (empty($result[0])) { $module = $bindModule; $available = true; } elseif ($module == $bindModule) { $available = true; } } elseif (!in_array($module, $this->app->config('app.deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { $available = true; } // 模块初始化 if ($module && $available) { // 初始化模块

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles



Latest Images