LOADING...

dadada~

loading

Java-RMI


  • RMI全称是Remote Method Invocation,远程方法调用,从这个名字就可以看出,他的目标和RPC其实是类似的,是让某个Java虚拟机上的对象调⽤另⼀个Java虚拟机中对象上的⽅法,只不过RMI是Java独有的⼀种机制

  • RMI Server:

    package aaaa.rmi;
    
    import java.rmi.Naming;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    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();
        }
    }
    
  • ⼀个RMI Server分为三部分:

    • ⼀个继承了java.rmi.Remote的接⼝,其中定义我们要远程调⽤的函数,比如这里的hello()
    • ⼀个实现了此接⼝的类
    • ⼀个主类,⽤来创建Registry,并将上⾯的类实例化后绑定到⼀个地址,这就是所谓的Server了
  • RMI Client:

    package aaaa.rmi;
    
    import java.rmi.Naming;
    import java.rmi.NotBoundException;
    import java.rmi.RemoteException;
    
    public class RMIClient {
        public static void main(String[] args) throws Exception {
            RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)
                    Naming.lookup("rmi://192.168.128.178:1099/Hello");
            String ret = hello.hello();
            System.out.println( ret);
        }
    }
    
  • 使⽤Naming.lookupRegistry中寻找到名字是Hello的对象,后面的使用就和在本地使用⼀样了

  • 一个RMI过程有以下三个参与者:RMI RegistryRMI ServerRMI Client

  • 通常我们在新建一个RMI Registry的时候,都会直接绑定一个对象在上面,也就是说我们示例代码中的Server其实包含了RegistryServer两部分

    LocateRegistry.createRegistry(1099);  //创建并运行RMI Registry
    Naming.bind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld());  //行将RemoteHelloWorld对象绑定到Hello这个名字上
    
  • Naming.bind的第一个参数是一个URL,形如:rmi://host:port/name,其中,hostport就是RMI Registry的地址和端口,name是远程对象的名字

  • 如果RMI Registry在本地运行,那么hostport是可以省略的,此时host默认是localhostport默认是1099

    Naming.bind("Hello", new RemoteHelloWorld());
    

攻击RMI Registry

  • Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebindbindunbind等方法

  • 不过listlookup方法可以远程调用,list方法可以列出目标上所有绑定的对象:

    String[] s = Naming.list("rmi://192.168.128.178:1099");
    
  • lookup作用就是获得某个远程对象,那么,只要目标服务器上存在一些危险方法,通过RMI就可以对其进行调用

RMI利用codebase执行任意代码

  • Java Applet是可以运行在浏览器中的,在使用Applet的时候通常需要指定一个codebase属性:

    <applet code="HelloWorld.class" codebase="Applets" width="800" height="600">
    </applet>
    
  • 除了AppletRMI中也存在远程加载的场景,也会涉及到codebasecodebase是一个地址,告诉Java虚拟机应该从哪个地方去搜索类,有点像
    CLASSPATH,但CLASSPATH是本地路径,而codebase通常是远程URL,比如httpftp

  • 如果指定codebase=http://example.com/,然后加载org.vulhub.example.Example类,则Java虚拟机会下载这个文件http://example.com/org/vulhub/example/Example.class,并作为Example类的字节码

  • RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类,如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类

  • 如果codebase被控制,就可以加载恶意类了

  • 在RMI中,可以将codebase随着序列化数据一起传输,服务器在接收到这个数据后就会去CLASSPATH和指定的codebase寻找类,由于codebase被控制导致任意命令执行漏洞

  • 漏洞条件:

    • 安装并配置了SecurityManager
    • Java版本低于7u21、6u45,或者设置了java.rmi.server.useCodebaseOnly=false
    • java.rmi.server.useCodebaseOnly配置为 true 的情况下,Java虚拟机将只信任预先配置好的codebase,不再支持从RMI请求中获取
  • 看着P神的文章想跟着复现一下不知道抽什么风一直报错,放弃了,我选择嗯看

  • 研究一下他的源码,大概意思就是,先起一个RMIServer,在编译运行的时候设置三个参数:

    java.rmi.server.hostname=192.168.135.142 //是服务器的IP地址,远程调用时需要根据这个值来访问RMI Server
    java.rmi.server.useCodebaseOnly=false //支持从RMI请求中获取Codebase
    java.security.policy=client.policy //授权
    
  • 再建立一个RMIClient.java,在另一个位置运行,此时RMI Server在本地CLASSPATH里找不到类,会去加载Codebase中的类

    import java.rmi.Naming;
    import java.util.List;
    import java.util.ArrayList;
    import java.io.Serializable;
    public class RMIClient implements Serializable {
        public class Payload extends ArrayList<Integer> {}
        public void lookup() throws Exception {
            ICalc r = (ICalc)
                    Naming.lookup("rmi://192.168.135.142:1099/refObj");
            List<Integer> li = new Payload();
            li.add(3);
            li.add(4);
            System.out.println(r.sum(li));
        }
        public static void main(String[] args) throws Exception {
            new RMIClient().lookup();
        }
    }
    
  • 查看example.com的日志,慧收到了来自Java的请求/RMIClient$Payload.class,因为还没有实际放置这个类文件,所以会出现异常

  • 只需要编译一个恶意类,将其class文件放置在Web服务器的/RMIClient$Payload.class即可