Bootstrap

ThinkPHP 5.0 * 远程代码执行漏洞分析

ThinkPHP 5.0 * 远程代码执行全版本通杀

本文主要以官网下载的5.0.23 完整版(thinkphp_5.0.23_with_extend.zip)为例分析。

Thinkphp处理请求的关键类为Request(thinkphp/library/think/Request.php)
该类可以实现对HTTP请求的一些设置。

其中成员函数method用来获取当前请求类型,其定义如下:
在这里插入图片描述

该函数主要在其他成员函数(例如isGet、isPost、isPut等)中被用来做请求类型判断
在这里插入图片描述

thinkphp支持配置“表单伪装变量”,默认情况下该变量值为_method
在这里插入图片描述

因此在method()中,可以通过“表单伪装变量”进行变量覆盖实现对该类任意函数的调用,并且$_POST作为函数的参数传入。
在这里插入图片描述

Request类的构造函数定义如下:
在这里插入图片描述

构造函数中,主要对 o p t i o n 数 组 进 行 遍 历 , 当 option数组进行遍历,当 optionoption的键名为该类属性时,则将该类同名的属性赋值为$options中该键的对应值。

因此可以构造请求如下,来实现对Request类属性值的覆盖,例如覆盖filter属性。filter属性保存了用于全局过滤的函数。
因此在thinkphp 5.0.10 中可以通过构造如下请求实现代码执行:
在这里插入图片描述

在官网最新下载的5.0.23完整版中,在App类(thinkphp/library/think/App.php)中module方法增加了设置filter参数值的代码,用于初始化filter。因此通过上述请求设置的filter参数值会被重新覆盖为空导致无法利用。
在这里插入图片描述

在5.0.23的Request类中,存在param成员函数用于获取当前请求的参数。其中也有调用method()函数,并且传入参数值为true。param函数代码实现如下:
在这里插入图片描述

当传入的$method === true时 会执行如下代码,
在这里插入图片描述

其中server()实现如下:
在这里插入图片描述

