Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions lib/metasploit/framework/login_scanner/ftp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,17 @@ def attempt_login(credential)
}

begin
success = connect_login(credential.public, credential.private)
ftpsock = connect(true)
res = send_user(credential.public, ftpsock)
res = send_pass(credential.private, ftpsock) if res =~ /^(331|2)/
result_options[:proof] = res.to_s.strip
result_options[:status] = res =~ /^2/ ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT
rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
success = false
ensure
disconnect
end

if success
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
elsif !(result_options.has_key? :status)
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end

result = ::Metasploit::Framework::LoginScanner::Result.new(result_options)
result.host = host
result.port = port
Expand Down
109 changes: 82 additions & 27 deletions modules/auxiliary/scanner/ftp/ftp_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Ftp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute

def proto
Expand All @@ -21,13 +20,13 @@ def initialize
'Name' => 'FTP Authentication Scanner',
'Description' => %q{
This module will test FTP logins on a range of machines and
report successful logins. If you have loaded a database plugin
report successful logins. If you have loaded a database plugin
and connected to a database this module will record successful
logins and hosts so you can track your access.
},
'Author' => 'todb',
'References' => [
[ 'CVE', '1999-0502'] # Weak password
[ 'CVE', '1999-0502' ] # Weak password
],
'License' => MSF_LICENSE,
'DefaultOptions' => {
Expand All @@ -39,29 +38,61 @@ def initialize
[
Opt::Proxies,
Opt::RPORT(21),
OptBool.new('RECORD_GUEST', [ false, "Record anonymous/guest logins to the database", false])
OptBool.new('ANONYMOUS_LOGIN', [ false, 'Attempt to login as an anonymous FTP user', false ]), # Overwrite the AuthBrute mixin, as its not sending blank/empty user/pass
OptBool.new('CHECK_ACCESS', [ false, 'Check READ/WRITE access for successful logins', true ])
]
)

register_advanced_options(
[
OptBool.new('SINGLE_SESSION', [ false, 'Disconnect after every login attempt', false]),
OptBool.new('SINGLE_SESSION', [ false, 'Disconnect after every login attempt', false ]),
]
)

deregister_options('FTPUSER', 'FTPPASS') # Can use these, but should use 'username' and 'password'
@accepts_all_logins = {}
end

def grab_report_banner
vprint_status('Getting FTP banner')

begin
connect(true, false)
rescue ::Rex::ConnectionRefused
vprint_error('Connection refused')
report_host(host: rhost)
return
rescue ::Rex::ConnectionError, ::IOError => e
vprint_error(e.message)
return
ensure
disconnect
end

unless banner
vprint_warning('No FTP banner received')
return
end

vprint_status("FTP Banner: #{banner_version}")
end

def run_host(ip)
print_status("#{ip}:#{rport} - Starting FTP login sweep")
grab_report_banner

print_status('Starting FTP login sweep')

cred_collection = build_credential_collection(
username: datastore['USERNAME'],
password: datastore['PASSWORD'],
prepended_creds: anonymous_creds
prepended_creds: anonymous_creds,
anonymous_login: false # Otherwise this would send blank for both user/password, so its different to anonymous_creds()
)

if cred_collection.empty?
print_error('No credentials specified. Set USERNAME/PASSWORD, USER_FILE/PASS_FILE, or ANONYMOUS_LOGIN.')
return
end

scanner = Metasploit::Framework::LoginScanner::FTP.new(
configure_login_scanner(
host: ip,
Expand All @@ -88,45 +119,69 @@ def run_host(ip)
scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: self.fullname,
module_fullname: fullname,
workspace_id: myworkspace_id
)
if result.success?
credential_data[:private_type] = :password
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)

print_good "#{ip}:#{rport} - Login Successful: #{result.credential}"
vprint_status('Checking read/write access') if datastore['CHECK_ACCESS']
access_level = test_ftp_access(result.credential.public, result.credential.private) if datastore['CHECK_ACCESS']
credential_data[:access_level] = access_level if access_level

msg = "Login Successful: #{result.credential}"
msg << " (#{access_level})" if access_level
print_good(msg)

report_vuln(
host: rhost,
port: rport,
proto: 'tcp',
name: 'Weak FTP Credentials',
info: "Login accepted: #{result.credential}#{" (#{access_level})" if access_level}",
refs: references
)

create_credential_login(credential_data)
else
proof = result.proof.to_s.strip
proof_str = proof.empty? ? result.status.to_s : "#{result.status}: #{proof}"
vprint_error("Login Failed: #{result.credential} (#{proof_str})")

invalidate_login(credential_data)
vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
end
end
end

# Always check for anonymous access by pretending to be a browser.
# Check for anonymous access by pretending to be a browser
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
return [] unless datastore['ANONYMOUS_LOGIN']

['mozilla@example.com', 'IEUser@', 'User@', 'chrome@example.com'].map do |password|
Metasploit::Framework::Credential.new(public: 'anonymous', private: password)
end
anon_creds
end

def test_ftp_access(user, scanner)
def test_ftp_access(user, pass)
connect(true, false)
send_user(user)
res = send_pass(pass)
return nil unless res =~ /^2/

dir = Rex::Text.rand_text_alpha(8)
write_check = scanner.send_cmd(['MKD', dir], true)
if write_check and write_check =~ /^2/
scanner.send_cmd(['RMD', dir], true)
print_status("#{rhost}:#{rport} - User '#{user}' has READ/WRITE access")
return 'Read/Write'
write_check = send_cmd(['MKD', dir], true)
if write_check && write_check =~ /^2/
send_cmd(['RMD', dir], true)
'Read/Write'
else
print_status("#{rhost}:#{rport} - User '#{user}' has READ access")
return 'Read-only'
'Read-only'
end
rescue StandardError
nil
ensure
disconnect
end

end
Loading