Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ GEM
mutex_m
railties (~> 7.0)
zeitwerk
metasploit-credential (6.0.16)
metasploit-credential (6.0.19)
bigdecimal
csv
drb
Expand All @@ -340,7 +340,7 @@ GEM
railties
rex-socket
rubyntlm
rubyzip
rubyzip (< 3.0.0)
metasploit-model (5.0.4)
activemodel (~> 7.0)
activesupport (~> 7.0)
Expand Down
1 change: 1 addition & 0 deletions lib/metasploit/framework/login_scanner/ssh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def attempt_login(credential)
:key_data => credential.private,
)
end
opt_hash[:passphrase] = cred_details.password

result_options = {
credential: credential
Expand Down
2 changes: 1 addition & 1 deletion lib/msf/core/auxiliary/report_summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def create_credential_and_login(credential_data)
# @param [Msf::Sessions::<SESSION_CLASS>] sess
# @return [Msf::Sessions::<SESSION_CLASS>]
def start_session(obj, info, ds_merge, crlf = false, sock = nil, sess = nil)
return super unless framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS) && datastore['ShowSuccessfulLogins']
return super unless framework.features.enabled?(Msf::FeatureManager::SHOW_SUCCESSFUL_LOGINS) && datastore['ShowSuccessfulLogins'] && @report

result = super
@report[rhost] ||= {}
Expand Down
133 changes: 75 additions & 58 deletions modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def session_setup(result, scanner, fingerprint, cred_core_private_id)
'PASS_FILE' => nil,
'USERNAME' => result.credential.public,
'CRED_CORE_PRIVATE_ID' => cred_core_private_id,
'SSH_KEYFILE_B64' => [result.credential.private].pack("m*").gsub("\n", ""),
'SSH_KEYFILE_B64' => [result.credential.private].pack('m*').gsub("\n", ''),
'KEY_PATH' => nil
}

Expand All @@ -113,12 +113,12 @@ def session_setup(result, scanner, fingerprint, cred_core_private_id)
def run_host(ip)
print_status("#{ip}:#{rport} SSH - Testing Cleartext Keys")

if datastore["USER_FILE"].blank? && datastore["USERNAME"].blank?
validation_reason = "At least one of USER_FILE or USERNAME must be given"
if datastore['USER_FILE'].blank? && datastore['USERNAME'].blank?
validation_reason = 'At least one of USER_FILE or USERNAME must be given'
raise Msf::OptionValidateError.new(
{
"USER_FILE" => validation_reason,
"USERNAME" => validation_reason
'USER_FILE' => validation_reason,
'USERNAME' => validation_reason
}
)
end
Expand All @@ -132,7 +132,7 @@ def run_host(ip)
)

unless keys.valid?
print_error("Files that failed to be read:")
print_error('Files that failed to be read:')
keys.error_list.each do |err|
print_line("\t- #{err}")
end
Expand All @@ -150,7 +150,7 @@ def run_host(ip)
key_sources.append('PRIVATE_KEY')
end

print_brute :level => :vstatus, :ip => ip, :msg => "Testing #{key_count} #{'key'.pluralize(key_count)} from #{key_sources.join(' and ')}"
print_brute level: :vstatus, ip: ip, msg: "Testing #{key_count} #{'key'.pluralize(key_count)} from #{key_sources.join(' and ')}"
scanner = Metasploit::Framework::LoginScanner::SSH.new(
configure_login_scanner(
host: ip,
Expand All @@ -176,36 +176,40 @@ def run_host(ip)
)
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_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)
tmp_key = result.credential.private
ssh_key = SSHKey.new tmp_key
print_brute level: :good, ip: ip, msg: "Success: '#{result.credential}' '#{result.proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'"
ssh_key = Net::SSH::KeyFactory.load_data_private_key(credential_data[:private_data], datastore['key_pass'], false)

begin
credential_core = create_credential(credential_data)
credential_data[:core] = credential_core
create_credential_login(credential_data)
rescue ::StandardError => e
print_brute level: :info, ip: ip, msg: "Failed to create credential: #{e.class} #{e}"
print_brute level: :warn, ip: ip, msg: 'We do not currently support storing password protected SSH keys: https://github.com/rapid7/metasploit-framework/issues/20598'
credential_core = nil
end

