Merge pull request #67 from rapid7/feature/MSP-9695/ftp_login

Access level string clarified, specs passing, valid looking cores with proper info

MSP-9695 #land
bug/bundler_fix
Samuel Huckins 2014-06-12 11:33:18 -05:00
commit 430b3d181e
1 changed files with 77 additions and 111 deletions

View File

@ -4,6 +4,8 @@
##
require 'msf/core'
require 'metasploit/framework/credential_collection'
require 'metasploit/framework/login_scanner/ftp'
class Metasploit3 < Msf::Auxiliary
@ -52,134 +54,98 @@ class Metasploit3 < Msf::Auxiliary
def run_host(ip)
print_status("#{ip}:#{rport} - Starting FTP login sweep")
if check_banner
@@credentials_tried = {}
if datastore['RECORD_GUEST'] == false and check_anonymous == :next_user
@accepts_all_logins[@access] ||= []
@accepts_all_logins[@access] << ip
print_status("Successful authentication with #{@access.to_s} access on #{ip} will not be reported")
cred_collection = Metasploit::Framework::CredentialCollection.new(
blank_passwords: datastore['BLANK_PASSWORDS'],
pass_file: datastore['PASS_FILE'],
password: datastore['PASSWORD'],
user_file: datastore['USER_FILE'],
userpass_file: datastore['USERPASS_FILE'],
username: datastore['USERNAME'],
user_as_pass: datastore['USER_AS_PASS'],
prepended_creds: anonymous_creds
)
scanner = Metasploit::Framework::LoginScanner::FTP.new(
host: ip,
port: rport,
proxies: datastore['PROXIES'],
cred_details: cred_collection,
stop_on_success: datastore['STOP_ON_SUCCESS'],
connection_timeout: 30
)
service_data = {
address: ip,
port: rport,
service_name: 'ftp',
protocol: 'tcp',
workspace_id: myworkspace_id
}
scanner.scan! do |result|
if result.success?
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: result.credential.private,
private_type: :password,
username: result.credential.public
}
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
login_data = {
access_level: test_ftp_access(result.credential.public, scanner),
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
create_credential_login(login_data)
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
else
invalidate_login(
address: ip,
port: rport,
protocol: 'tcp',
public: result.credential.public,
private: result.credential.private,
realm_key: nil,
realm_value: nil,
status: result.status)
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
each_user_pass { |user, pass|
next if user.nil?
ret = do_login(user,pass)
ftp_quit if datastore['SINGLE_SESSION']
if ret == :next_user
unless user == user.downcase
ret = do_login(user.downcase,pass)
if ret == :next_user
user = user.downcase
print_status("Username #{user} is not case sensitive")
end
end
if datastore['RECORD_GUEST']
report_ftp_creds(user,pass,@access)
else
if @accepts_all_logins[@access]
report_ftp_creds(user,pass,@access) unless @accepts_all_logins[@access].include?(ip)
else
report_ftp_creds(user,pass,@access)
end
end
end
ret
}
# check_anonymous
else
return
end
ftp_quit
end
def ftp_quit
begin
send_quit if @ftp_sock
rescue ::Rex::ConnectionError, EOFError, ::Errno::ECONNRESET
end
disconnect if @ftp_sock
@ftp_sock = nil
end
# Always check for anonymous access by pretending to be a browser.
def check_anonymous
browser_passwords = {}
browser_passwords['IE6'] = "IEUser@"
browser_passwords['IE8'] = "User@"
browser_passwords['Firefox'] = 'mozilla@example.com'
browser_passwords['Chrome'] = 'chrome@example.com'
unless @@credentials_tried.keys.include? "#{rhost}:#{rport}:anonymous"
do_login("anonymous",browser_passwords.values[rand(browser_passwords.size)])
end
end
def check_banner
@ftp_sock = connect(true, false)
if self.banner
banner_sanitized = Rex::Text.to_hex_ascii(self.banner.to_s)
print_status("#{rhost}:#{rport} - FTP Banner: '#{banner_sanitized}'")
report_service(:host => rhost, :port => rport, :name => "ftp", :info => banner_sanitized)
return true
else
print_error("#{rhost}:#{rport} - Did not get an FTP service banner")
return false
end
end
def do_login(user=nil,pass=nil)
vprint_status("#{rhost}:#{rport} - Attempting FTP login for '#{user}':'#{pass}'")
this_attempt ||= {}
this_attempt[[user,pass]] ||= 0
while this_attempt[[user,pass]] <= 3
@ftp_sock = connect(true,false) unless @ftp_sock
begin
user_response = send_user(user, @ftp_sock)
if user_response !~ /^(331|2)/
vprint_error("#{rhost}:#{rport} - The server rejected username: '#{user}'")
return :skip_user
end
pass_response = send_pass(pass, @ftp_sock)
if pass_response =~ /^2/
print_good("#{rhost}:#{rport} - Successful FTP login for '#{user}':'#{pass}'")
@access = test_ftp_access(user)
ftp_quit
return :next_user
else
vprint_status("#{rhost}:#{rport} - Failed FTP login for '#{user}':'#{pass}'")
return :fail
end
rescue ::Rex::ConnectionError, EOFError, ::Errno::ECONNRESET => e
this_attempt[[user,pass]] += 1
vprint_error "#{rhost}:#{rport} - Caught #{e.class}, reconnecting and retrying"
disconnect
@ftp_sock = nil
def anonymous_creds
anon_creds = [ ]
if datastore['RECORD_GUEST']
['IEUser@', 'User@', 'mozilla@example.com', 'chrome@example.com' ].each do |password|
anon_creds << Metasploit::Framework::Credential.new(public: 'anonymous', private: password)
end
end
return :connection_error
anon_creds
end
def test_ftp_access(user)
def test_ftp_access(user,scanner)
dir = Rex::Text.rand_text_alpha(8)
write_check = send_cmd(['MKD', dir], true)
write_check = scanner.send_cmd(['MKD', dir], true)
if write_check and write_check =~ /^2/
send_cmd(['RMD',dir], true)
scanner.send_cmd(['RMD',dir], true)
print_status("#{rhost}:#{rport} - User '#{user}' has READ/WRITE access")
return :write
return 'Read/Write'
else
print_status("#{rhost}:#{rport} - User '#{user}' has READ access")
return :read
return 'Read-only'
end
end
def report_ftp_creds(user,pass,access)
report_auth_info(
:host => rhost,
:port => rport,
:sname => 'ftp',
:user => user,
:pass => pass,
:type => "password#{access == :read ? "_ro" : "" }",
:source_type => "user_supplied",
:active => true
)
end
end