thinkphp代码执行漏洞分析

Thinkphp5.0.x命令执行

【1】漏洞技术细节(涉及代码段、原理等)

漏洞根源利用函数:

ReflectionMethod可执行已存在类中的函数,->invokeArgs能接受参数

ReflectionFunction可反射执行php中任何函数,->invokeArgs能接受参数

在于call_user_func()把第一个参数作为回调函数调用

如果两个参数都可控,即可命令执行,如下:

 

可控参数传入方式:

var_pathinfo的默认配置为s,可利用$_GET['s']来传递路由信息:

入口文件为public/index.php,调用了thinkphp/start.php

跟进start.php:

调用了App类中的静态方法run(),跟进run():

增加刚进入run函数时的断点,此时get到的变量如下:

在run函数中,会进入routeCheck函数进行URL路由检测,传入参数为 $request和$config

$request::instance = think\Request[35]

              ...

       $request::hook = array[0]

       $request->method = <null>

       $request->domain = <null>

       $request->url = <null>

       $request->baseUrl = <null>

       $request->baseFile = <null>

       $request->root = <null>

       $request->pathinfo = <null>

       $request->path = <null>

       $request->routeInfo = array[0]

       $request->env = <null>

       $request->dispatch = array[0]

       $request->module = <null>

       $request->controller = <null>

       $request->action = <null>

 

$config是应用的相关配置:

$config["app_host"] = <string>

       $config["app_debug"] = (bool) 0

       $config["app_trace"] = (bool) 0

       $config["app_status"] = <string>

       $config["app_multi_module"] = (bool) 1

       $config["auto_bind_module"] = (bool) 0

       $config["root_namespace"] = array[0]

       $config["extra_file_list"] = array[1]

              ...

       $config["default_return_type"] = (string) html

       $config["default_ajax_return"] = (string) json

       $config["default_jsonp_handler"] = (string) jsonpReturn

       $config["var_jsonp_handler"] = (string) callback

       $config["default_timezone"] = (string) PRC

       $config["lang_switch_on"] = (bool) 0

跟进routeCheck():

首先会执行$path = $request->path();

进入path函数中:

$suffix=html

再进入pathinfo函数:

      

       通过Config::get方法获取到var_pathinfo的对应值为s,然后即为$_GET[‘s’]

得到$pathinfo = (string) index/\think\app/invokefunction

return 返回的值为 $this->path = (string) index/\think\app/invokefunction

因此routeCheck中的 $path为(string) index/\think\app/invokefunction

然后设置$result=false;

进行完$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);后观察到值仍为false,因此再往下进入了parseUrl()函数进行path的解析,传入参数为:

( $path=index/\think\app/invokefunction , $depr=”/” , $config[‘url_domain_deploy’] )

此时调试栈情况如下:

跟进parseUrl()函数中:

执行$url = str_replace($depr, '|', $url); 后 $url = (string) index|\think\app|invokefunction

将$url再作为参数传入parseUrlPath进行处理,返回为以 / 分割开的数组

return [$path, $var]; 

 

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

后直接跳到了$controller = !empty($path) ? array_shift($path) : null;

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

把$route封装后:

return ['type' => 'module', 'module' => $route]; 返回解析完的url数组

回到routeCheck中:

回到run函数:

执行$data = self::exec($dispatch, $config);时用到了返回的数组,进入exec函数:

type为module,进入APP.php/module函数中:

此时调试栈的情况如下:

执行完$module = strip_tags(strtolower($result[0] ?: $config['default_module']));后:

之后获取控制器和操作:

本次修复便在这里进行了过滤,对控制器进行正则匹配

获取到的各个值为:

// 设置当前请求的控制器、操作

  $request->controller(Loader::parseName($controller, 1))->action($actionName);

继续执行如下初始化:

$instance = Loader::controller(

                $controller,

                $config['url_controller_layer'],

                $config['controller_suffix'],

                $config['empty_controller']

            );

Loader::controller用于 实例化(分层)控制器

执行完后对应$instance的值如下:

is_callable验证think\app中存在invokefunction方法,进入if语句

[ is_callable() 函数验证变量的内容能否作为函数调用 ]

进入invokeMethod中:

此时调试栈情况如下:

传入的参数值如下:

执行完反射类创建后:$reflect = new \ReflectionMethod($class, $method[1]);

$args = self::bindParams($reflect, $vars); 获取POC中的余下的参数function 、vars的值

下面执行$reflect->invokeArgs函数,在ReflectionMethod类中此函数作用是使用数组方法给函数传递参数,并执行函数(由于要选择类才能执行函数,因此要借助invoke中的

ReflectionFunction函数来达到可以执行任意函数,从而传入要执行的函数名和参数)

最终借助invokeFunction,跟进:

执行完赋值操作后各变量值如下:

因此最终执行的为call_user_func_array函数,通过数组方式传入的参数为phpinfo,到这一步执行完后,就已经完成了命令的执行。

Example #1 ReflectionFunction::invokeArgs() example

<?php
function title($title, $name)
{
    return sprintf("%s. %s\r\n", $title, $name);
}
$function = new ReflectionFunction('title');
echo $function->invokeArgs(array('Dr', 'Phil'));
?>

以上例程会输出:

Dr. Phil

因此把call_user_func()以及要执行的函数A和A所需参数一并传入ReflectionFunction中,即可执行

call_user_func() - 把第一个参数作为回调函数调用

所以第一个参数var[0]可以传要执行函数A,第二个var[1]可以传A函数需要的参数

 

【2】实验执行情况

【3】修复方法

thinkphp/library/think/App.php  类的module方法的获取控制器的代码后面加上:

if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) {

     throw new HttpException(404, 'controller not exists:' . $controller);

}

【4】参考文档/链接

https://www.cnblogs.com/backlion/p/10106676.html

https://www.cnblogs.com/st404/p/10245844.html

https://php.golaravel.com/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值