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


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

    0. 大致流程

    经过入口文件进入run函数

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Td04jYMC-1648648634252)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330210627549.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BulklkCS-1648648634253)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330212548714.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gd5cDwZP-1648648634253)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330212730853.png)]

    1.环境搭建

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

    POC:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fq1IdDPU-1648648634253)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330191546501.png)]

    复现成功

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ThpHpF6m-1648648634254)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-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:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330191944892.png)]

    这里509行的 t h i s − > m e t h o d 我们可控, ∗ ∗ 该值就来自与上一行的 this->method我们可控,**该值就来自与上一行的 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:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330193019724.png)]

    2.2 远程代码执行

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezS6Q8Pe-1648648634254)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330193750477.png)]

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Bg9P8xt-1648648634255)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    执行到这一行:$filter = t h i s − > g e t F i l t e r ( this->getFilter( this>getFilter(filter, $default);

    跟进,看代码意思就是将 t h i s − > f i l t e r 的值赋给 this->filter的值赋给 this>filter的值赋给filter变量并返回,这个$this->filter是我们可控的即[“system”]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mHPK8EcE-1648648634255)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330195220631.png)]

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

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

    走到array_walk_recursive(KaTeX parse error: Undefined control sequence: \[ at position 7: data, \̲[̲this, ‘filterValue’], $filter);

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLKKnCU3-1648648634255)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330195721842.png)]

    跟进到filterValue方法,看到此时 f i l t e r 为 " s y s t e m " , filter为"system", filter"system"value为whoami,走到1073行即可执行我们设置的回调函数并将结果赋值给value

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Al48JhBr-1648648634255)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330195856545.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZ4SP6Ye-1648648634256)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330200653307.png)]

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

    3.具体流程

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

    首先加载框架的引导文件

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9qYdWKAN-1648648634256)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330201445736.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jXCJ6MRK-1648648634256)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-275uC95M-1648648634256)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330202035941.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-luaT0x8s-1648648634257)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330202216999.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e99yC0R1-1648648634257)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330202632304.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mO5RjfCI-1648648634257)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330202728917.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozY6xYqU-1648648634257)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330202934897.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qs1Zglw-1648648634257)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330203110132.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wq5rHhsq-1648648634258)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330203138692.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhjCfdXi-1648648634258)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330203238779.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYyemMHO-1648648634258)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330203637954.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uRz8HMVj-1648648634258)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330203818995.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4WfGpQh-1648648634258)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330204013616.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKn6iRVG-1648648634259)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330204233623.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBOXoRSD-1648648634259)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330205658402.png)]

    走到1194行调用parseRule函数,注意看此时 r o u t e 参数为 h i n k c a p t c h a C a p t c h a C o n t r o l l e r @ i n d e x 已经匹配到路由 ∗ ∗ ∗ ∗ 进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据 r o u t e 的取值我们进入红框分支,在这个分支中, ‘ route参数为 hinkcaptchaCaptchaController@index已经匹配到路由** **进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,` route参数为hinkcaptchaCaptchaController@index已经匹配到路由进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,result的’type’键对应的值为‘method’。然后将 r e s u l t ‘ 层层返回到 r u n 函数中,并赋值给了 ‘ result`层层返回到run函数中,并赋值给了` result层层返回到run函数中,并赋值给了dispatch`。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-85qCfQO8-1648648634259)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330210127953.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOWlqdoo-1648648634259)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-20220330210627549.png)]

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

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHQCPjjc-1648648634259)(C:Users91136AppDataRoamingTypora	ypora-user-imagesimage-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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    进入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

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    C# 程序运行无法加载 C++ DLL“xxx.dll”: 找不到指定的模块 Exception from HRESULT: 0x8007007E
    MySQL的复制
    【老生谈算法】matlab遗传算法工具箱源码——遗传算法
    ES中的三种查询
    ALU,半加器,全加器,减法电路
    GHostNet网络最通俗易懂的解读【不接受反驳】
    MSDC 4.3 接口规范(30)
    Operator SDK开发ansible operator
    深入探讨TensorFlow:张量与矩阵
    linux下grep命令使用总结
  • 原文地址:https://blog.csdn.net/bhegi_seg/article/details/126080274