Thinkphp 5.0.24变量覆盖漏洞导致RCE分析

影响版本ThinkPHP5.0-5.0.23
大概思路就是我们可以修改requests类的filter属性、method属性以及get属性的值,从而在调用param方法时,call_user_func_array的值我们就可以控制,造成了远程代码执行漏洞。

0. 大致流程

经过入口文件进入run函数

首先在116行根据url获取调度信息时,触发变量覆盖漏洞从而修改requests对象的属性值,然后获取?s=captcha的调度信息并返回给$dispatch

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Td04jYMC-1648648634252)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330210627549.png)]

再到139行进入exec函数并将$dispatch作为参数带入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BulklkCS-1648648634253)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330212548714.png)]

跟进后根据$dispatch的type进入到case ‘method’,从而调用requests的param函数,进而造成了rce漏洞

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gd5cDwZP-1648648634253)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330212730853.png)]

1.环境搭建

这里用的环境是thinkphp5.0.20+php5.6.27+apache+phpstorm

POC:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fq1IdDPU-1648648634253)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330191546501.png)]

复现成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThpHpF6m-1648648634254)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330213551329.png)]

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行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HO0XepxW-1648648634254)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330191944892.png)]

这里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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZWgoq1B-1648648634254)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330193019724.png)]

2.2 远程代码执行

RCE的触发点在thinkphp/library/think/Request.php的param函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezS6Q8Pe-1648648634254)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330193750477.png)]

第一个if是获取post的提交内容并赋值给$vars

然后再整合一下赋值给requests对象的param属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Bg9P8xt-1648648634255)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330194725243.png)]

最后调用该类下的input方法,跟进input方法,$data的值为上图红框

public function input($data = [], $name = '', $default = null, $filter = '')
    {
        if (false === $name) {
            // 获取原始数据
            return $data;
        }
        $name = (string) $name;
        if ('' != $name) {
            // 解析name
            if (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”]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHPK8EcE-1648648634255)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330195220631.png)]

回到input函数,这时候$filter为[“system”]

往下走,$data确实是数组,所以进入if

走到array_walk_recursive($data, [$this, ‘filterValue’], $filter);

array_walk_recursive这个函数大概意思是每次从data中取一个值(第一次的取值由上面图红框可知是whoami),应用于第二个参数所指示的函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLKKnCU3-1648648634255)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330195721842.png)]

跟进到filterValue方法,看到此时$filter为"system",$value为whoami,走到1073行即可执行我们设置的回调函数并将结果赋值给value

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Al48JhBr-1648648634255)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330195856545.png)]

最终返回给我们value值,这就造成任意代码执行漏洞

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZ4SP6Ye-1648648634256)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330200653307.png)]

这就是因为我们覆盖了requests对象的属性值导致的rce漏洞

3.具体流程

当我们执行poc后,从入口函数开始分析

首先加载框架的引导文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9qYdWKAN-1648648634256)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330201445736.png)]

往下走,首先加载基础文件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jXCJ6MRK-1648648634256)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330201518480.png)]

然后执行app类的run函数

public static function run(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_dispatch
        Hook::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_begin
        Hook::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 ($data instanceof Response) {
        $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_end
    Hook::listen('app_end', $response);

    return $response;
}

首先实例化一个requests对象,这个包含了请求的相关信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-275uC95M-1648648634256)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330202035941.png)]

往下走到115行,因为我们并没有调度信息,则进入routeCheck函数进行url路由检测产生该url的调度信息,这个调度信息就是匹配?s=captcha对应的路由和类型值供后面的exec函数使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-luaT0x8s-1648648634257)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330202216999.png)]

进入函数,首先将url传给$path

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e99yC0R1-1648648634257)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330202632304.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mO5RjfCI-1648648634257)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330202728917.png)]

然后设置分割符,到643行进行路由检测,根据定义好的路由返回对应的url调度信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozY6xYqU-1648648634257)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330202934897.png)]

进入check函数,走到848行,注意这里就是我们触发变量覆盖漏洞的点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qs1Zglw-1648648634257)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330203110132.png)]

进入method函数,接下来就是最上面讲的变量覆盖了,这里就不在多赘述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wq5rHhsq-1648648634258)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330203138692.png)]

这个函数执行完会返回我们设置的$this->method即GET,注意这里设置为get是为了获取到s=captcha的路由规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhjCfdXi-1648648634258)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330203238779.png)]

返回到check函数,跟进到863行,这里给$item赋值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYyemMHO-1648648634258)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330203637954.png)]

然后检查是否存在$item的路由规则,我们先看一下路由规则里面都有啥,就只有一个当访问captcha/[:id]时路由为\think\captcha\CaptchaController@index

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRz8HMVj-1648648634258)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330203818995.png)]

