java安全-CC1
Java安全-CC1
环境搭建
1 | oracle_jdk8u65 |
将 openjdk 中的 sun 包拷贝到 Oracle_jdk 目录下 src 包中实现在调试的过程中查看源码,maven的源码点击下载就好
这个链主要利用的是org/apache/commons/collections/Transformer.java接口所实现的类

它其中只有一个重要的方法需要重写,transform方法
一、主要链逻辑
1.危险方法调用
Transformer接口的实现类ChainedTransformer重写的Transform方法
1 | Class r = Runtime.class; |
该类调用transform方法会触发命令执行
2.链接
- 寻找外部类中调用
transform方法的类

这里主要用到了TransformedMap这个类
内部的调用逻辑主要为下面几个方法
1 | org/apache/commons/collections/map/TransformedMap.java |
目前追踪到setValue方法,而这个方法的目的是
- 拦截“通过 Map.Entry 直接改值”的行为,让它也必须先经过父 Map 的校验/转换,再真正写进去。
其实,这个setValue方法本质也就是Map集合中Entry对象的一个setValue方法的重写
所以我们在遍历一个被ChainedTransformer(chuan)修饰的集合获取到Entry时,便可以调用setValue方法
刚好,1中valueTransformer这个类可以在保护构造器中传参控制

而下面这个decorate方法刚好是公开的并且返回一个TransformedMap

利用
1 | Map<Object, Object> map = new HashMap<>(); |

3.寻找入口
我们现在已经实现了,通过一个遍历数组,获取到Entry对象,我们最后只需要有一个反序列化类,这个类的readObject方法会遍历数组调用setValue方法

就是它!!看到sun/reflect/annotation/AnnotationInvocationHandler.java这个类中重写的 readObject 方法调用了 setValue 方法
1 | private void readObject(java.io.ObjectInputStream s) |
尝试构造这个类并且进行反序列化
构造方法具体代码如下

这个类参数为一个注解和一个Map集合,刚好可以将我们装饰的 transformedMap 传递,但这个构造方法是default的,所以只能通过反射去获取并且实例化,如下
1 | Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); |
但是依旧有一些条件需要满足

这个整体需要满足确认注解存在
memberTypes = annotationType.memberTypes();,他是由annotationType = AnnotationType.getInstance(type);获取的,AnnotationType.getInstance(type)的作用一句话概括:给某个“注解接口 Class”(比如
Target.class)创建/获取一份“运行期注解元数据缓存”,里面包含这个注解有哪些成员、每个成员应该是什么返回类型、默认值是什么、Retention/Inherited 是什么,用来 校验反序列化出来的注解值、以及 动态代理调用成员方法时的行为。也就是说它缓存了:
- memberTypes:成员名 → “代理调用时应该返回的类型”
- members:成员名 → 反射 Method(主要用于拼异常信息)
- memberDefaults:成员名 → 默认值
其中
memberTypes
- key:成员名(也就是注解里那些无参方法的名字,比如
value()的名字就是"value") - value:这个成员应该返回的类型
Class<?> memberType = memberTypes.get(name);通过我们传递数组的 key 来确认注解存在1
2
3
4
5
6
7
8
9
10Map<Object, Object> map = new HashMap<>();
map.put("value", "yy");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, new ChainedTransformer(chuan));
// for(Map.Entry<Object,Object> entry : transformedMap.entrySet()){
// entry.setValue(r);
// }
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);我们传递一个 Target 注解,集合第一个键值对 key 为 value ,他在之后成为name

这里
memberValue.setValue的需要被控制为我们的链对象这里我们用到了
ConstantTransformer类,使其无论参数无论是啥,都不管,第一层返回的都是Runtime.class1
2Class r = Runtime.class;
Transformer chaun0 = new ConstantTransformer(r);
成功打通

二、Transformer重要实现类
1.InvokerTransformer类
InvokerTransformer.java这个类中的transform用来实现危险方法调用

1 | public class InvokerTransformer implements Transformer, Serializable { |
这个方法为利用构造方法控制:
iMethodName:要调用的方法名iParamTypes:方法参数类型数组,类型为Class[]iArgs:实际参数数组,类型为Object[]- 以及
input:transform 被喂进来的对象是什么
那么 transform(input) 就不再是“固定逻辑”,而是:
对 input 调用任意方法,并传入任意参数(类型要匹配)。
1 | Runtime runtime = Runtime.getRuntime(); |

以此我们实现使用该类进行调用系统命令,但依旧有一个问题,就是Runtime类是无法进行序列化与反序列化的,故因此,我们也利用该类获取Runtime类
Class对象是可以进行反序列化的,它实现了Serializable接口
我们可以获取
Runtime.class然后调用Runtime.class.getClass()获取**“这个对象”的运行时类型对应的 **Class对象,然后,利用它调用getMethod方法,然后利用其获取Runtime对象,之后在连接初始的链。这里为啥获取class的class在获取getMethod,而不是直接获取grtRuntime方法呢,是因为这个恶意命令加载影响,传进来的Class一定会经过一次getClass,所以导致不能直接获取Runtime.class下的getRuntime方法
本质上是因为这里直接获取了方法并invoke,我们想要传Class对象,就只能用getMethod中转一下,第二个获取invoke方法同理!

所以最终
1 | Method transformer1 = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}).transform(Runtime.class); |
需要注意
Method.invoke(Object obj, Object... args) 的第二个参数
直接调用目标方法时:可以不写
1 | Method m = Runtime.class.getMethod("getRuntime"); |
但如果你是“通过反射去调用 invoke 本身”:第二个参数不能不写
1 | Method INVOKE = Method.class.getMethod("invoke", Object.class, Object[].class); |
2.ChainedTransformer类
org/apache/commons/collections/functors/ChainedTransformer.java这个类我们用来优化上面重复的代码。

