rmi_jndi
简介
RMI(Remote Method Invocation)即Java远程方法调用,RMI用于构建分布式应用程序,RMI实现了Java程序之间跨JVM的远程通信。

(来自[RMI · 攻击Java Web应用-Java Web安全] (javasec.org))
RMI客户端在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)。Stub会将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象。RemoteCall序列化RMI服务名称、Remote对象。RMI客户端的远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端的远程引用层。RMI服务端的远程引用层(sun.rmi.server.UnicastServerRef)收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)。Skeleton调用RemoteCall反序列化RMI客户端传过来的序列化。Skeleton处理客户端请求:bind、list、lookup、rebind、unbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。RMI客户端反序列化服务端结果,获取远程对象的引用。RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。RMI客户端反序列化RMI远程方法调用结果。
造成漏洞的真正原因:
当 RMI Registry 接收到客户端的
bind,rebind或lookup等请求时,它会自动反序列化客户端发送过来的对象数据。如果服务器端的 classpath 中恰好存在一个含有漏洞的第三方库(例如本例中的commons-collections),那么攻击者就可以构造一个恶意的序列化对象,在服务器反序列化它时触发漏洞,执行任意代码。也就是说当bind,或者吧rebind,lookup 的时候都会出现
- RMI Registry 是一栋大楼前台的访客登记簿。
- RMI Server 是一家公司,它派了一名员工(创建的
Remote对象)来这栋大楼提供服务。这名员工需要到前台,在登记簿上写下:“我是‘计算服务’公司的,找我请拨分机号 XXX”。这个“登记”的动作就是bind()。 - RMI Client 是一个访客,他想找“计算服务”。他来到前台,翻开登记簿(
lookup("计算服务")),找到了分机号 XXX,然后直接联系这位员工。
漏洞就出在这个“登记簿”上:
这个登记簿(RMI Registry)默认是完全开放的,它不会验证前来登记的人(调用
bind/rebind的程序)的身份。任何能够通过网络连接到前台的人,都可以在登记簿上写下任何内容。
谁都可以注册
rmi 流程
但是这个地方rmi 连接出现了很大的问题,导致一直连接不上。
但吃尝试了exp 又可以实现攻击…..

下面是部分代码
RMIServerTest.java
1 | |
RMIClientTest.java
1 | |
RMITestImpl.java
1 | |
RMITestInterface.java
1 | |
rmi 反序列化
RMI通信中所有的对象都是通过Java序列化传输的,在学习Java序列化机制的时候我们讲到只要有Java对象反序列化操作就有可能有漏洞。
既然RMI使用了反序列化机制来传输Remote对象,那么可以通过构建一个恶意的Remote对象,这个对象经过序列化后传输到服务器端,服务器端在反序列化时候就会触发反序列化漏洞。
首先我们依旧使用上述com.anbai.sec.rmi.RMIServerTest的代码,创建一个RMI服务,然后我们来构建一个恶意的Remote对象并通过bind请求发送给服务端。
RMI客户端反序列化攻击示例代码:
1 | |
最根本的问题还是利用了cc1 中的AnnotationInvocationHandler 类
Apache_Commons_Collections1分析 - AU9U5T
sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMap中MapEntry的setValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。
程序执行后将会在RMI服务端弹出计算器(仅Mac系统,Windows自行修改命令为calc),RMIExploit程序执行的流程大致如下:
- 使用
LocateRegistry.getRegistry(host, port)创建一个RemoteStub对象。 - 构建一个适用于
Apache Commons Collections的恶意反序列化对象(使用的是LazyMap+AnnotationInvocationHandler组合方式)。 - 使用
RemoteStub调用RMI服务端的bind指令,并传入一个使用动态代理创建出来的Remote类型的恶意AnnotationInvocationHandler对象到RMI服务端。 RMI服务端接受到bind请求后会反序列化我们构建的恶意Remote对象从而触发Apache Commons Collections漏洞的RCE。
问题
为什么在exp 中没有实现remote 还可以bind?
因为通过了动态代理的方式实现了remote
1
2
3
4// 使用动态代理创建出Remote类型的Payload
Remote remote = (Remote) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), new Class[]{Remote.class}, annHandler
);Java 的
Proxy.newProxyInstance()方法是一个强大的工具,它允许你在程序运行时创建一个“代理”对象,这个代理对象可以实现任何你指定的接口组合。
RMI-JRMP 反序列化漏洞
JRMP接口的两种常见实现方式:
JRMP协议(Java Remote Message Protocol),RMI专用的Java远程消息交换协议。IIOP协议(Internet Inter-ORB Protocol),基于CORBA实现的对象请求代理协议。
由于RMI数据通信大量的使用了Java的对象反序列化,所以在使用RMI客户端去攻击RMI服务端时需要特别小心,如果本地RMI客户端刚好符合反序列化攻击的利用条件,那么RMI服务端返回一个恶意的反序列化攻击包可能会导致我们被反向攻击。
我们可以通过和RMI服务端建立Socket连接并使用RMI的JRMP协议发送恶意的序列化包,RMI服务端在处理JRMP消息时会反序列化消息对象,从而实现RCE。
1 | |