Java安全--RMI基础学习
Contents
RMI定义
- Java远程调用,实现远程调用的应用程序编程接口。
- RMI对象是通过序列化方式进行编码传输的。
- Java程序远程调用另一台服务器的java对象。
- RMI依赖的通信协议 JRMP。
RMI实现流程
1.创建接口
在创建对象类之前,我们首先需要创建一个空接口,接口需要继承java.rmi.Remote
。
1 | import java.rmi.RemoteException; |
2.实现接口
接着我们实现这个接口,创建服务端对象类,实现的类必须继承UnicastRmeoteObject。
1 | import java.rmi.RemoteException; |
3.创建服务端&&注册中心
创建一个RMI服务端,服务端和客户端需要有共同的接口。然后创建注册中心,启动 RMI
的注册服务。server端将实例化的服务端远程对象绑定到registry
1 |
|
注意:低版本的JDK中,server服务端和register注册中心可以不在一台服务器上,高版本则只能在一台服务器上。
4.创建客户端
客户端与server和registry交互。
1 | package me.mole.javarmi; |
我们在客户端这里创建一个恶意的命令执行的类VulObject。
1 | import java.io.IOException; |
本地获取注册中心(反序列化点)
获取注册中心的两种方式。
- 创建时获取:LocateRegistry#createRegistry
- 远程获取:LocateRegistry#getRegistry
无论是客户端还是服务端,最终其调用注册中心的方法都是通过对创建的RegistryImpl对象进行调用。
我们这里分析下调用 LocateRegistry
类的 getRegistry
方法。
调用通过getRegistry
方法得到的RegistryImpl_Stub
的 bind
方法。
这里首先通过newCall方法调用 TCPChannel 类的 createConnection 方法创建 socket 连接和注册服务通信。
然后通过writeObject方法先后写入bind方法序列化的参数值。
然后通过调用serviceCall 方法,获取到dispatcher,最后调用registry.RegistryImpl_Skel类的dispatch方法。
var3是传递过来的int类型的参数,在这里有如下关系的对应:
- 0->bind
- 1->list
- 2->lookup
- 3->rebind
- 4->unbind
根据参数来决定服务端与客户端调用的方法。这个过程中基于序列化和反序列化来进行通讯的。那么我们就可以寻找反序列化的点来进行攻击。
调用rmi执行反序列化攻击
首先启动注册服务,然后执行服务端,最后执行客户端。可以发现客户端能够成功调用服务端上的方法,实现远程方法调用。
总结流程
- 服务端Clockmpl()继承Clock()创建对象。
- 服务端CLock()注册远程对象
- 客户端访问服务器b并查找相应远程对象。
- 服务端将stub(存根返回)客户端
- 客户端调用stub(存根)的方法
- stub(存根)作为代理与服务端骨架通信//骨架作为服务端代理。
- 骨架代理调用Clockmpl相应方法。
- 骨架将结果返回给客户端的存根
- 存根返回给客户端。
P牛对注册中心的解释
RMI Registry就像⼀个⽹关,他⾃⼰是不会执⾏远程⽅法的,但RMI Server可以在上⾯注册⼀个Name 到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server;最后,远程⽅法实际上在RMI Server上调用。
插一张先知的流程图
RMI攻击手法
先知社区上的一些总结,上图:
大致可以分为以下四类:
- 探测利用开放的RMI服务。
- 基于RMI服务反序列化过程的攻击。
- 利用RMI的动态加载特性的攻击利用。
- 结合JNDI注入。
我们主要学习RMI结合反序列化攻击的相关内容。
基于RMI服务反序列化过程的攻击
RMI反序列化漏洞的存在必须包含两个条件:
- 能够进行RMI通信
- 目标服务器引用了第三方存在反序列化漏洞的jar包
注:复现的时候需要JDK8 121以下版本,121及以后加了白名单限制。
利用RMI的动态加载特性的攻击利用
codebase
1 | <applet code="HelloWorld.class" codebase="Applets" width="800" height="600"> |
codebase是一个地址,告诉Java虚拟机我们应该从哪个地方去搜索类;CLASSPATH是本地路径,而codebase通常是远程URL,比如http、ftp等。所以动态加载的class文件可以保存在web服务器、ftp中。
如果我们指定 codebase=http://example.com/ ,动态加载 org.vulhub.example.Example 类,
则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class,并作为 Example类的字节码。
在RMI中,我们可以通过codebase随着序列化数据一起传输的,服务器在接收到这个数据后就会去 CLASSPATH和指定的codebase寻找类,由于codebase被控制导致任意命令执行漏洞。
但是相对而言这种限制条件很严:
- 安装并配置了SecurityManager
- Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false
这里使用这位师傅打包好的代码学习:https://github.com/fa1c0n1/rmi-attack-demo
客户端动态加载
- 创建HTTP服务器,作为动态加载代码的远程仓库。
1 | python -m http.server 8000 |
- 服务端创建远程对象,
RMI Registry
启动并完成名称绑定,并设置java.rmi.server.codebase
。
- 客户端对
RMI Registry
发起请求,根据提供的Name
得到Stub
,并根据服务器返回的java.rmi.server.codebase
远程加载动态所需的类。
服务端动态加载
恶意的客户端代码:
受害服务端代码:
结合JNDI注入
放到后面再细说。。(学晕了)
参考文章
https://paper.seebug.org/1091/
https://www.bookstack.cn/read/anbai-inc-javaweb-sec/javase-RMI-README.md#6mltu7
Author: Shu1L
Link: https://shu1l.github.io/2021/02/09/java-an-quan-rmi-ji-chu-xue-xi/
License: 知识共享署名-非商业性使用 4.0 国际许可协议