1 | public Object transform(Object object) { |
这是一个实例方法,名字也叫
transform入参和返回值都是
Object:说明它不限定具体类型,啥都能传/返回
1 | for (int i = 0; i < iTransformers.length; i++) { |
iTransformers是一个数组(Transformer[])这段循环就是:从第 0 个 transformer 一直跑到最后一个
1 | object = iTransformers[i].transform(object); |
核心:调用第 i 个 transformer 的 transform 方法
把当前
object丢进去,得到一个新对象再把这个新对象赋值回
object——
“覆盖”意味着你只保留最新的结果,中间结果不再保留
1 | } |
- 循环结束时,
object已经是最后一个 transformer 处理后的结果
优化链
1 | Class r = Runtime.class; |
这样,我们就实现了,只需要传递一个Transformer数组到ChainTransformer对象然后寻找链调用transform即可
3.ConstantTransformer类
org/apache/commons/collections/functors/ConstantTransformer.java这个类我们把它当作常量类
他的transform方法是返回一个常量类,而且这个类可控

这个方法无论,传入的参数是什么,返回的都是它自己的一个属性iConstant
而这个input可控,通过构造方法就可以实现

所以我们构造一个类,让他调用transform方法时无论参数是啥都返回Runtime类
1 | Class r = Runtime.class; |
三、回顾
最终的漏洞 poc 如下
1 | Class r = Runtime.class; |
大概的链路调用逻辑
1 | InvokerTransformer-transform() |
四、Commons Collection包
它是 JDK 自带 java.util.\* 的“增强包”,补了一堆更好用的数据结构、装饰器(decorator)、以及“函数式回调”(Transformer/Predicate/Closure/Factory)工具。官方定位就是“基于并受 JDK 集合框架启发的一套集合类库”。
1.核心
根包通常放的是 接口 + 工具类,给下面的子包(bag/map/iterators/functors…)提供统一抽象。
A. 四个“函数式回调”接口(3.x 很标志性)
这些接口在 3.x 里非常常见,用来把“逻辑”当对象传来传去:
- Predicate:
boolean evaluate(Object)(过滤用) - Transformer:
Object transform(Object)(把 A 变 B) - Closure:
void execute(Object)(对元素做动作) - Factory:
Object create()(延迟创建对象)
这些实现大量集中在 functors 包。
B. 常用工具类
CollectionUtils:集合并/交/差、判空、计数等(比如“统计每个元素出现次数”)。MapUtils、SetUtils、ListUtils、IteratorUtils:各种“补 JDK 缺口”的小工具(4.x/3.x 都有类似思想,名字也很像)。
3.分工
(1)functors:回调/函数对象实现
提供 Predicate/Transformer/Closure/Factory 的一堆现成实现,比如“链式 transformer”、“switch transformer”等。
(2)bag:Bag/Multiset(“带计数的集合”)
像 Python 的 Counter:同一个元素可以出现多次,并能直接拿到出现次数。包内有 HashBag、TreeBag 等。 [
(3)bidimap:BidiMap(双向 Map)
既能 key -> value,也能 value -> key 反查(值也要求能唯一映射)。有 DualHashBidiMap、TreeBidiMap 等。
(4)buffer:Buffer / FIFO / PriorityBuffer
提供一些队列/缓冲相关结构,比如 UnboundedFifoBuffer、CircularFifoBuffer 等。
(5)collection / map / list / set 等:装饰器(Decorator)家族
这一类非常多,典型思路是**“包装一个现有集合,并在 add/put 时做额外约束”**:
- PredicatedXxx:只允许满足 Predicate 的元素进入
- TransformedXxx:放进去前先做 transform
- TypedXxx:运行期做类型检查(在没泛型的年代很有用)
- SynchronizedXxx / UnmodifiableXxx:加锁/只读包装
安全方向的 commons-collections
库本身不是“会自动 RCE”,问题出在“应用把不可信数据做 Java 反序列化(ObjectInputStream.readObject())”。 3.2.1 里 functors 的一些可序列化类,历史上被滥用去拼“gadget chain”,导致在特定反序列化场景下触发任意代码执行风险(著名 CVE 在 2015 年集中爆发)。
Apache 在 3.2.2 之后对 functors 包里“被认为不安全的类”做了处理:在序列化/反序列化时直接抛 UnsupportedOperationException 来降低被利用的风险。