
CVE-2020-2551 POC to use in Internet
Test POC (can be used for external network testing)
python CVE-2020-2551.py [HOST] [IP]
Attempt to modify codebase for remote class loading (failed)
python CVE-2020-TEST.py [HOST] [IP]
(First published on Anquanke, original link)
Learning this vulnerability requires some prerequisite knowledge, such as CORBA and RMI.
A brief overview:
CORBA is a set of technical standards formulated by OMG for distributed applications, which uses IDL for cross-language support, and communication between client and server uses the IIOP protocol.
RMI is another distributed application technology. In Java, JNDI can be used to simplify its application. The client and server communicate using the JRMP protocol. However, in weblogic, RMI uses the T3 protocol, for which many vulnerabilities have been reported before.
RMI-IIOP combines the advantages of RMI and CORBA, deploying RMI applications via the IIOP protocol.
The official documentation also mentions:
RMI server objects can use the IIOP protocol and communicate with CORBA client objects written in any language
Leaving weblogic aside for now, let's first focus on how to write an RMI-IIOP example:
Client code can refer to the test project in Java: RMI, JNDI, LDAP, JRMP, JMX, JMS (Part 1). You can compile HelloClient and HelloServer yourself, or use the pre-compiled ones from the test project.
Start the naming server (built-in in Java) from the command line:
start orbd -ORBInitialPort 1050
Start the server-side HelloServer from the command line and configure remote debugging. For how to perform remote debugging with IDEA, refer to the method mentioned at the beginning of this article:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
Of course, you can also just run it directly without remote debugging to see the result. Simply start:
java HelloServer
Start the client from the command line:
Java HelloClient
At this point, the calculator will pop up. If remote debugging is successful, you can see the following call stack:
Command execution in EvilMessage.readObject():

Side note: Articles about weblogic installation and debugging.
Now, what about RMI-IIOP in weblogic? The article About RMI-IIOP in Java mentions exploitation of weblogic RMI-IIOP. Based on it, further research was done. Using WebLogic’s RMI over IIOP discusses several ways for weblogic to use RMI-IIOP clients, including:
The difference between the first two methods seems to be only the JNDI_FACTORY setting:

When studying the weblogic T3 deserialization earlier, I deployed a HelloServer application on weblogic, which has a sayhello() method that can be exploited. I tried setting two different JNDI_FACTORY values and successfully invoked the sayHello() method using the second JNDI_FACTORY:
Then I modified the weblogic T3 protocol POC, simply changing RMI to IIOP. The jtaTransactionManager exploitation chain executed successfully, sending a jrmp request to the local jrmplisten:

Looking at the traffic, when the remove() method is called, a remove__java_lang_Object request is sent. Malicious data is present in the traffic, but the aced magic header is not found:

It is speculated that the server performs special parsing before deserializing the data. Looking at the call stack, the latter part of the execution chain is very similar to the native RMI-IIOP execution chain. The former part is triggered from CDRInputStream.read_value(), while here it is triggered from weblogic's IIOPInputStream.read_value(). (The read_value point was also mentioned in the 2019 Black Hat talk)

Here, the request first goes to clusterableServerRef.invoke() for handling. Based on different invokers, it calls this.invoker.invoke(). Then it calls Mejb_dj5nps_HomeImpl_WLSkel.invoke(). Because it is "remove", it enters case 6 and calls IIOPInputStream.readObject(). In the read_value() method, it parses the IIOPInputStream data and triggers deserialization. This is the POC for exploiting the remove() method.
Lucifaer's analysis [article](https://lucifaer.com/2020/02/25/WebLogic WLS核心组件RCE分析(CVE-2020-2551)/?from=timeline&isappinstalled=0#2-2-Weblogic解析流程) mentions exploiting the bind() method, which is also the mainstream exploitation method on the internet. Let's trace the call stack:

Similar to before, the request first goes to clusterableServerRef.invoke(). Based on different invokers, it calls this.invoker.invoke(). Here it calls CobraServerRef.invoke(), and then in _NamingContextAnyImplBase._invoke(), because va1 is "bind_any", it enters case 0 and calls IIOPInputStream.read_any(). Later, it calls IIOPInputStream.read_value() to trigger deserialization. Earlier, I said that the aced magic header was not seen in the traffic. This is because there is a set of parsing methods in IIOPInputStream. The hex-value form of IIOPInputStream is as follows, which includes the class name and field information:

Eventually, it calls the readObject() method of the malicious class:

Looking at the patch, it was found to be in the same location as the patch for the 2015 T3 deserialization exploit:

In the tests of WebLogic CVE-2020-2551 Vulnerability Analysis, it was seen that the filtered class location for CVE-2020-2551 is also in the weblogic.iiop.Utils class:

However, during local testing, weblogic 10.3.6 with the 2015 patch did not trigger the isBlacklisted() function (but in MsgAbbrevInputStream and InboundMsgAbbrev, isBlacklisted() is called for blacklist verification... strange):

The patch for this CVE-2020-2551 added the verifyClassPermitted() method for filtering in weblogic.iiop.Utils.LoadClass():

