影响版本ThinkPHP5.0-5.0.23
大概思路就是我们可以修改requests类的filter属性、method属性以及get属性的值,从而在调用param方法时,call_user_func_array的值我们就可以控制,造成了远程代码执行漏洞。
0. 大致流程
经过入口文件进入run函数
首先在116行根据url获取调度信息时,触发变量覆盖漏洞从而修改requests对象的属性值,然后获取?s=captcha的调度信息并返回给$dispatch
再到139行进入exec函数并将$dispatch作为参数带入
跟进后根据$dispatch的type进入到case ‘method’,从而调用requests的param函数,进而造成了rce漏洞
1.环境搭建
这里用的环境是thinkphp5.0.20+php5.6.27+apache+phpstorm
POC:
复现成功
1.1 POC参数解析
method=get 因为captcha的路由规则是get方式下的,所以我们得让method为get,才能获取到captcha的路由
s=captcha 因为在进入exec函数后我们要switch到method中执行param函数,而这个captcha的路由刚好对应类型为method,所以我们选择captcha
filter[]=system 覆盖变量
get[]=whoami 覆盖变量
_method=__construct 为了能够进入construct,从而覆盖变量
2.漏洞分析
这是一个变量覆盖漏洞导致的rce,我们首先来说下变量覆盖漏洞
2.1 变量覆盖漏洞
漏洞触发点在thinkphp/library/think/Request.php的509行:
这里509行的$this->method我们可控,该值就来自与上一行的$_POST[Config::get(‘var_method’)],其中Config::get(‘var_method’)的值是_method
我们post传入_method为__construct,就会调用request对象的构造函数,参数为post内容
跟进__construct,可以看到他将传入的参数依次赋值给相应的属性,这就造成了变量覆盖漏洞,我们可以随便给requests对象的属性赋值,这为后面的rce打下基础。从poc也能看出来,为了能够rce,这里我们需要修改的属性值有filter,get,method,_method
2.2 远程代码执行
RCE的触发点在thinkphp/library/think/Request.php的param函数:
第一个if是获取post的提交内容并赋值给$vars
然后再整合一下赋值给requests对象的param属性
最后调用该类下的input方法,跟进input方法,**$data的值为上图红框**
publicfunctioninput($data=[],$name='',$default=null,$filter=''){if(false===$name){// 获取原始数据return$data;}$name=(string)$name;if(''!=$name){// 解析nameif(strpos($name,'/')){list($name,$type)=explode('/',$name);}else{$type='s';}// 按.拆分成多维数组进行判断foreach(explode('.',$name)as$val){if(isset($data[$val])){$data=$data[$val];}else{// 无输入数据,返回默认值return$default;}}if(is_object($data)){return$data;}}// 解析过滤器$filter=$this->getFilter($filter,$default);if(is_array($data)){array_walk_recursive($data,[$this,'filterValue'],$filter);reset($data);}else{$this->filterValue($data,$name,$filter);}if(isset($type)&&$data!==$default){// 强制类型转换$this->typeCast($data,$type);}return$data;}
执行到这一行:$filter = $this->getFilter($filter, $default);
跟进,看代码意思就是将$this->filter的值赋给$filter变量并返回,这个$this->filter是我们可控的即[“system”]
回到input函数,这时候$filter为[“system”]
往下走,$data确实是数组,所以进入if
走到array_walk_recursive($data, [$this, ‘filterValue’], $filter);
array_walk_recursive这个函数大概意思是每次从data中取一个值(第一次的取值由上面图红框可知是whoami),应用于第二个参数所指示的函数
跟进到filterValue方法,看到此时$filter为"system",$value为whoami,走到1073行即可执行我们设置的回调函数并将结果赋值给value
最终返回给我们value值,这就造成任意代码执行漏洞
这就是因为我们覆盖了requests对象的属性值导致的rce漏洞
3.具体流程
当我们执行poc后,从入口函数开始分析
首先加载框架的引导文件
往下走,首先加载基础文件。
然后执行app类的run函数
publicstaticfunctionrun(Request$request=null){$request=is_null($request)?Request::instance():$request;try{$config=self::initCommon();// 模块/控制器绑定if(defined('BIND_MODULE')){BIND_MODULE&&Route::bind(BIND_MODULE);}elseif($config['auto_bind_module']){// 入口自动绑定$name=pathinfo($request->baseFile(),PATHINFO_FILENAME);if($name&&'index'!=$name&&is_dir(APP_PATH.$name)){Route::bind($name);}}$request->filter($config['default_filter']);// 默认语言Lang::range($config['default_lang']);// 开启多语言机制 检测当前语言$config['lang_switch_on']&&Lang::detect();$request->langset(Lang::range());// 加载系统语言包Lang::load([THINK_PATH.'lang'.DS.$request->langset().EXT,APP_PATH.'lang'.DS.$request->langset().EXT,]);// 监听 app_dispatchHook::listen('app_dispatch',self::$dispatch);// 获取应用调度信息$dispatch=self::$dispatch;// 未设置调度信息则进行 URL 路由检测if(empty($dispatch)){$dispatch=self::routeCheck($request,$config);}// 记录当前调度信息$request->dispatch($dispatch);// 记录路由和请求信息if(self::$debug){Log::record('[ ROUTE ] '.var_export($dispatch,true),'info');Log::record('[ HEADER ] '.var_export($request->header(),true),'info');Log::record('[ PARAM ] '.var_export($request->param(),true),'info');}// 监听 app_beginHook::listen('app_begin',$dispatch);// 请求缓存检查$request->cache($config['request_cache'],$config['request_cache_expire'],$config['request_cache_except']);$data=self::exec($dispatch,$config);}catch(HttpResponseException$exception){$data=$exception->getResponse();}// 清空类的实例化Loader::clearInstance();// 输出数据到客户端if($datainstanceofResponse){$response=$data;}elseif(!is_null($data)){// 默认自动识别响应输出类型$type=$request->isAjax()?Config::get('default_ajax_return'):Config::get('default_return_type');$response=Response::create($data,$type);}else{$response=Response::create();}// 监听 app_endHook::listen('app_end',$response);return$response;}
首先实例化一个requests对象,这个包含了请求的相关信息
往下走到115行,因为我们并没有调度信息,则进入routeCheck函数进行url路由检测产生该url的调度信息,这个调度信息就是匹配?s=captcha对应的路由和类型值供后面的exec函数使用
进入函数,首先将url传给$path
然后设置分割符,到643行进行路由检测,根据定义好的路由返回对应的url调度信息
进入check函数,走到848行,注意这里就是我们触发变量覆盖漏洞的点
进入method函数,接下来就是最上面讲的变量覆盖了,这里就不在多赘述
这个函数执行完会返回我们设置的$this->method即GET,注意这里设置为get是为了获取到s=captcha的路由规则
返回到check函数,跟进到863行,这里给$item赋值
然后检查是否存在$item的路由规则,我们先看一下路由规则里面都有啥,就只有一个当访问captcha/[:id]时路由为\think\captcha\CaptchaController@index
这个路由规则是在vendor\topthink\think-captcha\src\helper.php定义的,这也就是为啥我们将method设置为get,因为只有这样才能获得captcha的路由规则
好了,回到正题,继续跟进到877行,开始路由规则以及类型匹配!!!!!!!!
进入checkRoute函数后走到955行,调用checkRule
走到1194行调用parseRule函数,注意看此时$route参数为\think\captcha\CaptchaController@index已经匹配到路由
**进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,
\$result
的’type’键对应的值为‘method’。然后将
$result
层层返回到run函数中,并赋值给了
$dispatch
。**
返回到最开始的run函数,可以看到$result已经包含了captcha的调度信息
继续往下走,因为我们没开debug模式所以123行直接跳过,如果我们开启了debug模式,则直接在126行就可以进入到param函数执行rce
走到139行,带着调度信息进入exec函数
exec函数,因为我们的$dispatch[‘type’]为method,所以进入case ‘method’
protectedstaticfunctionexec($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:thrownew\InvalidArgumentException('dispatch type not support');}return$data;}
进入requests的param函数
然后就是之前2.2节分析的rce流程了,不再赘述。
4.参考链接
https://www.freebuf.com/vuls/307413.html
版权归原作者 浔阳江头夜送客丶 所有, 如有侵权,请联系我们删除。