文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox-Retired

HackTheBox-Active

VulnHub

代码审计

PHP代码审计

大数据安全

机器学习

基础学习

Python

Python基础

Python安全

Java

Java基础

Java安全

算法

Leetcode

随笔

经验

技术

 2021-03-13   1.3k

Java安全学习之RMI基础

参考:

java安全漫谈-04.RMI篇(1)

深入理解RMI原理

先知——RMI-反序列化

Seebug——Java 安全-RMI-学习总结

浅显易懂的JAVA反序列化入门

【入坑JAVA安全】RMI基础看这一篇就足够了

Java安全之RMI反序列化

前置知识:

RPC、反射、动态代理

0x01 RMI是什么

RMI(Remote Method Invocation),是一种跨JVM实现方法调用的技术。

在RMI的通信方式中,由以下三个大部分组成:

  • Client:客户端调用服务端的方法
  • Registry:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果
  • Server:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。

其中Client是客户端,Server是服务端,而Registry是注册中心。

客户端会Registry取得服务端注册的服务,从而调用服务端的远程方法。

注册中心在RMI通信中起到了一个什么样的作用?我们可以把他理解成一个字典,一个负责网络传输的模块。

服务端在注册中心注册服务时,需要提供一个key以及一个value,这个value是一个远程对象,Registry会对这个远程对象进行封装,使其转为一个远程代理对象。当客户端想要调用远程对象的方法时,则需要先通过Registry获取到这个远程代理对象,使用远程代理对象与服务端开放的端口进行通信,从而取得调用方法的结果。

0x02 RMI基础使用

Server部署:

  1. Server向Registry注册远程对象,远程对象绑定在一个//hostL:port/objectname上,形成一个映射表(Service-Stub)。

Client调用:

  1. Client向Registry通过RMI地址查询对应的远程引用(Stub)。这个远程引用包含了一个服务器主机名和端口号。
  2. Client拿着Registry给它的远程引用,照着上面的服务器主机名、端口去连接提供服务的远程RMI服务器
  3. Client传送给Server需要调用函数的输入参数,Server执行远程方法,并返回给Client执行结果。

先定义一个远程接口:

1
2
3
4
5
6
7
8
package com.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface rmidemo extends Remote {
public String hello() throws RemoteException;
}

在定义远程接口的时候需要继承java.rmi.Remote接口,并且修饰符需要为public否则远程调用的时候会报错。并且定义的方法里面需要抛出一个RemoteException的异常。

再编写一个远程接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteHelloWorld extends UnicastRemoteObject implements rmidemo{


protected RemoteHelloWorld() throws RemoteException {
System.out.println("构造方法");
}

public String hello() throws RemoteException {
System.out.println("hello方法被调用");
return "hello,world";
}
}

在编写该实现类中需要将该类继承UnicastRemoteObject

接着创建服务器实例,并且创建一个注册表,将需要提供给客户端的对象注册到注册到注册表中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.rmi;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class servet {
public static void main(String[] args) throws RemoteException {
rmidemo hello = new RemoteHelloWorld();//创建远程对象
Registry registry = LocateRegistry.createRegistry(1099);//创建注册表
registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello

}
}

到了这一步,简单的RMI服务端的代码就写好了。下面来写一个客户端调用该远程对象的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.rmi.rmiclient;

import com.rmi.RemoteHelloWorld;
import com.rmi.rmidemo;

import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class clientdemo {
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("localhost", 1099);//获取远程主机对象
// 利用注册表的代理去查询远程注册表中名为hello的对象
rmidemo hello = (rmidemo) registry.lookup("hello");
// 调用远程方法
System.out.println(hello.hello());
}
}

在这一步需要注意的是,如果远程的这个方法有参数的话,调用该方法传入的参数必须是可序列化的。在传输中是传输序列化后的数据,服务端会对客户端的输入进行反序列化。

参考文章中有关于对RMI过程源码分析或者流量分析的文章,我这里就摘取总结性的段落。

所以捋⼀捋这整个过程,⾸先客户端连接Registry,并在其中寻找Name是hello的对象,这个对应数据流中的Call消息;然后Registry返回⼀个序列化的数据,这个就是找到的Name=Hello的对象,这个对应数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是⼀个远程对象,地址在 192.168.135.142:33769 ,于是再与这个地址建⽴TCP连接;在这个新的连接中,才执⾏真正远程⽅法调⽤,也就是hello()。

0x03 RMI攻击方式

待填坑,等学完反序列化再来。

Copyright © ca01h 2019-2021 | 本站总访问量