RMI反序列化漏洞分析

原创:Xman21合天智汇
原创投稿活动:
http://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/Nw2VDyvCpPt_GG5YKTQuUQ
一、RMI简介首先看一下RMI在wikipedia上的描述:
JAVA远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口 。它使客户机上运行的程序可以调用远程服务器上的对象 。远程方法调用特性使Java编程人员能够在网络环境中分布操作 。RMI全部的宗旨就是尽可能简化远程接口对象的使用 。Java RMI极大地依赖于接口 。在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节 。客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信 。这样一来,程序员只需关心如何通过自己的接口句柄发送消息 。
换句话说,使用RMI是为了不同JVM虚拟机的Java对象能够更好地相互调用,就像调用本地的对象一样 。RMI为了隐藏网络通信过程中的细节,使用了代理方法 。如下图所示,在客户端和服务器各有一个代理,客户端的代理叫Stub,服务端的代理叫Skeleton 。代理都是由服务端产生的,客户端的代理是在服务端产生后动态加载过去的 。当客户端通信是只需要调用本地代理传入所调用的远程对象和参数即可,本地代理会对其进行编码,服务端代理会解码数据,在本地运行,然后将结果返回 。在RMI协议中,对象是使用序列化机制进行编码的 。

RMI反序列化漏洞分析

文章插图
 
?
我们可以将客户端存根编码的数据包含以下几个部分:
  • 被使用的远程对象的标识符
  • 被调用的方法的描述
  • 编组后的参数
当请求数据到达服务端后会执行如下操作:
  1. 定位要调用的远程对象
  2. 调用所需的方法,并传递客户端提供的参数
  3. 捕获返回值或调用产生的异常 。
  4. 将返回值编组,打包送回给客户端存根
客户端存根对来自服务器端的返回值或异常进行反编组,其结果就成为了调用存根返回值 。
RMI反序列化漏洞分析

文章插图
 
?
二、RMI示例【RMI反序列化漏洞分析】接下来我们编写一个RMI通信的示例,使用IDEA新建一个Java项目,代码结构如下:
RMI反序列化漏洞分析

文章插图
 
?
Client.java
  1. package client;
  2. import service.Hello;
  3. import java.rmi.registry.LocateRegistry;
  4. import java.rmi.registry.Registry;
  5. import java.util.Scanner;
  6. public class Client {
  7. public static void main(String[] args) throws Exception{
  8. // 获取远程主机上的注册表
  9. Registry registry=LocateRegistry.getRegistry("localhost",1099);
  10. String name="hello";
  11. // 获取远程对象
  12. Hello hello=(Hello)registry.lookup(name);
  13. while(true){
  14. Scanner sc = new Scanner( System.in );
  15. String message = sc.next();
  16. // 调用远程方法
  17. hello.echo(message);
  18. if(message.equals("quit")){
  19. break;
  20. }
  21. }
  22. }
  23. }
Server.java
  1. package server;
  2. import service.Hello;
  3. import service.impl.HelloImpl;
  4. import java.rmi.registry.LocateRegistry;
  5. import java.rmi.registry.Registry;
  6. import java.rmi.server.UnicastRemoteObject;
  7. public class Server {
  8. public static void main(String[] args) throws Exception{
  9. String name="hello";
  10. Hello hello=new HelloImpl();
  11. // 生成Stub
  12. UnicastRemoteObject.exportObject(hello,1099);
  13. // 创建本机 1099 端口上的RMI registry
  14. Registry registry=LocateRegistry.createRegistry(1099);
  15. // 对象绑定到注册表中
  16. registry.rebind(name, hello);
  17. }
  18. }
Hello.java
  1. package service;
  2. import java.rmi.Remote;
  3. import java.rmi.RemoteException;
  4. public interface Hello extends Remote {
  5. public String echo(String message) throws RemoteException;
  6. }
HelloImpl
  1. package service.impl;
  2. import service.Hello;
  3. import java.rmi.RemoteException;
  4. public class HelloImpl implements Hello {
  5. @Override
  6. public String echo(String message) throws RemoteException {
  7. if("quit".equalsIgnoreCase(message.toString())){
  8. System.out.println("Server will be shutdown!");
  9. System.exit(0);
  10. }
  11. System.out.println("Message from client: "+message);


    推荐阅读