Java安全-3.RMI

RMI

RMI (Remote Method Invocation) 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果。这里的另一个 JVM 可以在同一台计算机也可以是远程计算机。因此,RMI 意味着需要一个 Server 端和一个 Client 端。

Server 端通常会创建一个对象,并使之可以被远程访问。

这个对象被称为远程对象。Server 端需要注册这个对象可以被 Client 远程访问。

Client 端调用可以被远程访问的对象上的方法,Client 端就可以和 Server 端进行通信并相互传递信息。

说到这里,是不是发现使用 RMI 在构建一个分布式应用时十分方便,它和 RPC 一样可以实现分布式应用之间的互相通信,甚至和现在的微服务思想都十分类似。

工作原理及流程

image-20240331203155379

以上为RMI工作具体流程图

Client端在调用远程方法时首先创建Stub(sun.rmi.registry.RegistryImpl_Stub),在请求远程方法时构造信息块,其中包含:1.远程对象标识符 2.调用的方法描述 3.编组后的参数值(RMI协议使用的是对象的序列化),构造完成后将对象传递给Remote Reference Layer(java.rmi.server.RemoteRef),并且创建java.rmi.server.RemoteCall远程代用对象

此后,将RemoteCall序列化后通过Socoket连接传输给ServerRemote Reference Layer(sun.rmi.server.UnicastServerRef)),接收后传输给SkeletonSkeleton将数据反序列化,处理客户端的请求bindlistlookuprebindunbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。

Skeleton的主要工作为:1. 解析数据块的对象标识和方法描述,可以在Server端进行调用 2. 读取调用方法的返回值或者异常值 2. 将方法返回值进行编组,返回给Stub

以上流程完成,便完成一次远程调用

RMI服务端(Server+Registry)

此大体创建的为一个远程用户查询系统

1.定义用户对象,需要实现反序列化的接口,否则在传输时发出报错

package rmi.server;

import java.io.Serializable;
public class User implements Serializable{
    private static final long serialVersionUID = 6490921832856589236L;

    private String name;
    private Integer age;
    private String skill;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + "'" +
        ", age=" + age +
                ", skill='" + skill + "'" +
        '}';
    }

}

2.实现远程调用方法实体,定义需要远程调用的函数,如这里的findUser()

package rmi.server;

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

public class UserServiceImpl extends UnicastRemoteObject implements UserService {
    protected UserServiceImpl() throws RemoteException {
    }

    @Override
    public User findUser(String userId) throws RemoteException {
        if ("1".equals(userId)) {
            User user = new User();
            user.setName("Parar");
            user.setAge(19);
            user.setSkill("Web");
            return user;
        }
        throw new RemoteException("查无此人");
    }
}

3.定义服务器接口

package rmi.server;

import java.rmi.Remote;
import java.rmi.RemoteException;
public interface UserService extends Remote{
    User findUser(String userId) throws RemoteException;
}

4.最后注册远程对象,启动服务端程序,将以上类实例化并绑定到一个地址

package rmi.server;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static void main(String[] args){
        try {
            UserService userService = new UserServiceImpl();//实例化
            LocateRegistry.createRegistry(1900);        //创建并运行RMI Registry
            Naming.rebind("rmi://localhost:1900/user", userService);//将userService绑定到user这个名字上
            System.out.println("RMIServer is running. port is 1900");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

启动后显示RMIServer is running. port is 1900

image-20240401082802542

RMI客户端

Client需要做的只是引入可远程访问和需要传输的类,通过Server端绑定的地址与接口,即可完成一次调用

package rmi.client;

import java.rmi.Naming;
import rmi.server.User;
import rmi.server.UserService;

public class RMIClient {
    public static void main(String[] args){
        User answer;
        String UserID = "1";
        try {
            // lookup method to find reference of remote object
            UserService access = (UserService)Naming.lookup("rmi://localhost:1900/user");
            answer = access.findUser(UserID);
            System.out.println("query:" + UserID);
            System.out.println("result:" + answer);
        } catch (Exception ae) {
            System.out.println(ae);
        }
    }
}

在Server端运行的基础上,运行Client

image-20240401083331770

(踩坑经历)

在初次编写时,认为远程调用二者无关,于是将ServerClient端写在了两个目录下,但虽然执行远程方法的时候代码在远程服务器上执行,但也需要实际知道有哪些方法。因此最后在这里写在相同目录的不同包下。

利用codebase执行任意代码

codebase作用

codebase是一个地址告诉虚拟机在哪里搜索类,如:codebase=http://example.com/,然后加载org.vulhub.example.Example类,在RMI流程中,客户端和服务端传递的是序列化后的对象,在这些对象反序列时就会去寻找类,寻找时会先在ClassPath下寻找,然后在codebase中寻找。因此这里JVM就会去下载:http://example.com/org/vulhub/example/Example.class文件

在RMI中,客户端和服务端之间传递序列化后的对象,在反序列化时。就会去寻找类。当在反序列化时发现一个对象,那么首先就会在自己的CLASSPATH下寻找对应的类,当本地没有时,就会根据codebase进行加载远程的类

因此控制codebase,设置为恶意类,将codebase随着序列化的数据一起进行传输,服务器接受到数据进行寻找,因而加载恶意类

利用前提:

  • 安装并配置了SecurityManager

  • Java版本低于7u21、6u45,或者设置了 java.rmi.server.useCodebaseOnly=false
java -Djava.rmi.server.hostname=192.168.135.142 -Djava.rmi.server.useCodebaseOnly=false -dDjava.security.policy=client.policyRemoteRMIServer

java -Djava.rmi.server.hostname=192.168.135.142 -Djava.rmi.server.useCodebaseOnly=false -Djava.security.policy=client.policyRemoteRMIServer

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