ManageEngine OpManager is a popular Java-based network monitoring solution used by large companies such as NASA, DHL or Siemens.
Among other things, it allows the monitoring of network devices such as routers, webcams, servers, firewalls, and others.
In this post we present a critical deserialization vulnerability which allows an unauthenticated
attacker to execute arbitrary system commands with root or Administrator privileges.
The vulnerability not only affects ManageEngine OpManager but also other products that are based upon OpManager such as ManageEngine NetFlow Analyzer.
Vulnerability Details
The vulnerability exists in the SUMCommunicationServlet. The not so Smart Update Manager (SUM) Communication Servlet can be invoked through the
endpoint /servlets/com.adventnet.tools.sum.transport.SUMCommunicationServlet without prior authentication checks.
The following Listing shows the entry point of the servlet for POST requests.
If the request contains a session, the application tries to get an instance of SUMHttpRequestHandler (line 10) from the HttpSession attributes.
If successful, the execution reaches line 14, where the body of the POST request is processed by the SUMHttpRequestHandler.
We can add the requestHandler to our session by first sending a POST request containing the serialized integer 1002 to the SUMHandshakeServlet:
The function SUMHttpRequestHandler.process converts our payload InputStream to a DataInputStream,
reads the payload length from that stream and finally creates a byte array from our payload.
After that conversion process, function processSumPDU with the payload byte array as the argument is called.
And finally, the payload is deserialized with Java built-in class ObjectInputStream.
This means we can deserialize an arbitrary object which can lead to a critical vulnerability
when a corresponding gadget chain is available.
In order to leverage this vulnerability into a fully blown RCE, we need a gadget chain that allows executing Java code or system commands.
An option is to look for a custom chain in the code base of OPManager and the included libraries or looking for publicly available gadgets.
In this case, OpManager uses the commons-beanutils-1.9.3.jar as a dependency.
For this library, a publicly known RCE chain exists from ysoserial.
The CommonsBeanutils1 chain from ysoserial requires commons-beanutils:1.9.3, commons-collections:3.1 and commons-logging:1.2
and will therefore fail since the library commons-collections is not present in the classpath of OpManager.
We slightly modified the CommonsBeanutils1 chain such that commons-collections is not required anymore.
With this chain, we are able to execute arbitrary Java byte code.
The chain is based on the three gadgets which we detail in the following sections.
The class java.util.PriorityQueue is a built-in Java class that implements a priority queue that can be ordered by a custom comparator.
It implements a custom deserialization function of the Serializable interface.
This custom function readObject is invoked during deserialization and is our entry point.
The following Listing shows an excerpt of class PriorityQueue.
An array of objects from the attacker controlled ObjectInputStream is written into property queue.
Function heapify is called in line 8 to order the array by our custom comparator.
Then, shiftDown is called in line 13.
Since we will provide a custom comparator siftDownUsingComparator is called in line 19.
Finally, function compare of our custom comparator is invoked in line 31.
As a custom comparator we choose another gadget - the org.apache.commons.beanutils.BeanComparator gadget.
The BeanComparator Gadget
The BeanComparator allows comparing two objects based on a supplied property.
This means that the getter function of the property is called on both objects and the results are compared.
In particular, calling an arbitrary getter function of an object’s property makes this gadget powerful.
As mentioned before, the public chain requires the library commons-collections:3.1 as we can see from the import of class ComparableComparator.
However, it is possible to use the overloaded constructor with a native Java comparator such as java.util.Collections.ReverseComparator.
With the ability to call an arbitrary getter function of an arbitrary object we can continue with building the chain to get RCE.
The Xalan TemplatesImpl gadget provides the rare feature to define and initialize classes through supplied Java bytecode.
The execution of our malicious class constructor can be triggered by invoking the public getter function getOutputProperties through the previous gadget (line 2).
In line 6 getTransletInstance is invoked and in line 13 finally, the constructor of our malicious class property is called.
By default, OpManager runs with root/Administrator privileges. Therefore our exploit pops a root shell on Linux and an Administrator shell on Windows.
Fixing object injection vulnerabilities is not a big deal if the classes that should be deserialized are known.
One solution is creating a wrapper around the built-in ObjectInputStream and only allow classes from a whitelist for deserialization.
This can be achieved by overriding the method resolveClass of ObjectInputStream.
The following snippet shows the simplified wrapper class that was used to fix the reported RCE which contains a fatal mistake.
The function resolveClass tries to resolve the class name of a serialized object. Note, that it can be invoked multiple times if the serialized stream contains nested or chained objects.
If this function is called on the first object of the stream, it is checked if the untrusted class name is part of the whitelist.
In case it is, the private boolean field classResolved is set to true and the usual class resolving process is continued.
Setting the field classResolved to true causes that all subsequent classes within the object stream are not being checked against the whitelist anymore.
That means if the deserialization routine first reads a String array and after that reads any other object we can bypass the whitelist and again gain remote code execution.
The following code snippet illustrates the described scenario.
We have identified multiple usages of that insecure class resolving procedure and reported an unauthenticated RCE as proof-of-concept to the vendor.
Summary
In this post we saw how a broken authentication management combined with an insecure deserialization
of untrusted user input lead to a critical vulnerability in ManageEngine OpManager. We explained in detail
how an object chain is created and how it can be abused to execute arbitrary code. Furthermore, we explained the fix of that issue and detailed a way to bypass that fix.
Timeline
Date
Action
2020-11-07
Reported issue via bugbounty.zoho.com
2020-11-13
Vendor confirmed the vulnerability and released the supposedly fixed versions 125203 and 125233.
2020-11-16
CVE-2020-28653 was assigned to the issue.
2021-01-22
The vendor was informed that the fix was insufficient and a new issue was reported.
2021-01-25
Vendor confirmed the new issue and assigned CVE-2021-3287.