Skip to content

Commit

Permalink
ssh_login module
Browse files Browse the repository at this point in the history
  • Loading branch information
h00die committed Jan 9, 2024
1 parent ad4b47f commit e73159c
Showing 1 changed file with 171 additions and 120 deletions.
291 changes: 171 additions & 120 deletions modules/auxiliary/scanner/ssh/ssh_login.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,159 +3,210 @@
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'net/ssh'
require 'net/ssh/command_stream'
require 'metasploit/framework/login_scanner/ssh'
require 'metasploit/framework/credential_collection'
require 'recog'
require 'net/ssh/transport/session'

class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::AuthBrute
include Msf::Auxiliary::Report
include Msf::Auxiliary::CommandShell
include Msf::Auxiliary::Scanner
include Msf::Exploit::Remote::SSH::Options
include Msf::Auxiliary::Report

def initialize
super(
'Name' => 'SSH Login Check Scanner',
'Description' => %q{
This module will test ssh logins on a range of machines and
report successful logins. If you have loaded a database plugin
and connected to a database this module will record successful
logins and hosts so you can track your access.
},
'Author' => ['todb'],
'References' =>
[
[ 'CVE', '1999-0502'] # Weak password
],
'License' => MSF_LICENSE,
'DefaultOptions' => {'VERBOSE' => false} # Disable annoying connect errors
'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)
], self.class
)

register_advanced_options(
[
Opt::Proxies,
OptBool.new('SSH_DEBUG', [false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
OptInt.new('SSH_TIMEOUT', [false, 'Specify the maximum time to negotiate a SSH session', 30]),
OptBool.new('GatherProof', [true, 'Gather proof of access via pre-session shell commands', true])
]
Opt::RPORT(22),
OptInt.new('TIMEOUT', [true, 'Timeout for the SSH probe', 30])
],
self.class
)
end

deregister_options('PASSWORD_SPRAY')
def timeout
datastore['TIMEOUT']
end

def rport
datastore['RPORT']
end

def session_setup(result, scanner)
return unless scanner.ssh_socket

platform = scanner.get_platform(result.proof)

# Create a new session
sess = Msf::Sessions::SshCommandShellBind.new(scanner.ssh_socket)
def run_host(target_host)
::Timeout.timeout(timeout) do
transport = Net::SSH::Transport::Session.new(target_host, { port: rport })

merge_me = {
'USERPASS_FILE' => nil,
'USER_FILE' => nil,
'PASS_FILE' => nil,
'USERNAME' => result.credential.public,
'PASSWORD' => result.credential.private
}
s = start_session(self, nil, merge_me, false, sess.rstream, sess)
self.sockets.delete(scanner.ssh_socket.transport.socket)
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

# Set the session platform
s.platform = platform
ident = transport.server_version.version

# Create database host information
host_info = {host: scanner.host}
table = Rex::Text::Table.new(
'Header' => 'Server Encryption',
'Indent' => 2,
'SortIndex' => 0,
'Columns' => [ 'Type', 'Value']
)

unless s.platform == 'unknown'
host_info[:os_name] = s.platform
end
server_data[:language_server].each do |language|
table << ['Language', language]
end

report_host(host_info)
server_data[:compression_server].each do |compression|
table << ['Compression', compression]
end

s
end
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

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

def run_host(ip)
@ip = ip
print_brute :ip => ip, :msg => 'Starting bruteforce'
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
table << ['Host Key', host_key]
end

cred_collection = build_credential_collection(
username: datastore['USERNAME'],
password: datastore['PASSWORD'],
)
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

scanner = Metasploit::Framework::LoginScanner::SSH.new(
host: ip,
port: rport,
cred_details: cred_collection,
proxies: datastore['Proxies'],
stop_on_success: datastore['STOP_ON_SUCCESS'],
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
connection_timeout: datastore['SSH_TIMEOUT'],
framework: framework,
framework_module: self,
skip_gather_proof: !datastore['GatherProof']
)
# XXX check for host key size?
# https://www.tenable.com/plugins/nessus/153954

scanner.verbosity = :debug if datastore['SSH_DEBUG']
# 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'

scanner.scan! do |result|
credential_data = result.to_h
credential_data.merge!(
module_fullname: self.fullname,
workspace_id: myworkspace_id
)
case result.status
when Metasploit::Model::Login::Status::SUCCESSFUL
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'"
credential_data[:private_type] = :password
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)

if datastore['CreateSession']
begin
session_setup(result, scanner)
rescue StandardError => e
elog('Failed to setup the session', error: e)
print_brute :level => :error, :ip => ip, :msg => "Failed to setup the session - #{e.class} #{e.message}"
info << "#{k}=#{v} "
end
info << ')'
end

if datastore['GatherProof'] && scanner.get_platform(result.proof) == 'unknown'
msg = "While a session may have opened, it may be bugged. If you experience issues with it, re-run this module with"
msg << " 'set gatherproof false'. Also consider submitting an issue at github.com/rapid7/metasploit-framework with"
msg << " device details so it can be handled in the future."
print_brute :level => :error, :ip => ip, :msg => msg
end
:next_user
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
vprint_brute :level => :verror, :ip => ip, :msg => "Could not connect: #{result.proof}"
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
invalidate_login(credential_data)
:abort
when Metasploit::Model::Login::Status::INCORRECT
vprint_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
invalidate_login(credential_data)
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
else
invalidate_login(credential_data)
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
end

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
rescue Timeout::Error
vprint_warning("Timed out after #{timeout} seconds. Skipping.")
end
end

0 comments on commit e73159c

Please sign in to comment.