Pre-Auth RCE in ManageEngine OPManager · Haxolot.com

Vulnerability Summary

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class SUMCommunicationServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession httpSession = request.getSession();
        response.setStatus(200);
        DataOutputStream dos = new DataOutputStream(response.getOutputStream());
        if (httpSession == null) {
            dos.writeInt(1000);
        } else {
            SUMHttpRequestHandler requestHandler = (SUMHttpRequestHandler)httpSession.getAttribute("requestHandler");
            if (requestHandler == null) {
                dos.writeInt(1000);
            } else {
                byte[] responseData = requestHandler.process(request.getInputStream());
                [...]
            }
        }
        dos.flush();
    }
}
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class SUMHandShakeServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
        int requestId = ois.readInt();
        HttpSession httpSession;
        if (requestId == 1002) {
            httpSession = request.getSession(true);
            SUMHttpRequestHandler reqHandler = new SUMHttpRequestHandler(request.getRemoteHost());
            httpSession.setAttribute("requestHandler", reqHandler);
         [...]   
        }
    }
}
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
   public byte[] process(InputStream is) {
        BufferedInputStream bis = new BufferedInputStream(is);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] dataa = new byte[4096];
        int lengthx;
 
        while((lengthx = bis.read(dataa, 0, dataa.length)) > 0) {
             bos.write(dataa, 0, lengthx);
         }

        DataInputStream ois = new DataInputStream(new ByteArrayInputStream(bos.toByteArray()));
        int length = ois.readInt();
        byte[] data = new byte[length];
        ois.readFully(data, 0, length);
        byte[] processedData = this.processSumPDU(data);
        [...]
In this function, our payload is passed further to method SUMPDU.deserializePDU in line 8.

1
2
3
4
5
6
7
8
   private byte[] processSumPDU(byte[] pduData) throws Exception {
        if (pduData == null) {
            return null;
        } else if (pduData == SUMPDU.CLOSE_SESSION) {
            this.cleanUp();
            return null;
        } else {
            SUMPDU sumpdu = SUMPDU.deSerializePDU(pduData);
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.
1
2
3
4
    public static SUMPDU deSerializePDU(byte[] b) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(b));
        return (SUMPDU)ois.readObject();
    }

The Chain Read More

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.

  • java.util.PriorityQueue
    • org.apache.commons.beanutils.BeanComparator
      • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

The PriorityQueue Gadget

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.

  1. An array of objects from the attacker controlled ObjectInputStream is written into property queue.
  2. Function heapify is called in line 8 to order the array by our custom comparator.
  3. Then, shiftDown is called in line 13.
  4. Since we will provide a custom comparator siftDownUsingComparator is called in line 19.
  5. Finally, function compare of our custom comparator is invoked in line 31.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        var1.readInt();
        this.queue = new Object[this.size];
        for(int var2 = 0; var2 < this.size; ++var2) {
            this.queue[var2] = var1.readObject();
        }
        this.heapify();
    }

    private void heapify() {
        for(int var1 = (this.size >>> 1) - 1; var1 >= 0; --var1) {
            this.siftDown(var1, this.queue[var1]);
        }
    }

    private void siftDown(int var1, E var2) {
        if (this.comparator != null) {
            this.siftDownUsingComparator(var1, var2);
        } else {
            this.siftDownComparable(var1, var2);
        }
    }

    private void siftDownUsingComparator(int var1, E var2) {
        int var4;
        for(int var3 = this.size >>> 1; var1 < var3; var1 = var4) {
            var4 = (var1 << 1) + 1;
            Object var5 = this.queue[var4];
            int var6 = var4 + 1;
            if (var6 < this.size && this.comparator.compare(var5, this.queue[var6]) > 0) {
                var4 = var6;
                var5 = this.queue[var6];
            }
        }
    }
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.apache.commons.collections.comparators.ComparableComparator;

public class BeanComparator implements Comparator, Serializable {
    private String property;
    private Comparator comparator;

    public BeanComparator(String property, Comparator comparator) {
        this.setProperty(property);
        if (comparator != null) {
            this.comparator = comparator;
        } else {
            this.comparator = ComparableComparator.getInstance();
        }
    }

    public int compare(Object o1, Object o2) {
        if (this.property == null) {
            return this.comparator.compare(o1, o2);
        } else {
                Object value1 = PropertyUtils.getProperty(o1, this.property);
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.comparator.compare(value1, value2);
            }
        }
    }

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. 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public synchronized Properties getOutputProperties() {
            return this.newTransformer().getOutputProperties();
    }

    public synchronized Transformer newTransformer() throws TransformerConfigurationException {
        TransformerImpl var1 = new TransformerImpl(this.getTransletInstance(), this._outputProperties, this._indentNumber, this._tfactory);
        [...]
    }
   private Translet getTransletInstance() throws TransformerConfigurationException {
        if (this._class == null) {
            this.defineTransletClasses();
        }
        AbstractTranslet var1 = (AbstractTranslet)this._class[this._transletIndex].newInstance();
        var1.postInitialization();
        var1.setTemplates(this);
        var1.setServicesMechnism(this._useServicesMechanism);
        var1.setAllowedProtocols(this._accessExternalStylesheet);
        if (this._auxClasses != null) {
            var1.setAuxiliaryClasses(this._auxClasses);
        }

        return var1;
    }

