JAVA_RMI

RMI

RMI全称是Remote Method Invocation,远程⽅法调⽤。使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。

RMI主要由三部分组成

1
2
3
Server: 	提供远程的对象
Client: 调用远程的对象
Registry: 一个注册表,存放着远程对象的位置(ip、端口、标识符)

Java本身对RMI规范的实现默认使用的是JRMP协议。

1
JRMP:Java Remote Message Protocol ,Java 远程消息交换协议。这是运行在Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为Java编写,就像HTTP协议一样,规定了客户端和服务端通信要满足的规范

而在Weblogic中对RMI规范的实现使用T3协议。

1
WebLogic T3协议(Two-Tier TCP/IP Protocol)是Oracle WebLogic Server中的一种专有协议,它建立在TCP/IP协议之上,用于在客户端和服务器之间进行通信。T3协议是WebLogic Server的默认通信协议,主要用于处理Java客户端和WebLogic Server之间的交互。

Server&Registry

Server需要构建一个可以被传输的类,一个可以被远程访问的类,同时这个对象要被注册到RMI中开放给客户端使用

Registry需要将我们的类和端口放入到注册表里面

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
package org.example.java_RMI;

import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class server {
public interface RemoteInterface extends java.rmi.Remote {
public String exp() throws IOException;
}

public class example extends UnicastRemoteObject implements RemoteInterface{

protected example() throws RemoteException {
}

@Override
public String exp() throws IOException {
Runtime.getRuntime().exec("calc");
return null;
}
}
private void start() throws RemoteException, MalformedURLException {
example exp = new example();
LocateRegistry.createRegistry(1145);
Naming.rebind("rmi://127.0.0.1:1145/exp",exp);
}

public static void main(String[] args) throws MalformedURLException, RemoteException {
new server().start();
System.out.println("sever is running");
}
}

我们在接口RemoteInterface里面定义了一个可以被远程调用的函数exp()

然后example类实现了这个接口。最后使用主类创建了这个server

Client

需要引入可以远程访问和需要传输的类,通过端口和绑定了sever的地址,就可以发起调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example.java_RMI;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
public static void main(String[] args) throws MalformedURLException, RemoteException {
try {
// 创建远程对象
server.RemoteInterface myclass = (server.RemoteInterface) Naming.lookup("rmi://127.0.0.1:1145/exp");
// 调用远程方法
String response = myclass.exp();
System.out.println(response);
} catch (Exception e) {
e.printStackTrace();
}
}
}

可以看到成功获取这个对象,并且调用了方法

image-20240204143757392

image-20240204143807082

过程

首先客户端(client)链接注册端(Registry),然后在里面寻找相关的类exp,寻找到之后,Registry返还一个序列化的数据。然后这个时候客户端会对这个对象进行反序列化,发现是远程对象,然后发起链接,才链接的过程中执行远程方法调用

远程方法实际上只在Server上调用,在本地不调用

危险方法探测

如果我们可以访问目标的RMI Registry ,我们可以查看上面存在那些可能的危险方法

java虽然存在相关的限制,只有来源地址是localhost的时候,才能调用rebind、 bind、unbind等方法。,不过依然可以使用list方法和lookup探测危险方法

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
package org.example.java_RMI;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
public static void main(String[] args) throws MalformedURLException, RemoteException {
try {
// 创建远程对象
// server.RemoteInterface myclass = (server.RemoteInterface) Naming.lookup("rmi://127.0.0.1:1145/exp");
// // 调用远程方法
// String response = myclass.exp();
String[] remoteObjects = Naming.list("rmi://127.0.0.1:1145/");
System.out.println("存在的方法是");
for (String object : remoteObjects) {
System.out.println(object);
}

} catch (Exception e) {
e.printStackTrace();
}
}
}

image-20240204150325929

codebase完成rce

在RMI 的过程中,客户端和服务端会传递一些序列化之后的数据,对象在反序列化的时候,会去寻找对应的类,如果寻找失败,就回去寻找classpath下的类,如果还是寻找失败,就会加载codebase,去加载一个远程的类,如果能控制这个codebase,就能加载恶意类

利用条件

1
2
3
4
5
java7 < 7u21
java6 < 6u54
java5< 6u45
java8 < 8u122
或者java.rmi.server.useCodebaseOnly=false