目录
RMIServer,创建注册中心默认端口1099,然后绑定对象
只要创建了远程对象就会生成DGC,DGC的服务端和客户端都可以被攻击。
java动态代理其实就是方便了开发人员的工作量, 不需要一层一层的修改代码。
一般来说会有四个类来实现,
- 接口类 FileSystemProxyTest.java
- 实现接口类 UnixFileSystem.java
- 实现注解类 JDKInvocationHandler
- 主类 FileSystemProxyTest.java
UnixFileSystem fileSystem = new UnixFileSystem();//new的实现接口的类
// 使用JDK动态代理生成FileSytem动态代理类实例
FileSystem proxyInstance=(FileSystem) Proxy.newProxyInstance(
FileSystem.class.getClassLoader(),//指定动态代理类的类加载器
new Class[]{FileSystem.class},//定义动态代理生成的类实现的接口
new JDKInvocationHandler(fileSystem)//动态代理处理类
);
其实关键信息就是这几行。
javaRMI基础
前提:JDK<=8u121
RMI全称(远程方法调用),即在一个JVM中java程序调用在另一个远程JVM中运行的java程序,两者之间通过网络进行通信。
RMI 依赖的通信协议为 JRMP(Java Remote Message Protocol,Java 远程消息交换协议),该协议为 Java 定制,要求服务端与客户端都为 Java 编写。
RMI包括三部分
Server----------服务端:服务端通过绑定远程对象,这个对象可以封装很多网络操作,Socket
Client----------客户端:客户端调用服务端的方法
Registry-------注册端:提供服务注册与服务获取。
先简单的创建一个Demo
服务端
RemoteObj,继承Remote,定义了一个接口
package com.cxk.RMI;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RemoteObj extends Remote{
public String sayHello(String keywords) throws RemoteException;
}
RemoteObjImpl,实现了接口
package com.cxk.RMI;
import com.sun.org.apache.xpath.internal.compiler.Keywords;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Locale;
public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj{
protected RemoteObjImpl() throws RemoteException {
}
public String sayHello(String keywords) throws RemoteException {
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
}
RMIServer,创建注册中心默认端口1099,然后绑定对象
package com.cxk.RMI;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws RemoteException, AlreadyBoundException {
RemoteObjImpl remoteObj = new RemoteObjImpl();//new一个实现类,好像动态代理 实例化远程对象
Registry registry = LocateRegistry.createRegistry(1099);//绑定对象示例到注册中心
registry.bind("remoteObj",remoteObj);
}
}
客户端
RMIClient,锁定代理中心然后查询对象名
package com.cxk.RMI;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
RemoteObj remoteObj = (RemoteObj) registry.lookup("remoteObj");
remoteObj.sayHello("hello");
}
}
服务端和客户端同时运行,发现结果输出在服务端,不难理解因为我们在客户端实现的功能其实就是远程操作服务端。
ps :实现类的接口,发现没 implements接口,一直报错代理错误,搞了一个小时 md气死
从 IDEA 断点分析 RMI 通信原理
首先会调用父类的方法, port=0发送到一个随机的端口,然后调用exportObject
这里的obj是我们传入的对象,前面函数是实现接口的方法,猜测后面的是处理网络接口的
跟进new LiveRef中去
跟进this看一下构造函数
之后就是各种封装,感觉不太重要就不 记录了。
创建注册中心
首先创建注册中心,默认端口为1099
这里发现最后一个值为true,而我们上面调用的是false,true的作用说明是永久的 跟进exportObject
然后里面有判断跟进去看以下,
如果当前文件名+_Stub在系统中存在就返回true,这里的remoteClass=Registrylmpl,系统中存在Registrylmpl_stub是反编译出来的。
满足条件调用createStub方法
获取类名然后获取构造器,
返回回来发现stub
然后用Target封装我们有的东西
绑定的流程
bindings.get就是里面看有没有重复的名字。checkAccess限制了本地
服务端的流程
第一步需要从注册中心获取代理的远程对象
第二步通过代理向服务端获取调用。
首先
LocateRegistry.getRegistry,getRegistry也很神奇,这里采用了生成liveRef本地对象
之后的操作和创建服务端一模一样 ,也就是说,客户端并不是直接运用服务端的东西,而是获取对象在客户端自己创建了一个,
客户端自己 创建的对象,
获取远程对象
传一个字符串进来就是在注册中定义的名字,然后进行序列化输出,这里注册中心肯定有反序列化 的点
里面会调用一个executeCall()方法
是一个真正处理网络请求的方法,客户端的请求都是它来处理
通过反序列化来获取代理,客户端向注册中心获取代理的过程是通过反序列化得来的,如果有一个恶意的注册 中心,那么就可以危害客户端
在executecall()方法中有反序列化的点,如果注册中心返回一个恶意的流,这个危害性很高因为很多方法都会调用它,因为它是处理 网络请求的,所以很多人访问
发现 invoke中就调用了executeCall()方法,所以只要调用invoke都有可能有漏洞,所以客户端很危险
客户端连接服务端
remoteObj.sayHello("hello");
这里直接进入了invoke方法
因为remoteObj是代理对象,代理不论调用什么都会先调用invoke方法
marshalValue会序列化对象的 值
会把参数值序列化进去
在下面又调用了executeCall,
这里的返回值如果不是空的话,会调用
刚好我们的sayHello有返回值所以说就会进去
umarshaValue里面会进行 反序列化
漏洞有两个点,一个是有返回值调用in.readobject一个是处理协议的excutecall(协议叫做JRMP协议) RMI自定的客户端来攻击
客户端请求注册中心(客户端攻击注册中心)
发现case0 1 2中都有readobject,都是可能被攻击的,是客户端序列化的数据。
只要创建了远程对象就会生成DGC,DGC的服务端和客户端都可以被攻击。
高版本jdk绕过
jdk8u121左右跟新
注册中心和DGC里面的反序列类做了限制,只要规定的类才能进行反序列化。
Registrylmpl::反序列化---->readExternal::LiveRef.read----->LiveRef::stream.saveRef--->Connectioninputstream::incomingRefTable.put赋值--->
然后为了调用,DGCClient.registerRefs-->register