The blacklist filters malicious classes, including the parent class of JtaTransactionManager, com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager, which is built-in to weblogic and very dangerous. When looking at the patch, one idea came to mind: because the verification on line 606 occurs after LoadClass(), if the class is loaded during the loading of className and executes malicious static code blocks, wouldn't that bypass the defense? More on this later.
POCs written in Java have network issues; they work when targeting a local weblogic service but fail when targeting Docker containers or external machines. Analysis articles on this issue:
Step-by-step guide to solving WebLogic CVE-2020-2551 POC network problem
Discussion on WebLogic CVE-2020-2551
Let's debug the POC. You can refer to the earlier remove() POC, or Y4er's. The two articles mentioned two solutions:
I tried both. Repackaging weblogic caused a java.lang.NoSuchMethodError: weblogic.security.subject.SubjectManager.installCESubjectManager error, but I couldn't find a solution:
So I tried simulating the IIOP protocol. First, set a breakpoint in the POC for debugging:

It was discovered that when calling new InitialContext(env), EndPointImpl.sendReceive() sends and receives two packets:

LocateReply contains IOR information. It is important to understand what IOR is. Its role is to provide the host and port required for IIOP communication when the RMI-IIOP client interacts with the server object using the IIOP protocol. Also, the red-boxed part, Object_key, is used to distinguish different objects on the server:

When simulating the IIOP protocol, special attention needs to be paid to Object_key. Host and IP don't actually matter. Initially, during testing, I directly replayed all the packets, and sending resolve_any returned a location forward:

The GIOP official documentation mentions that when location forward occurs, it indicates that the Object_key may change; the Object_key returned by different requests may be different (the Object_key here refers to the key address in the data packet). As mentioned earlier, this value is used to distinguish which object to communicate with when using the IIOP protocol, and it needs to be dynamically obtained from LocateReply:

In the end, I chose not to simulate remove(), but to simulate the IIOP request sent by the bind() method, because there are fewer requests. Let's look at the data packet used locally for normal exploitation:

Send LocateRequest, receive data, and obtain the key address in LocateReply through regex matching:

Manually set the malicious jrmp server address (rmi://...), and send the bind_any packet at intervals of 1 second. Because the jrmp request issued by this exploitation chain does not use DGCClient, it is not affected by JEP290, and can be exploited through jrmplisten:

The POC works successfully in a Docker environment. You can use vulhub SSRF environment, set the IP to the host machine, and the Docker successfully obtains the jrmp request from the host. The specific code is placed on Github:

Earlier, I mentioned the idea of using codebase to load remote code to bypass detection. Students who have studied JNDI attacks should be familiar that codebase can be used to specify the location of remote classes. If codebase is controllable and the program allows remote class loading, then a remote malicious class can be loaded to execute malicious code in its static code block.
Reading the code shows that the second parameter of weblogic.iiop.Utils.loadClass() indicates codebase. This parameter is read in IIOPInputStream.read_value() as the var8 parameter. Line 1659 calls readIndirectingRepositoryId(var8), which eventually calls weblogic.iiop.Utils.loadClass(). To execute lines 1644 and 1659, we need (var4 & 1) = 1 and (var4 & 6) = 2, so var4 must be 3:
The call stack from readIndirectingRepositoryId to getClassFromId, finally executes line 304 loadclass():

Looking at the bind_any data packet, it is composed of GIOP Header and GIOP Request. The GIOP Request includes key address (same as in LocateReply), ServiceContextList, and stub_data. The value of var4 is \x7f\xff\xff\x02 in stub_data, so (var4 & 1) = 0 and (var4 & 6) = 2, which means line 1644 is not executed to set the codebase:

We modify the first box's \x7f\xff\xff\x02 to \x00\x00\x00\x03, and add the codebase length and value information in the second box. The specific code is placed on Github. You can see that an alignment operation was also performed. This is a pitfall. Before reading the following class information, it checks whether the position of the next byte is a multiple of 4. If not, it ignores some bytes. For example, if the next byte position is 1, it will ignore 3 bytes and start reading from the byte at position 4. This position is relative to the entire bind_any packet. If the byte is not a multiple of 4, zero padding is performed:

There is another issue. Looking at the call stack from readIndirectingRepositoryId to getClassFromId, it passes through the findClassInfo() function. Here, if a class has already been loaded, its ID information is saved. When findClassInfo() is called, it directly returns the class information without entering the weblogic.iiop.Utils.getClassFromID() function:

Therefore, when testing, I had to change the class name each time:

Regardless, in the end, I successfully simulated the IIOP protocol to modify the codebase value and executed the weblogic.iiop.Utils.getClassFromId() function.
Unfortunately, when obtaining RMIURLClassFinder, it returned NULL, and RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() returned false:

The reason is that the value of the _NetworkClassLoadingEnable parameter in ServerMBeanImpl is False:

I wanted to find which weblogic configuration file sets this parameter, but couldn't find it...
In the process of learning this vulnerability, I realized that a lot of prerequisite knowledge is needed, such as Java deserialization, RMI, JNDI, etc. Related learning can refer to this article collection. Although the attempt to exploit by modifying codebase failed in the end, I still gained a lot.