if datastore['CreateSession']
if credential_core.is_a? Metasploit::Credential::Core
session_setup(result, scanner, ssh_key.fingerprint, credential_core.private_id)
else
session_setup(result, scanner, ssh_key.fingerprint, nil)
end
cred_id = credential_core.is_a?(Metasploit::Credential::Core) ? credential_core.private_id : nil
session_setup(result, scanner, ssh_key.public_key.fingerprint, cred_id)
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 = '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
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
if datastore['VERBOSE']
print_brute :level => :verror, :ip => ip, :msg => "Could not connect: #{result.proof}"
print_brute level: :verror, ip: ip, msg: "Could not connect: #{result.proof}"
end
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
invalidate_login(credential_data)
:abort
when Metasploit::Model::Login::Status::INCORRECT
if datastore['VERBOSE']
print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
print_brute level: :verror, ip: ip, msg: "Failed: '#{result.credential}'"
end
invalidate_login(credential_data)
scanner.ssh_socket.close if scanner.ssh_socket && !scanner.ssh_socket.closed?
Expand All @@ -224,7 +228,7 @@ class KeyCollection < Metasploit::Framework::CredentialCollection

# Override CredentialCollection#has_privates?
def has_privates?
!@key_data.empty?
@key_data.present?
end

def realm
Expand All @@ -235,49 +239,62 @@ def valid?
@error_list = []
@key_data = Set.new

unless @private_key.present? || @key_path.present?
raise RuntimeError, "No key path or key provided"
if @private_key.present?
results = validate_private_key(@private_key)
elsif @key_path.present?
results = validate_key_path(@key_path)
else
@error_list << 'No key path or key provided'
raise RuntimeError, 'No key path or key provided'
end

if @key_path.present?
if File.directory?(@key_path)
@key_files ||= Dir.entries(@key_path).reject { |f| f =~ /^\x2e|\x2epub$/ }
@key_files.each do |f|
begin
data = read_key(File.join(@key_path, f))
@key_data << data if valid_key?(data)
rescue StandardError => e
@error_list << "#{File.join(@key_path, f)}: #{e}"
end
end
elsif File.file?(@key_path)
begin
data = read_key(@key_path)
@key_data << data if valid_key?(data)
rescue StandardError => e
@error_list << "#{@key_path} could not be read, #{e}"
end
else
raise RuntimeError, "Invalid key path"
end
if results[:key_data].present?
@key_data.merge(results[:key_data])
else
@error_list.concat(results[:error_list]) if results[:error_list].present?
end

if @private_key.present?
data = Net::SSH::KeyFactory.load_data_private_key(@private_key, @password, false).to_s
if valid_key?(data)
@key_data << data
else
raise RuntimeError, "Invalid private key"
@key_data.present?
end

def validate_private_key(private_key)
key_data = Set.new
error_list = []
begin
if Net::SSH::KeyFactory.load_data_private_key(private_key, @password, false).present?
key_data << private_key
end
rescue StandardError => e
error_list << "Error validating private key: #{e}"
end

!@key_data.empty?
{key_data: key_data, error_list: error_list}
end

def valid_key?(key_data)
!!(key_data.match(/BEGIN [RECD]SA PRIVATE KEY/) && !key_data.match(/Proc-Type:.*ENCRYPTED/))
def validate_key_path(key_path)
key_data = Set.new
error_list = []

if File.file?(key_path)
key_files = [key_path]
elsif File.directory?(key_path)
key_files = Dir.entries(key_path).reject { |f| f =~ /^\x2e|\x2epub$/ }.map { |f| File.join(key_path, f) }
else
return {key_data: nil, error: "#{key_path} Invalid key path"}
end

key_files.each do |f|
begin
if read_key(f).present?
key_data << File.read(f)
end
rescue StandardError => e
error_list << "#{f}: #{e}"
end
end
{key_data: key_data, error_list: error_list}
end


def each
prepended_creds.each { |c| yield c }

Expand Down Expand Up @@ -307,7 +324,7 @@ def each_key

def read_key(file_path)
@cache ||= {}
@cache[file_path] ||= Net::SSH::KeyFactory.load_data_private_key(File.read(file_path), password, false, key_path).to_s
@cache[file_path] ||= Net::SSH::KeyFactory.load_private_key(file_path, password, false)
@cache[file_path]
end
end
Expand Down
5 changes: 4 additions & 1 deletion spec/lib/metasploit/framework/login_scanner/ssh_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'spec_helper'
require 'metasploit/framework/login_scanner/ssh'
require 'metasploit/framework/credential_collection'

RSpec.describe Metasploit::Framework::LoginScanner::SSH do
let(:public) { 'root' }
Expand Down Expand Up @@ -48,7 +49,8 @@
}

let(:detail_group) {
[ pub_blank, pub_pub, pub_pri]
Metasploit::Framework::CredentialCollection.new()
# [ pub_blank, pub_pub, pub_pri]
}

subject(:ssh_scanner) {
Expand Down Expand Up @@ -145,6 +147,7 @@
:proxy => factory,
:append_all_supported_algorithms => true,
:auth_methods => ['password','keyboard-interactive'],
:passphrase => nil,
:password => private,
:non_interactive => true,
:verify_host_key => :never
Expand Down
Loading