由此可见,参数$name为REQUEST_METHOD,最终会调用input函数。input函数实现如下。最终会执行到$filter = t h i s − > g e t F i l t e r ( this->getFilter( this>getFilter(filter,$default); 来解析过滤器。
在这里插入图片描述

此时 f i l t e r 为 ‘ ’ , filter 为‘’, filterdefault为null。
继续跟进查看getFilter函数的实现:
在这里插入图片描述

由于 f i l t e r = ‘ ‘ , 因 此 可 以 执 行 到 红 线 处 , 并 且 最 终 filter = ‘‘,因此可以执行到红线处,并且最终 filter=线filter会被赋值进 t h i s − > f i l t e r , 和 this->filter ,和 this>filterdefault 即 null。因此经过“解析过滤器”的过程, f i l t e r 最 终 可 被 添 加 来 自 请 求 中 的 f i l t e r 。 之 后 , 代 码 判 断 了 filter最终可被添加来自请求中的filter。之后,代码判断了 filterfilterdata是否为数组,最终将每个值作为参数,通过filterValue()函数进行过滤。而 d a t a 即 为 s e r v e r ( ) 函 数 中 传 入 的 data即为server()函数中传入的 dataserver()this->server ,因此也可通过在调用构造函数时,实现对$this->server值的覆盖。

filterValue函数实现如下:
在这里插入图片描述

最终会通过call_user_func来实现代码执行。此处原理同刚刚爆出的THINKPHP 5 通过控制controller值实现反射调用指定类来远程代码执行漏洞原理一致,不再过多分析。

因此只需要找到自动触发调用param()函数的地方即可。

其中在App类中的run()函数中,如果开启了debug模式,会实现日志的记录,其中有调用$request->param()。
在这里插入图片描述

因此在debug模式下,发送如下请求可实现代码执行:
在这里插入图片描述

例如touch /tmp/xxxx
在这里插入图片描述

继续分析。可以看到“记录路由和请求信息”之前,还实现了“未设置调度信息则进行URL路由检测。”其中通过routeCheck函数来设置 d i s p a t c h 。 最 终 再 通 过 e x e c 函 数 , 将 dispatch。最终再通过exec函数,将 dispatchexecrequest和 c o n f i g 作 为 参 数 传 入 后 执 行 。 其 中 config 作为参数传入后执行。其中 configconfig 是通过initCommon函数调用init函数来加载config配置文件来实现赋值。$request即为Request::instance()。
在这里插入图片描述

其中routeCheck函数实现如下:
在这里插入图片描述

首先会通过加载config文件导入“路由配置”。默认配置如下:
在这里插入图片描述

然后通过Route类的import方法加载路由。由于在入口文件index.php(public/index.php)中加载了start.php(thinkphp/start.php)
在这里插入图片描述

start.php又加载了base.php(thinkphp/base.php)
在这里插入图片描述

base.php中实现了注册自动加载
在这里插入图片描述

register函数实现如下:
在这里插入图片描述

该函数完成了对VENDOR下class的加载。其中完成了验证码类路由的加载
在这里插入图片描述

因此上面import之后,Route类$rules值将形如下:
在这里插入图片描述

然后代码会执行到“路由检测”的部分即调用Route::check的地方,路由检测主要用于根据路由的定义返回不同的URL调度。

Route类check函数实现如下:
在这里插入图片描述

可以看到该check函数中调用了Request类的method方法。并将函数执行结果转换为小写后保存在 m e t h o d 变 量 。 由 于 R e q u e s t 类 中 m e t h o d 函 数 在 返 回 m e t h o d 时 直 接 判 断 了 如 果 存 在 method变量。由于Request类中method函数在返回method时直接判断了如果存在 methodRequestmethodmethodthis->method直接返回
在这里插入图片描述

因此我们在调用构造函数覆盖变量时,可以直接覆盖method,这样上面的 m e t h o d = s t r t o l o w e r ( method = strtolower( method=strtolower(request->method()); 的$method最终的值就可以被控制了。

回到check函数,在设置完 m e t h o d 后 , 会 根 据 method 后,会根据 methodmethod在self:: r u l e s 中 获 取 对 应 rules中获取对应 rulesmethod的路由信息,并将结果赋值给$rules.
在这里插入图片描述

最终函数的return将依赖 r u l e s , 因 此 rules ,因此 rulesmethod不同返回也不同。当 m e t h o d 为 g e t 时 , method 为get 时, methodgetrules 将为如下:
在这里插入图片描述

由于$item = str_replace(’|’, ‘/’, $url); 而 u r l = s t r r e p l a c e ( url = str_replace( url=strreplace(depr, ‘|’, u r l ) ; 而 这 个 url); 而这个 url);url最初为该函数的参数,在App类routeCheck方法中调用
在这里插入图片描述

而这个$path = $request->path();

Request类的path方法实现如下,
在这里插入图片描述

当is_null($this->path)时,通过pathinfo()函数来获取
在这里插入图片描述

其中var_pathino 默认值为s

在这里插入图片描述

因此综上,我们可以通过URL中s参数来设置App类routeCheck函数中的 p a t h , 即 R o u t e r 类 c h e c k 函 数 中 的 path ,即Router类check函数中的 pathRoutercheckurl ,最终即能控制 Router类check函数中的 i t e m 从 而 控 制 c h e c k 函 数 真 正 r e t u r n 哪 种 结 果 。 从 而 最 终 影 响 A p p 类 r u n 方 法 的 item 从而控制check函数真正return哪种结果。从而最终影响App类run方法的 itemcheckreturnApprundispath值即调度信息。

由于$dispatch 会作为参数在exec中调用。

在这里插入图片描述

其中exec的实现如下:
在这里插入图片描述

exec会根据 d i s p a t c h [ ‘ t y p e ’ ] 来 决 定 实 际 执 行 的 代 码 。 前 面 分 析 可 知 , 我 们 需 要 触 发 R e q u e s t 类 中 p a r a m 函 数 的 调 用 来 完 成 对 f i l t e r 的 覆 盖 。 此 处 显 而 易 见 的 是 , 当 dispatch[‘type’]来决定实际执行的代码。 前面分析可知,我们需要触发Request类中param函数的调用来完成对filter的覆盖。此处显而易见的是,当 dispatch[type]Requestparamfilterdispatch[‘type’]为controller 和method时有直接的调用。当然其他的类型,例如当 d i s p a t c h [ ‘ t y p e ’ ] 为 f u n c t i o n , 调 用 了 i n v o k e F u n c t i o n , 而 i n v o k e F u n c t i o n 调 用 了 b i n d P a r a m s 函 数 , b i n d P a r a m s 函 数 里 存 在 对 p a r a m 函 数 的 调 用 。 总 之 , 我 们 需 要 控 制 请 求 u r l 中 s 的 值 完 成 设 置 不 同 的 dispatch[‘type’]为function,调用了invokeFunction,而invokeFunction调用了bindParams函数,bindParams函数里存在对param函数的调用。总之,我们需要控制请求url中s的值完成设置不同的 dispatch[type]functioninvokeFunctioninvokeFunctionbindParamsbindParamsparamurlsmethod,最终让routeCheck返回我们需要的$dispath即可。

例如我们控制url为 /public/index.php?s=captcha,同时post body为_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al则check函数最终会进入
在这里插入图片描述

然后checkRoute函数,由于rule为字符串,
在这里插入图片描述

因此最终会进入

在这里插入图片描述

然后会进入 parseRule
在这里插入图片描述

然后会进入
在这里插入图片描述

最后返回$result. 因此最终$dispatch值为:
在这里插入图片描述

因而在传入exec函数后,触发Request类的param方法,最终覆盖Request类的server变量,接着通过Request类的input方法,实现任意代码执行。

在这里插入图片描述

修复建议:

git更新最新框架代码 https://github.com/top-think/framework

在这里插入图片描述

;