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 #landbug/bundler_fix
commit
430b3d181e
|
@ -4,6 +4,8 @@
|
||||||
##
|
##
|
||||||
|
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
|
require 'metasploit/framework/credential_collection'
|
||||||
|
require 'metasploit/framework/login_scanner/ftp'
|
||||||
|
|
||||||
class Metasploit3 < Msf::Auxiliary
|
class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
|
@ -52,134 +54,98 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
def run_host(ip)
|
def run_host(ip)
|
||||||
print_status("#{ip}:#{rport} - Starting FTP login sweep")
|
print_status("#{ip}:#{rport} - Starting FTP login sweep")
|
||||||
if check_banner
|
|
||||||
@@credentials_tried = {}
|
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||||
if datastore['RECORD_GUEST'] == false and check_anonymous == :next_user
|
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||||
@accepts_all_logins[@access] ||= []
|
pass_file: datastore['PASS_FILE'],
|
||||||
@accepts_all_logins[@access] << ip
|
password: datastore['PASSWORD'],
|
||||||
print_status("Successful authentication with #{@access.to_s} access on #{ip} will not be reported")
|
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
|
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
|
end
|
||||||
ftp_quit
|
|
||||||
end
|
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.
|
# Always check for anonymous access by pretending to be a browser.
|
||||||
def check_anonymous
|
def anonymous_creds
|
||||||
browser_passwords = {}
|
anon_creds = [ ]
|
||||||
browser_passwords['IE6'] = "IEUser@"
|
if datastore['RECORD_GUEST']
|
||||||
browser_passwords['IE8'] = "User@"
|
['IEUser@', 'User@', 'mozilla@example.com', 'chrome@example.com' ].each do |password|
|
||||||
browser_passwords['Firefox'] = 'mozilla@example.com'
|
anon_creds << Metasploit::Framework::Credential.new(public: 'anonymous', private: password)
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return :connection_error
|
anon_creds
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ftp_access(user)
|
def test_ftp_access(user,scanner)
|
||||||
dir = Rex::Text.rand_text_alpha(8)
|
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/
|
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")
|
print_status("#{rhost}:#{rport} - User '#{user}' has READ/WRITE access")
|
||||||
return :write
|
return 'Read/Write'
|
||||||
else
|
else
|
||||||
print_status("#{rhost}:#{rport} - User '#{user}' has READ access")
|
print_status("#{rhost}:#{rport} - User '#{user}' has READ access")
|
||||||
return :read
|
return 'Read-only'
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue