CVE-2017-8917(Joomla 3.7.0 SQL 注入漏洞)
漏洞描述
Joomla 3.7 版本后引入一个新的组件 “com_fields”,这一组件会引发易被利用的漏洞,并且不需要受害者网站上的高权限,这意味着任何人都可以通过对站点恶意访问利用这个漏洞。
影响版本
- joomla 3.7.0
环境配置
- Vulhub 项目
- Docker-compose 启动
漏洞复现
漏洞存在验证
[!success]
joomla 版本为 3.7.0,符合漏洞利用条件
http://192.168.40.129:8080/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(0x23,concat(1,database()),1)
search cve:2017-8917
use exploit/unix/webapp/joomla_comfields_sqli_rce
[!success]
在正常情况下已经成功了,我这里由于没有注册管理员,所以失败了
漏洞原理
Joomla 在 3.7.0 中新增了一个 com_field
组件,其控制器的构造函数如下,在 components/com_fields/controller.php
中:
可以看到当访问的 view
是 fields
,layout
是 modal
的时候,程序会从 JPATH_ADMINISTRATOR
中加载 com_fields
,这就意味着普通用户可以通过这样的请求来使用管理员的 com_fields
。
接下来我们看管理员的 com_fields
组件,我们来到 administrator/components/com_fields/models/fields.php
,其中的 getListQuery
的部分代码如下:
程序通过 $this->getState
取到 list.fullordering
,然后使用 $db->escape
处理后传入 $query->order
函数,mysqli 的 escape
函数代码如下:
这里调用 mysqli_real_escape_string
来转义字符,该函数具体作用如下:
仅对单双引号等字符进行转义,并未做更多过滤。另外 $query->order
函数的作用仅仅是将数据拼接到 ORDER BY
语句后,也并未进行过滤,所以如果 list.fullordering
可控,那么就可以进行注入。
我们可以看到 list.fullordering
是一个 state
,state
会在视图的 display
函数中进行设置:
跟进这个设置过程,程序会走到 libraries/legacy/model/list.php
中的 populateState
函数中,具体的调用栈如下:
该函数中有如下一段代码:
if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
{
foreach ($list as $name => $value)
{
// Exclude if blacklisted
if (!in_array($name, $this->listBlacklist))
{
...
$this->setState('list.' . $name, $value);
}
}
}
程序通过 $app->getUserStateFromRequest
取到一个 $list
数组 ,如果数组的 key 不在黑名单中,则遍历该数组对相应 state
进行注册,getUserStateFromRequest
的代码如下:
结合前面的调用来看,我们可以通过请求中的参数 list
来设置 $list
变量,因此我们访问 http://ip/index.php?option=com_fields&view=fields&layout=modal&list[fullordering]=updatexml(2,concat(0x7e,(version())),0)
并开启动态调试动态调试,结果如下:
可以看到 list.fullordering
已经被我们控制。
回到 getListQuery
,该函数会在视图加载时被自动调用,具体函数调用栈如下:
所以我们的 payload 也就通过 getState
传入了这个函数,最终导致 SQL 注入:
修复建议
- 升级 joomla 版本
补丁分析
改为取 list.ordering
和 list.direction
作为查询的参数,这两个参数在 populateState
函数中做了如下处理:
如果值不在指定范围内则将其更改为默认值,因此无法再将 payload 带入。