Skip to content

Commit

Permalink
ssh_version module
Browse files Browse the repository at this point in the history
  • Loading branch information
h00die committed Jan 9, 2024
1 parent ad4b47f commit b8e2fee
Showing 1 changed file with 162 additions and 49 deletions.
211 changes: 162 additions & 49 deletions modules/auxiliary/scanner/ssh/ssh_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,96 +4,209 @@
##

require 'recog'
require 'net/ssh/transport/session'

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report

# the default timeout (in seconds) to wait, in total, for both a successful
# connection to a given endpoint and for the initial protocol response
# from the supposed SSH endpoint to be returned
DEFAULT_TIMEOUT = 30

def initialize
super(
'Name' => 'SSH Version Scanner',
'Description' => 'Detect SSH Version.',
'References' =>
[
[ 'URL', 'https://en.wikipedia.org/wiki/SecureShell' ]
],
'Author' => [ 'Daniel van Eeden <metasploit[at]myname.nl>' ],
'License' => MSF_LICENSE
'Name' => 'SSH Version Scanner',
'Description' => 'Detect SSH Version, and the algorithms available from the server',
'References' => [
['URL', 'https://en.wikipedia.org/wiki/SecureShell'], # general info
['URL', 'https://datatracker.ietf.org/doc/html/rfc8732#name-deprecated-algorithms'], # deprecation of kex gss-sha1 stuff
['URL', 'https://datatracker.ietf.org/doc/html/draft-ietf-curdle-ssh-kex-sha2-20#page-16'], # diffie-hellman-group-exchange-sha1, diffie-hellman-group1-sha1, rsa1024-sha1
['URL', 'https://datatracker.ietf.org/doc/html/rfc8758#name-iana-considerations'], # arc4 deprecation
['URL', 'https://github.com/net-ssh/net-ssh?tab=readme-ov-file#supported-algorithms'] # a bunch of diff removed things from the ruby lib
],
'Author' => [
'Daniel van Eeden <metasploit[at]myname.nl>', # original author
'h00die' # algorithms enhancements
],
'License' => MSF_LICENSE
)

register_options(
[
Opt::RPORT(22),
OptInt.new('TIMEOUT', [true, 'Timeout for the SSH probe', DEFAULT_TIMEOUT])
OptInt.new('TIMEOUT', [true, 'Timeout for the SSH probe', 30])
],
self.class
)
end

def timeout
datastore['TIMEOUT'] <= 0 ? DEFAULT_TIMEOUT : datastore['TIMEOUT']
datastore['TIMEOUT']
end

def rport
datastore['RPORT']
end

def run_host(target_host)
::Timeout.timeout(timeout) do
connect
transport = Net::SSH::Transport::Session.new(target_host, { port: rport })

resp = sock.get_once(-1, timeout)
server_data = transport.algorithms.instance_variable_get(:@server_data)
host_keys = transport.algorithms.session.instance_variable_get(:@host_keys).instance_variable_get(:@host_keys)
if !host_keys.empty?
print_status("Key Fingerprint: #{host_keys[0].fingerprint}")
end

ident = transport.server_version.version

table = Rex::Text::Table.new(
'Header' => 'Server Encryption',
'Indent' => 2,
'SortIndex' => 0,
'Columns' => [ 'Type', 'Value']
)

if ! resp
vprint_warning("No response")
return Exploit::CheckCode::Unknown
server_data[:language_server].each do |language|
table << ['Language', language]
end

ident, first_message = resp.split(/[\r\n]+/)
info = ""
server_data[:compression_server].each do |compression|
table << ['Compression', compression]
end

if /^SSH-\d+\.\d+-(.*)$/ !~ ident
vprint_warning("Was not SSH -- #{resp.size} bytes beginning with #{resp[0, 12]}")
return Exploit::CheckCode::Safe(details: { ident: ident })
server_data[:encryption_server].each do |encryption|
['arcfour', 'arcfour128', 'arcfour256'].each do |bad_enc|
next unless encryption.downcase.start_with? bad_enc

print_good("Encryption #{encryption} is deprecated and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Encryption #{encryption} is available, but should be deprecated",
refs: ['https://datatracker.ietf.org/doc/html/rfc8758#name-iana-considerations']
)
end
[
'aes256-cbc', 'aes192-cbc', 'aes128-cbc', '[email protected]',
'blowfish-ctr blowfish-cbc', 'cast128-ctr', 'cast128-cbc', '3des-ctr', '3des-cbc', 'idea-cbc', 'none'
].each do |bad_enc|
next unless encryption.downcase.start_with? bad_enc