这个路由规则是在vendor\topthink\think-captcha\src\helper.php定义的,这也就是为啥我们将method设置为get,因为只有这样才能获得captcha的路由规则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4WfGpQh-1648648634258)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330204013616.png)]

好了,回到正题,继续跟进到877行,开始路由规则以及类型匹配!!!!!!!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKn6iRVG-1648648634259)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330204233623.png)]

进入checkRoute函数后走到955行,调用checkRule

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBOXoRSD-1648648634259)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330205658402.png)]

走到1194行调用parseRule函数,注意看此时$route参数为\think\captcha\CaptchaController@index已经匹配到路由
进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,\$result的’type’键对应的值为‘method’。然后将$result层层返回到run函数中,并赋值给了$dispatch

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85qCfQO8-1648648634259)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330210127953.png)]

返回到最开始的run函数,可以看到$result已经包含了captcha的调度信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOWlqdoo-1648648634259)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330210627549.png)]

继续往下走,因为我们没开debug模式所以123行直接跳过,如果我们开启了debug模式,则直接在126行就可以进入到param函数执行rce

走到139行,带着调度信息进入exec函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHQCPjjc-1648648634259)(C:\Users\91136\AppData\Roaming\Typora\typora-user-images\image-20220330210754588.png)]

exec函数,因为我们的$dispatch[‘type’]为method,所以进入case ‘method’

protected static function exec($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:
            throw new \InvalidArgumentException('dispatch type not support');
    }

    return $data;
}

进入requests的param函数

然后就是之前2.2节分析的rce流程了,不再赘述。

4.参考链接

https://paper.seebug.org/787/

https://www.freebuf.com/vuls/307413.html

https://xz.aliyun.com/t/8143#toc-6

https://www.kancloud.cn/zmwtp/tp5/119426

### 回答1: ThinkPHP是一款优秀的PHP开发框架,但在不久前,其官方发布的5.0.24版本中存在一处安全漏洞,可以被攻击者利用进行非法操作或流量劫持。 近期网络上出现了一些ThinkPHP5.0.24漏洞利用工具,攻击者可以利用这些工具进行恶意攻击,比如模拟用户登录、上传恶意文件、获取服务器权限等。而这些攻击都可能严重危害服务器安全,甚至导致数据泄露或丢失。 因此,使用ThinkPHP5.0.24漏洞利用工具的行为是不道德的、非法的,同时也将危及自己和他人的安全。我们应该高度关注漏洞修复的情况,并及时更新框架版本,以保障网站安全。 此外,防范漏洞利用的最好方式是对网站进行全面加固,比如使用防火墙、加强用户认证、控制权限等等。只有在加强安全方面的努力下,才能避免因漏洞被攻击,导致不必要的损失。 ### 回答2: thinkphp5.0.24是一种基于PHP的开源Web应用框架,简单易用,并且具有高度扩展性。然而,其中存在一个被恶意利用的漏洞,即命令执行漏洞,攻击者可以利用该漏洞执行任意命令。 针对这个漏洞,黑客们开发了一些利用工具。其中,比较常见的有ThinkPHP5.0/5.1 远程代码执行漏洞利用工具和thinkphp5.0.24远程代码执行漏洞利用工具。 这些利用工具的基本原理是通过漏洞点执行一段代码,然后让服务器返回含有WebShell的Payload。其中,一个比较常用的Payload是通过控制Meta信息中的User-Agent,让服务器返回一段WebShell代码。得到WebShell后,攻击者可以执行任意命令。 针对此类漏洞,建议管理员及时更新最新版本的ThinkPHP,并定期对系统进行安全扫描等操作,以及增强系统的安全性。另外,也建议加强员工安全意识教育,不要随意下载不明来源的文件或点击垃圾邮件等操作。 ### 回答3: ThinkPHP是一个使用PHP语言开发的MVC框架,它具有开源、易上手、性能强等优点,因此被广泛应用于各类Web开发中。但是,ThinkPHP也存在一些漏洞,例如ThinkPHP 5.0.24版本中,存在一种反序列化漏洞,攻击者可以通过发送一个恶意的HTTP请求来执行任意代码,造成系统安全威胁。 为了利用该漏洞,黑客们开发了一些漏洞利用工具。其中,比较有名的是一款名为“ThinkPHP5.0.24 反序列化命令执行漏洞一键”的工具,该工具可以帮助攻击者快速识别和利用漏洞,在不需要过多技术能力的情况下就能实现攻击。 具体来说,该工具可以通过一些简单的配置,如输入目标IP地址、选择漏洞扫描或直接执行命令等,来实现对已知漏洞目标的攻击,从而获取系统权限、窃取敏感信息等恶意行为。 但是,提醒广大用户,利用漏洞工具进行攻击是违法的行为,严格禁止任何个人和组织非法攻击和滥用该工具。我们应该采取更有效的方式,如及时更新补丁、加强网络安全防范等,来保障自己和他人的网络安全
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值