From b8e2feebe0f6cea38ee98c85d47fad167cc7e003 Mon Sep 17 00:00:00 2001 From: h00die Date: Tue, 9 Jan 2024 17:52:17 -0500 Subject: [PATCH] ssh_version module --- modules/auxiliary/scanner/ssh/ssh_version.rb | 211 ++++++++++++++----- 1 file changed, 162 insertions(+), 49 deletions(-) diff --git a/modules/auxiliary/scanner/ssh/ssh_version.rb b/modules/auxiliary/scanner/ssh/ssh_version.rb index fc4397eb3cbf..11e31b6dcbca 100644 --- a/modules/auxiliary/scanner/ssh/ssh_version.rb +++ b/modules/auxiliary/scanner/ssh/ssh_version.rb @@ -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 ' ], - '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 ', # 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', 'rijndael-cbc@lysator.liu.se', + '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