Joomla! 3.7 Core SQL 注入 (CVE-2017-8917)漏洞分析
Author: p0wd3r (知道创宇404安全实验室)
Date: 2017-05-18
0x00 漏洞概述
漏洞简介
Joomla于5月17日发布了新版本3.7.1,(https://www.joomla.org/announcements/release-news/5705-joomla-3-7-1-release.html),本次更新中修复一个高危SQL注入漏洞(https://developer.joomla.org/security-centre/692-20170501-core-sql-injection.html),成功利用该漏洞后攻击者可以在未授权的情况下进行SQL注入。
漏洞影响
未授权状态下SQL注入
影响版本: 3.7.0
0x01 漏洞复现
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
函数中,具体的调用栈如下:
该函数中有如下一段代码:
1 |
<ol class="linenums"><li class="L0"><code class="lang-php"><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$list </span><span class="pun">=</span><span class="pln"> $app</span><span class="pun">-></span><span class="pln">getUserStateFromRequest</span><span class="pun">(</span><span class="pln">$this</span><span class="pun">-></span><span class="pln">context </span><span class="pun">.</span><span class="pln"> </span><span class="str">'.list'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'list'</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">(),</span><span class="pln"> </span><span class="str">'array'</span><span class="pun">))</span></code></li><li class="L1"><code class="lang-php"><span class="pun">{</span></code></li><li class="L2"><code class="lang-php"><span class="pln"> </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">$list </span><span class="kwd">as</span><span class="pln"> $name </span><span class="pun">=></span><span class="pln"> $value</span><span class="pun">)</span></code></li><li class="L3"><code class="lang-php"><span class="pln"> </span><span class="pun">{</span></code></li><li class="L4"><code class="lang-php"><span class="pln"> </span><span class="com">// Exclude if blacklisted</span></code></li><li class="L5"><code class="lang-php"><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">in_array</span><span class="pun">(</span><span class="pln">$name</span><span class="pun">,</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">listBlacklist</span><span class="pun">))</span></code></li><li class="L6"><code class="lang-php"><span class="pln"> </span><span class="pun">{</span></code></li><li class="L7"><code class="lang-php"></code></li><li class="L8"><code class="lang-php"><span class="pln"> </span><span class="pun">...</span></code></li><li class="L9"><code class="lang-php"></code></li><li class="L0"><code class="lang-php"><span class="pln"> $this</span><span class="pun">-></span><span class="pln">setState</span><span class="pun">(</span><span class="str">'list.'</span><span class="pln"> </span><span class="pun">.</span><span class="pln"> $name</span><span class="pun">,</span><span class="pln"> $value</span><span class="pun">);</span></code></li><li class="L1"><code class="lang-php"><span class="pln"> </span><span class="pun">}</span></code></li><li class="L2"><code class="lang-php"><span class="pln"> </span><span class="pun">}</span></code></li><li class="L3"><code class="lang-php"><span class="pun">}</span></code></li></ol> |
程序通过$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注入:
0x02 补丁分析
改为取list.ordering
和list.direction
作为查询的参数,这两个参数在populateState
函数中做了如下处理:
如果值不在指定范围内则将其更改为默认值,因此无法再将payload带入。
0x03 参考
https://www.seebug.org/vuldb/ssvid-93113
https://blog.sucuri.net/2017/05/sql-injection-vulnerability-joomla-3-7.html
https://developer.joomla.org/security-centre/692-20170501-core-sql-injection.html
https://www.joomla.org/announcements/release-news/5705-joomla-3-7-1-release.html