print_good("Encryption #{encryption} is deprecated and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Encryption #{encryption} is available, but should be deprecated",
refs: ['https://github.com/net-ssh/net-ssh?tab=readme-ov-file#encryption-algorithms-ciphers']
)
end
table << ['Encryption', encryption]
end

banner = $1
server_data[:hmac_server].each do |hmac|
['hmac-sha2-512-96', 'hmac-sha2-256-96', 'hmac-sha1-96', 'hmac-ripemd160', 'hmac-md5', 'hmac-md5-96', 'none'].each do |bad_hmac|
next unless hmac.downcase.start_with? bad_hmac

print_good("HMAC #{hmac} is deprecated and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH HMAC #{hmac} is available, but should be deprecated",
refs: ['https://github.com/net-ssh/net-ssh?tab=readme-ov-file#message-authentication-code-algorithms']
)
end
table << ['HMAC', hmac]
end

# Try to match with Recog and show the relevant fields to the user
recog_match = Recog::Nizer.match('ssh.banner', banner)
if recog_match
info << " ( "
recog_match.each_pair do |k,v|
next if k == 'matched'
info << "#{k}=#{v} "
server_data[:host_key].each do |host_key|
['ecdsa-sha2-nistp521', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp256'].each do |bad_key|
next unless host_key.downcase.start_with? bad_key

print_good("Host Key Encryption #{host_key} uses a weak elliptic curve and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Host Key Encryption #{host_key} is available, but should be deprecated",
refs: ['https://github.com/net-ssh/net-ssh?tab=readme-ov-file#host-keys']
)
end
info << ")"
table << ['Host Key', host_key]
end

# Check to see if this is Kippo, which sends a premature
# key init exchange right on top of the SSH version without
# waiting for the required client identification string.
if first_message && first_message.size >= 5
extra = first_message.unpack("NCCA*") # sz, pad_sz, code, data
if (extra.last.size + 2 == extra[0]) && extra[2] == 20
info << " (Kippo Honeypot)"
server_data[:kex].each do |kex|
['gss-group1-sha1-', 'gss-group14-sha1-', 'gss-gex-sha1-'].each do |bad_kex|
next unless kex.downcase.start_with? bad_kex

print_good("Key Exchange (kex) #{kex} is deprecated and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Encryption #{kex} is available, but should be deprecated",
refs: ['https://datatracker.ietf.org/doc/html/rfc8732#name-deprecated-algorithms']
)
end
['ecdsa-sha2-nistp521', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp256'].each do |bad_kex|
next unless kex.downcase.start_with? bad_kex

print_good("Key Exchange (kex) #{kex} uses a weak elliptic curve and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Encryption #{kex} is available, but should be deprecated",
refs: ['https://github.com/net-ssh/net-ssh?tab=readme-ov-file#key-exchange']
)
end
['diffie-hellman-group-exchange-sha1', 'diffie-hellman-group1-sha1', 'rsa1024-sha1'].each do |bad_kex|
next unless kex.downcase.start_with? bad_kex

print_good("Key Exchange (kex) #{kex} is deprecated and should not be used.")
report_vuln(
host: target_host,
port: rport,
proto: 'tcp',
name: name,
info: "Module #{fullname} confirmed SSH Encryption #{kex} is available, but should be deprecated",
refs: ['https://datatracker.ietf.org/doc/html/draft-ietf-curdle-ssh-kex-sha2-20#page-16']
)
end
table << ['Key Exchange (kex)', kex]
end

print_good("SSH server version: #{ident}#{info}")
report_service(host: rhost, port: rport, name: 'ssh', proto: 'tcp', info: ident)
# XXX check for host key size?
# https://www.tenable.com/plugins/nessus/153954

# Try to match with Recog and show the relevant fields to the user
info = ''
if /^SSH-\d+\.\d+-(.*)$/ =~ ident
recog_match = Recog::Nizer.match('ssh.banner', ::Regexp.last_match(1))
if recog_match
info << ' ( '
recog_match.each_pair do |k, v|
next if k == 'matched'

info << "#{k}=#{v} "
end
info << ')'
end
end

Exploit::CheckCode::Detected(details: { ident: ident, info: info })
print_status("SSH server version: #{ident}#{info}")
report_service(host: target_host, port: rport, name: 'ssh', proto: 'tcp', info: ident)
print_status(table.to_s)
end
rescue EOFError, Rex::ConnectionError => e
vprint_error(e.message) # This may be a little noisy, but it is consistent
Exploit::CheckCode::Unknown
rescue Timeout::Error
vprint_warning("Timed out after #{timeout} seconds. Skipping.")
Exploit::CheckCode::Unknown
ensure
disconnect
end
end

0 comments on commit b8e2fee

Please sign in to comment.