Pre-Auth RCE in ManageEngine OPManager
121000 - 125233
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.
The vulnerability exists in the
SUMCommunicationServlet. The not so Smart Update Manager (SUM) Communication Servlet can be invoked through the
/servlets/com.adventnet.tools.sum.transport.SUMCommunicationServlet without prior authentication checks.
The following Listing shows the entry point of the servlet for POST requests.
SUMHttpRequestHandler(line 10) from the
HttpSessionattributes. If successful, the execution reaches line 14, where the body of the POST request is processed by the
SUMHttpRequestHandler. We can add the
requestHandlerto our session by first sending a POST request containing the serialized integer
SUMHttpRequestHandler.processconverts our payload
DataInputStream, reads the payload length from that stream and finally creates a byte array from our payload. After that conversion process, function
processSumPDUwith the payload byte array as the argument is called.
SUMPDU.deserializePDUin line 8.
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.
CommonsBeanutils1 chain from ysoserial requires
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 PriorityQueue Gadget
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
This custom function
readObject is invoked during deserialization and is our entry point.
The following Listing shows an excerpt of class
- An array of objects from the attacker controlled
ObjectInputStreamis written into property queue.
heapifyis called in line 8 to order the array by our custom comparator.
shiftDownis called in line 13.
- Since we will provide a custom comparator
siftDownUsingComparatoris called in line 19.
- Finally, function
compareof our custom comparator is invoked in line 31.
The BeanComparator Gadget
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
However, it is possible to use the overloaded constructor with a native Java comparator such as
The TemplatesImpl Gadget
With the ability to call an arbitrary getter function of an arbitrary object we can continue with building the chain to get RCE.
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
The following snippet shows the simplified wrapper class that was used to fix the reported RCE which contains a fatal mistake.
resolveClasstries 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
classResolvedis set to
trueand the usual class resolving process is continued. Setting the field
truecauses 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.
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.
|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.|
|2021-02-09||The fixed version 125329 has been released.|