The Fix Read More

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class InSecureObjectInputStream extends ObjectInputStream {
    private static List<String> whitlelist = Arrays.asList("[Ljava.lang.String;");
	private boolean classResolved = false;

    protected Class<?> resolveClass(ObjectStreamClass streamclass){
		if (!this.classResolved) {
			String untrustedClassName = streamclass.getName();        
			if(!this.whitelist.contains(untrustedClassName)){
				throw new InvalidClassException("Unsupported Class", streamclass.getName());		
			}
			this.classResolved = true;
		}
		
        return super.resolveClass(streamclass);
    }
}
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.

1
2
3
4
ByteArrayInputStream bais = new ByteArrayInputStream(untrustedData)
InSecureObjectInputStream ois = new InSecureObjectInputStream(bais)
String[] arr = (String[]) ois.readObject();
String someObj = (String) ois.readObject();

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

DateAction
2020-11-07Reported issue via bugbounty.zoho.com
2020-11-13Vendor confirmed the vulnerability and released the supposedly fixed versions 125203 and 125233.
2020-11-16CVE-2020-28653 was assigned to the issue.
2021-01-22The vendor was informed that the fix was insufficient and a new issue was reported.
2021-01-25Vendor confirmed the new issue and assigned CVE-2021-3287.
2021-02-09The fixed version 125329 has been released.

Stay Secure!

Contact - Privacy Policy
© 2021 The Haxolot'ls

本文来源于: https://sec.today/pulses/d0453d2d-3a6d-4267-89cd-4ced906f9bd3/

相关推荐

内网隐藏通信隧道技术——FRP隧道 - FreeBuf网络安全行业门户

本文介绍有关FRP代理配置以及使用FRP建立一级代理、二级代理、三级代理 frp是一个专注于内网穿透的高性能的反向代理应用,支持TCP、UDP、HTTP、HTTPS等多种协议。可以将内网服务以安全、便捷的方式通过具有公网IP节点的中转暴露到

About the security content of macOS Big Sur 11.5.1 - Apple Support

Released July 26, 2021 IOMobileFrameBuffer Available for: macOS Big Sur Impact: An application may be able to execute ar

WebContent->EL1 LPE: OOBR in AppleCLCD / IOMobileFrameBuffer | IOMobileFrameBuffer_LPE_POC

IOMobileFrameBuffer_LPE_POC WebContent->EL1 LPE: OOBR in AppleCLCD / IOMobileFrameBuffer While reversing some of the acc

Shellcoding: Process Injection with Assembly

Conor Richard home.. Shellcoding: Process Injection with Assembly July 2021 Introduction It has been a long time since m

Cobalt Strike and Tradecraft | hausec

It’s been known that some built-in commands in Cobalt Strike are major op-sec no-no’s, but why are they bad? The goal of

macOS TCC.db Internals by keith | Rainforest Engineering

macOS TCC.db Internals Keith Johnson, Tuesday February 9, 2021 A deep dive into what the TCC database contains and the m

A guide to non-conventional WAF/IDS evasion techniques – 0xFFFF@blog:~$

This is a tutorial detailing various non-conventional methods of circumventing signature based WAF or IDS software. Rath

CVE-2021-22555 linux内核提权 - 安全客,安全资讯平台

robots 该漏洞作者Andy Nguyen (theflow@) ,writeup已经公开。 简单概述 主要是在xt_compat_target_from_user()中有溢出,经过特殊构造产生uaf, 具体exp中,使用了struct

加密固件之依据老固件进行解密

作者:OneShell@知道创宇404实验室 时间:2021年7月27日 IoT漏洞分析最为重要的环节之一就是获取固件以及固件中的文件系统。固件获取的方式也五花八门,硬核派有直接将flash拆下来到编程器读取,通过硬件调试器UART/SPI

如何使用FalconEye实时检测Windows进程注入行为 - FreeBuf网络安全行业门户

关于FalconEye FalconEye是一款功能强大的Windows终端安全检测工具,可以帮助广大研究人员实时检测Windows进程注入行为。FalconEye也是一个内核模式驱动工具,旨在实现实时的进程注入行为。由于FalconEye

梨子带你刷burpsuite靶场系列之服务器端漏洞篇 - 服务端请求伪造(SSRF)专题 - 安全客,安全资讯平台

robots 本系列介绍 PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者