Java中怎么远程调用RMI


Java中怎么远程调用RMI,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互JRMPJava远程方法协议(英语:Java Remote Method Protocol,JRMP)是特定于Java技术的、用于查找和引用远程对象的协议。这是运行在Java远程方法调用(RMI)之下、TCP/IP之上的线路层协议。RMIJava远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。JDK关键版本RMI Serialization Attack注意:此Demo没有版本限制,但部分逻辑会由于版本原因造成出入。Demowith JDK 1.8.0_151with java-rmi-server/ rmi.RMIServer、Services、PublicKnownwith java-rmi-client/ rmi.RMIClient、Services、ServicesImpl、PublicKnownPS:低版本无法在RegistryImpl_Skel下有效断点。分析两种 bind 区别Server RMI Registry Clientserver 通过 bind 注册服务时会进行序列化传输服务名&Ref,因此会进入RegistryImpl_Skel.dispatch先经过反序列化获取。Server(RMI Registry) Client这种模式下,由于 server 与 Registry 是同一台机器,在 bind 注册时由于 server 上已有其 Ref,因此不需要序列化传输,只需要在 bindings list 中添加对应键值即可。注册、请求流程RMI Registry 的核心在于 RegistryImpl_Skel。当Server执行bind、Client执行lookup时候,均会通过sun.rmi.registry.RegistryImpl_Skel#dispatch进行处理。bind首先注意到ServiceImpl继承了UnicastRemoteObject,在实例化时会通过exportObject创建返回此服务的stub。再通过bind向RMI Registry服务器申请注册绑定服务名&stub跟入到sun.rmi.registry.RegistryImpl_Stub#bind,注意观察到向RMI Registry申请时,第三个参数对应 operations 里的操作。这里尤其注意的两个 writeObject,分别向 var3 的输出流中写入序列化后的服务名&stub。RMI Registry收到申请时会进行会通过传入的操作值进入相关流程,0时进入bind,注意到两次 readObject 分别反序列化获取服务名&stub后,再向 bindings List 中写入键值。这里就引出来了一个点:Server 通过向 RMI Registry 申请 bind 操作进行序列化攻击。lookup再看Client向RMI Registry申请lookup 查找时候(sun.rmi.registry.RegistryImpl_Stub#lookup)传递的操作数为 2,且反序列化了目标服务名。RMI Registry(sun.rmi.registry.RegistryImpl_Skel#dispatch)这边同样会先反序列化获取查询服务名,再从 bindings list 中进行查询。这里就引出来了另一个点:Client 通过向 RMI Registry 申请 lookup 操作进行序列化攻击。但是就完了么?我们再往下看,注意到 86 行出现的 writeObject,这里是将查询到的stub序列化传输给 Client。回到 Client 的代码中,可以看到104 行的 readObject。这里就引出来了第三个点:RMI Registry 通过 lookup 操作被动式攻击 Client。调用时序列化现在我们理清了bind、lookup的部分内容,那么 client 是如何实现远程调用呢?通过跟进后可以看到由java.rmi.server.RemoteObjectInvocationHandler实现的动态代理,并最终由sun.rmi.server.UnicastRef#invoke实现调用。在调用中我们注意到通过marshalValue打包参数,由unmarshalValue对传回的内容进行反序列化。限制这里的 Demo 实际情况中很难遇到,因为evil是我们根据已知的Services、PublicKnown(含已知漏洞)生成的,在攻击时更多都是采用本地 gadget。攻击方向注意到我们上面提出了三个攻击向。1.Server 通过向 RMI Registry 申请 bind 操作进行序列化攻击;2.Client 通过向 RMI Registry 申请 lookup 操作进行序列化攻击;3.RMI Registry 通过 lookup 操作被动式攻击 Client。其实注意到第一个点里提到的 Server 并不是要求一定要由目标服务器发起,比如任意一台(包括攻击者)均可以向注册中心发起注册请求进而通过 bind 在 RMI Registry 上进行攻击,例如:Client — bind –> RMI Registry(Server)同理第二点、第三点里也是,所以我们更新一下:1.向 RMI Registry 申请 bind 操作进行序列化攻击;2.向 RMI Registry 申请 lookup 操作进行序列化攻击;3.RMI Registry通过lookup操作被动式序列化攻击请求者。bind – RMIRegistryExploitwith JDK 1.7.0_17with java-rmi-server/ rmi.RMIServer2with ysoserial.exploit.RMIRegistryExploitysoserial.exploit.RMIRegistryExploit实际对应bind攻击方向,我们来简单看下它的代码。核心在于两点,对于第一点可以看看 cc1 分析以及Java动态代理-实战这篇。sun.reflect.annotation.AnnotationInvocationHandler动态代理Remote.classbind 操作这里提一下为什么需要动态代理,是由于在sun.rmi.registry.RegistryImpl_Skel#dispatch,执行bind时会通过Remote.readObject反序列化,导致调用AnnotationInvocationHandler.invoke。codebase传递以及useCodebaseOnlyRMI有一个重要的特性是动态类加载机制,当本地CLASSPATH中无法找到相应的类时,会在指定的codebase里加载class,需要java.rmi.server.useCodebaseOnly=false,但是这个特性是一直开启的,直到6u45、7u21修改默认为 true 以防御攻击。这里引用官方文档Enhancements in JDK 7:如果RMI连接一端的JVM在其java.rmi.server.codebase系统属性中指定了一个或多个URL,则该信息将通过RMI连接传递到另一端。如果接收方JVM的java.rmi.server.useCodebaseOnly系统属性设置为false,则它将尝试使用这些URL来加载RMI请求流中引用的Java类。从由RMI连接的远程端指定位置加载类的行为,当被禁用java.rmi.server.useCodebaseOnly被设定为true。在这种情况下,仅从预配置的位置(例如本地指定的java.rmi.server.codebase属性或本地CLASSPATH)加载类,而不从codebase通过RMI请求流传递的信息中加载类。demoClient 攻击 Serverwith JDK 1.7.0_17with java-rmi-server/rmi.RMIServer2with java-rmi-client/rmi.RMIClient2、remote.RemoteObject若 Client 指定了 codebase 地址,Server 加载目标类时会现在本地 classpath 中进行查找,在没有找到的情况下会通过 codebase 对指定地址再次查找。为了能够远程加载目标类,需要Server加载并配置RMISecurityManager,并同时设置:java.rmi.server.useCodebaseOnly=false在传输了 codebase 之后是如何调用的呢?也是由动态代理类java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod实现远程调用。Server 接收到调用指令后,进入sun.rmi.server.MarshalInputStream#resolveClass,由于 useCodebaseOnly 为 false,从客户端指定地址远程读取目标类。全部读取完毕后回到java.io.ObjectInputStream#readOrdinaryObject,调用java.io.ObjectStreamClass#initNonProxy进行实例化。Server 攻击 Clientwith JDK 1.7.0_17with java-rmi-server/rmi.RMIServer3、remote.RemoteObject2with java-rmi-client/rmi.RMIClient3可以对比看到,从sun.rmi.server.UnicastRef#invoke起是一致的逻辑,只是上层调用来源不一样,不再赘述。区别攻击方向方法调用请求均来自 Client。但区别的产生在于sun.rmi.server.UnicastRef#invoke(java.rmi.Remote,java.lang.reflect.Method,java.lang.Object[], long)处的逻辑代码。line 79: Client 攻击 Server,在于让 Server 请求远程 Class 产生结果,由于本地同名恶意类安全所以不会对本地造成攻击。line 89: Server 攻击 Clinet,在于 Client 获取到安全结果后需要获取远程 Class 进行本地反序列化导致被攻击。with JDK 1.7.0_80with java-rmi-server/rmi.RMIServer2看情况取舍:上面说的RMI通信过程中假设客户端在与RMI服务端通信中,虽然也是在JRMP协议上进行通信,尝试传输序列化的恶意对象到服务端,此时服务端若也返回客户端一个恶意序列化的对象,那么客户端也可能被攻击,利用JRMP就可以利用socket进行通信,客户端直接利用JRMP协议发送数据,而不用接受服务端的返回,因此这种攻击方式也更加安全。这里我们针对 ysoserial 的几个相关 Class 进行分析,首先先列举下相关的作用。payloads.JRMPListener 在目标服务器目标端口上开启JRMP监听服务 – 独立利用payloads.JRMPClient 向目标服务器发送注册 Ref,目标 exploit.JRMPListener 地址exploit.JMRPListener 被动向请求方传输序列化 payloadexploit.JRMPClient 主动向目标服务器传输序列化 payload除此之外,我们还需要了解下关于DGC的一些内容,以便理解下面的内容。RMI.DGC 为 RMI 分布式垃圾回收提供了类和接口。当 RMI 服务器返回一个对象到其客户机(远程方法的调用方)时,其跟踪远程对象在客户机中的使用。当再没有更多的对客户机上远程对象的引用时,或者如果引用的“租借”过期并且没有更新,服务器将垃圾回收远程对象。payloads.JRMPListener在了解之前,我们先看下JAVA原生序列化有两种接口实现。1.Serializable接口:要求实现writeObject、readObject、writeReplace、readResolve2.Externalizable接口:要求实现 writeExternal、readExternal分析回到JRMPListener中,代码很简单,主要功能就是生成一个开启目标端口进行监听RMI服务的payload。我们首先跟入到ysoserial.payloads.util.Reflections#createWithConstructor,了解下函数逻辑。1.先查找RemoteObject下参数类型为 RemoteRef 的构造器。2.根据找到的构造器为ActivationGroupImpl动态生成一个新的构造器并生成实例。为什么需要这样呢?其实就是为了避免调用ActivationGroupImpl本身的构造方法,避免复杂的或其他不可控的问题。我们关注下UnicastRemoteObject在序列化阶段做了什么,从reexport跟入到exportObject,创建监听并返回此 stub。另外,通过上面的分析实际上我们只用需要UnicastRemoteObject就足够开启监听利用,下面两种也可以,但好奇为什么作者要通过子类转换实现利用呢?利用payloads.JRMPClient分析作为 payloads 核心代码依旧不是很多,生成 ref 并封装到 handler,动态代理Registry类。实际上,对于 ClassLoader 我们是可以设置为 Null,这个问题可以通过上面的资料链接回答。至于为什么强转为 Registry ?只是因为我们动态代理了这个类,集成了需要代理类的各种方法,在不调用这些方法时替换成任意 Object 子类均可。现在我们看下代码逻辑:当我们传递一个 proxy 准备序列化时,大意上同样会对其成员进行序列化(这里不展开,需要自己看序列化),所以会调用其父类 RemoteObject.readObject()注意到最后会调用 readExternal 方法,原因已在上文提到。这里便会调用sun.rmi.server.UnicastRef#readExternal,之后进入sun.rmi.transport.LiveRef#read,但这里并不能进入到 DGCClient 注册,但会把 ref 信息存入到ConnectionInputStream.incomingRefTable中。在最后释放输入连接时,会对incomingRefTable中的 ref 进行注册。为什么要这么做呢?java 注释写有,详细内容没有查到。而在sun.rmi.transport.DGCIm免费云主机域名pl_Skel#dispatch中也是类似注释中的流程。回到 ref 注册,实际是会在 DGCClient 中对 refs 进行注册。然后对传输过来的数据直接进行反序列化解析,这里的内容放在exploit.JRMPListener中讲解。所以整个流程分析下来,并没有看到需要使用动态代理的地方,因此生成 payload 时直接序列化传输RemoteObject子类也就足够,而原生自带的容易控制的子类为RemoteObjectInvocationHandler,即:利用payloads.JRMPClient 是要配合 exploit.JRMPListener 一起使用的。exploit.JRMPListener分清两个JRMPListener的区别payloads.JRMPListener 在目标机上开启 JMRP 监听exploit.JRMPListener 实现对 JRMP Client 请求的应答分析从 Main 可以看到基本逻辑就是开启监听 JRMP 端口等待连接后传输恶意 payload。在监听时对协议进行解析,对为 StreamProtocol、SingleOpProtocol 的连接均会通过 doMessage 进行应答。而在 doMessage 中对远程RMI调用发送 payload 数据包。那么 payload 是填充到哪里了呢?注意到 doCall 函数中的这段代码,和 cc5 的入口点是一样的。但需要注意的是,BadAttributeValueExpException.readObject的触发点不一定是 valObj.toSting(),这里在调试的时候出现了一堆莫名其妙的现象。抛开后续的利用,我们从开始看下目标是如何向 JRMPListener 请求的。会向 DGCClient 中进行注册 Ref,通过80请求、81应答进行传输,这里可以关注下调用栈,结合上面 DGC 内容进行了解。那么 80 是如何出现的呢?看到StreamRemoteCall初始化时会直接往第一个字节写入 80。接着目标会读取 Listener 传递的值对之后的内容选择是否进行反序列化,反序列化的内容就和上面连接起来了。额外提一下,var1在这里的意义是用来判断Listener是否为正常返回,如果因为某些原因在 Listener 端产生了异常报错需要将报错信息传递回请求端,而传递的信息是序列化的所以会在请求端触发反序列化。利用本身无法直接利用的,需要向目标机发送 payloads.JRMPClient 以被动攻击。exploit.JRMPClient分清两个 JRMPClient 区别,以及 RMIRegistryExploitpayloads.JRMPClient 向目标DGC注册Refexploit.JRMPClient 向目标DGC传输序列化 payloadexploit.RMIRegistryExploit 向目标RMI.Registry传输序列化 payload,目标为 RMI.Registry 监听端口下面是payloads.JRMPListener和RMI.Registry 开启的监听端口在nmap扫描下的不同信息:exploit.JRMPClient 可以对两者进行攻击;exploit.RMIRegistryExploit只能攻击后者。分析先在sun.rmi.server.UnicastServerRef#dispatch中读取 Int 数据。然后在sun.rmi.server.UnicastServerRef#oldDispatch中读取 Long 数据。之后进入sun.rmi.transport.DGCImpl_Skel#dispatch,先对读取的 Long 数据即接口 hash 值进行判断是否为相同。再根据之前读取的 Int 数据进行相应的处理。利用关于 JNDI 的内容已在整篇文章开头有涉及,此处暂时无额外需求。demowith JDK 1.7.0_17with jndirmi.RMIClient、rmi.RMIServer分析我们跟进Client执行lookup后看看发生了什么。同样也是Client向Server请求查询test_service对应的 stub,再执行到com.sun.jndi.rmi.registry.RegistryContext#decodeObject中获取目标类的 ref。之后带入 ref 到javax.naming.spi.NamingManager#getObjectInstance中进行远程工厂类的加载(所以Server 端 new Reference 时的第一个 class 参数随便写不影响)。这样就是在 Client 执行 lookup 操作时让其直接加载远程恶意类进行 RCE,不需要任何其他的 gadget。防御受到自6u141、7u131、8u121起默配置com.sun.jndi.rmi.object.trustURLCodebase=false,直接远程加载会被限制,报错信息如下:看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注云编程开发博客行业资讯频道,感谢您对云编程开发博客的支持。

相关推荐: 单态设计模式

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

(0)
打赏 微信扫一扫 微信扫一扫
上一篇 02/02 08:25
下一篇 02/02 08:25