Merge branch 'feature/MSP-9684/sshkey_loginscanner' into staging/electro_release
MSP-9684 #landbug/bundler_fix
commit
ddee401e27
50
Gemfile.lock
50
Gemfile.lock
|
@ -1,61 +1,61 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (3.2.14)
|
||||
activesupport (= 3.2.14)
|
||||
activemodel (3.2.17)
|
||||
activesupport (= 3.2.17)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.14)
|
||||
activemodel (= 3.2.14)
|
||||
activesupport (= 3.2.14)
|
||||
activerecord (3.2.17)
|
||||
activemodel (= 3.2.17)
|
||||
activesupport (= 3.2.17)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activesupport (3.2.14)
|
||||
activesupport (3.2.17)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
arel (3.0.2)
|
||||
arel (3.0.3)
|
||||
bcrypt (3.1.7)
|
||||
builder (3.0.4)
|
||||
database_cleaner (1.1.1)
|
||||
diff-lcs (1.2.4)
|
||||
factory_girl (4.2.0)
|
||||
database_cleaner (1.2.0)
|
||||
diff-lcs (1.2.5)
|
||||
factory_girl (4.4.0)
|
||||
activesupport (>= 3.0.0)
|
||||
fivemat (1.2.1)
|
||||
i18n (0.6.5)
|
||||
json (1.8.0)
|
||||
i18n (0.6.9)
|
||||
json (1.8.1)
|
||||
metasploit_data_models (0.17.0)
|
||||
activerecord (>= 3.2.13)
|
||||
activesupport
|
||||
pg
|
||||
mini_portile (0.5.1)
|
||||
msgpack (0.5.5)
|
||||
mini_portile (0.5.3)
|
||||
msgpack (0.5.8)
|
||||
multi_json (1.0.4)
|
||||
network_interface (0.0.1)
|
||||
nokogiri (1.6.0)
|
||||
nokogiri (1.6.1)
|
||||
mini_portile (~> 0.5.0)
|
||||
packetfu (1.1.9)
|
||||
pcaprub (0.11.3)
|
||||
pg (0.16.0)
|
||||
rake (10.1.0)
|
||||
redcarpet (3.0.0)
|
||||
pg (0.17.1)
|
||||
rake (10.3.1)
|
||||
redcarpet (3.1.1)
|
||||
rkelly-remix (0.0.6)
|
||||
robots (0.10.1)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.5)
|
||||
rspec-expectations (2.14.2)
|
||||
rspec-core (2.14.8)
|
||||
rspec-expectations (2.14.5)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.3)
|
||||
shoulda-matchers (2.3.0)
|
||||
rspec-mocks (2.14.6)
|
||||
shoulda-matchers (2.6.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.5.4)
|
||||
multi_json (~> 1.0.3)
|
||||
simplecov-html (~> 0.5.3)
|
||||
simplecov-html (0.5.3)
|
||||
timecop (0.6.3)
|
||||
tzinfo (0.3.37)
|
||||
yard (0.8.7)
|
||||
timecop (0.7.1)
|
||||
tzinfo (0.3.39)
|
||||
yard (0.8.7.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
|
@ -38,6 +38,9 @@ module Metasploit
|
|||
# @!attribute port
|
||||
# @return [Fixnum] The port to connect to
|
||||
attr_accessor :port
|
||||
# @!attribute proxies
|
||||
# @return [String] The proxy directive to use for the socket
|
||||
attr_accessor :proxies
|
||||
# @!attribute ssh_socket
|
||||
# @return [Net::SSH::Connection::Session] The current SSH connection
|
||||
attr_accessor :ssh_socket
|
||||
|
@ -103,7 +106,8 @@ module Metasploit
|
|||
:disable_agent => true,
|
||||
:password => credential.private,
|
||||
:config => false,
|
||||
:verbose => verbosity
|
||||
:verbose => verbosity,
|
||||
:proxies => proxies
|
||||
}
|
||||
|
||||
result_options = {
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
require 'net/ssh'
|
||||
require 'metasploit/framework/login_scanner'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# This is the LoginScanner class for dealing with the Secure Shell protocol and PKI.
|
||||
# It is responsible for taking a single target, and a list of credentials
|
||||
# and attempting them. It then saves the results. In this case it is expecting
|
||||
# SSH private keys for the private credential.
|
||||
class SSHKey
|
||||
include ActiveModel::Validations
|
||||
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
VERBOSITIES = [
|
||||
:debug,
|
||||
:info,
|
||||
:warn,
|
||||
:error,
|
||||
:fatal
|
||||
]
|
||||
|
||||
# @!attribute connection_timeout
|
||||
# @return [Fixnum] The timeout in seconds for a single SSH connection
|
||||
attr_accessor :connection_timeout
|
||||
# @!attribute cred_details
|
||||
# @return [Array] An array of Credential objects
|
||||
attr_accessor :cred_details
|
||||
# @!attribute successes
|
||||
# @return [Array] Array of of result objects that failed
|
||||
attr_accessor :failures
|
||||
# @!attribute host
|
||||
# @return [String] The IP address or hostname to connect to
|
||||
attr_accessor :host
|
||||
# @!attribute port
|
||||
# @return [Fixnum] The port to connect to
|
||||
attr_accessor :port
|
||||
# @!attribute proxies
|
||||
# @return [String] The proxy directive to use for the socket
|
||||
attr_accessor :proxies
|
||||
# @!attribute ssh_socket
|
||||
# @return [Net::SSH::Connection::Session] The current SSH connection
|
||||
attr_accessor :ssh_socket
|
||||
# @!attribute stop_on_success
|
||||
# @return [Boolean] Whether the scanner should stop when it has found one working Credential
|
||||
attr_accessor :stop_on_success
|
||||
# @!attribute successes
|
||||
# @return [Array] Array of results that successfully logged in
|
||||
attr_accessor :successes
|
||||
# @!attribute verbosity
|
||||
# The verbosity level for the SSH client.
|
||||
#
|
||||
# @return [Symbol] An element of {VERBOSITIES}.
|
||||
attr_accessor :verbosity
|
||||
|
||||
validates :connection_timeout,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 1
|
||||
}
|
||||
|
||||
validates :cred_details, presence: true
|
||||
|
||||
validates :host, presence: true
|
||||
|
||||
validates :port,
|
||||
presence: true,
|
||||
numericality: {
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 1,
|
||||
less_than_or_equal_to: 65535
|
||||
}
|
||||
|
||||
validates :stop_on_success,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
validates :verbosity,
|
||||
presence: true,
|
||||
inclusion: { in: VERBOSITIES }
|
||||
|
||||
validate :host_address_must_be_valid
|
||||
|
||||
validate :validate_cred_details
|
||||
|
||||
# @param attributes [Hash{Symbol => String,nil}]
|
||||
def initialize(attributes={})
|
||||
attributes.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
self.successes= []
|
||||
self.failures=[]
|
||||
end
|
||||
|
||||
# This method attempts a single login with a single credential against the target
|
||||
# @param credential [Credential] The credential object to attmpt to login with
|
||||
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
|
||||
def attempt_login(credential)
|
||||
ssh_socket = nil
|
||||
opt_hash = {
|
||||
:auth_methods => ['publickey'],
|
||||
:port => port,
|
||||
:disable_agent => true,
|
||||
:key_data => credential.private,
|
||||
:config => false,
|
||||
:verbose => verbosity,
|
||||
:proxies => proxies
|
||||
}
|
||||
|
||||
result_options = {
|
||||
private: credential.private,
|
||||
public: credential.public,
|
||||
realm: nil
|
||||
}
|
||||
begin
|
||||
::Timeout.timeout(connection_timeout) do
|
||||
ssh_socket = Net::SSH.start(
|
||||
host,
|
||||
credential.public,
|
||||
opt_hash
|
||||
)
|
||||
end
|
||||
rescue ::EOFError, Net::SSH::Disconnect, Rex::AddressInUse, Rex::ConnectionError, ::Timeout::Error
|
||||
result_options.merge!( proof: nil, status: :connection_error)
|
||||
rescue Net::SSH::Exception
|
||||
result_options.merge!( proof: nil, status: :failed)
|
||||
end
|
||||
|
||||
unless result_options.has_key? :status
|
||||
if ssh_socket
|
||||
proof = gather_proof
|
||||
result_options.merge!( proof: proof, status: :success)
|
||||
else
|
||||
result_options.merge!( proof: nil, status: :failed)
|
||||
end
|
||||
end
|
||||
|
||||
::Metasploit::Framework::LoginScanner::Result.new(result_options)
|
||||
|
||||
end
|
||||
|
||||
# This method runs all the login attempts against the target.
|
||||
# It calls {attempt_login} once for each credential.
|
||||
# Results are stored in {successes} and {failures}
|
||||
# @return [void] There is no valid return value for this method
|
||||
# @yield [result]
|
||||
# @yieldparam result [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object for the attempt
|
||||
# @yieldreturn [void]
|
||||
def scan!
|
||||
valid!
|
||||
cred_details.each do |credential|
|
||||
result = attempt_login(credential)
|
||||
result.freeze
|
||||
|
||||
yield result if block_given?
|
||||
|
||||
if result.success?
|
||||
successes << result
|
||||
break if stop_on_success
|
||||
else
|
||||
failures << result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @raise [Metasploit::Framework::LoginScanner::Invalid] if the attributes are not valid on the scanner
|
||||
def valid!
|
||||
unless valid?
|
||||
raise Metasploit::Framework::LoginScanner::Invalid.new(self)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method attempts to gather proof that we successfuly logged in.
|
||||
# @return [String] The proof of a connection, May be empty.
|
||||
def gather_proof
|
||||
proof = ''
|
||||
begin
|
||||
Timeout.timeout(5) do
|
||||
proof = ssh_socket.exec!("id\n").to_s
|
||||
if(proof =~ /id=/)
|
||||
proof << ssh_socket.exec!("uname -a\n").to_s
|
||||
else
|
||||
# Cisco IOS
|
||||
if proof =~ /Unknown command or computer name/
|
||||
proof = ssh_socket.exec!("ver\n").to_s
|
||||
else
|
||||
proof << ssh_socket.exec!("help\n?\n\n\n").to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ::Exception
|
||||
end
|
||||
proof
|
||||
end
|
||||
|
||||
# This method validates that the host address is both
|
||||
# of a valid type and is resolveable.
|
||||
# @return [void]
|
||||
def host_address_must_be_valid
|
||||
unless host.kind_of? String
|
||||
errors.add(:host, "must be a string")
|
||||
end
|
||||
begin
|
||||
resolved_host = ::Rex::Socket.getaddress(host, true)
|
||||
if host =~ /^\d{1,3}(\.\d{1,3}){1,3}$/
|
||||
unless host =~ Rex::Socket::MATCH_IPV4
|
||||
errors.add(:host, "could not be resolved")
|
||||
end
|
||||
end
|
||||
host = resolved_host
|
||||
rescue
|
||||
errors.add(:host, "could not be resolved")
|
||||
end
|
||||
end
|
||||
|
||||
# This method validates that the credentials supplied
|
||||
# are all valid.
|
||||
# @return [void]
|
||||
def validate_cred_details
|
||||
if cred_details.kind_of? Array
|
||||
cred_details.each do |detail|
|
||||
unless detail.kind_of? Metasploit::Framework::LoginScanner::Credential
|
||||
errors.add(:cred_details, "has invalid element #{detail.inspect}")
|
||||
next
|
||||
end
|
||||
unless detail.valid?
|
||||
errors.add(:cred_details, "has invalid element #{detail.inspect}")
|
||||
end
|
||||
end
|
||||
else
|
||||
errors.add(:cred_details, "must be an array")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,437 @@
|
|||
require 'spec_helper'
|
||||
require 'metasploit/framework/login_scanner/ssh_key'
|
||||
|
||||
describe Metasploit::Framework::LoginScanner::SSHKey do
|
||||
let(:public) { 'root' }
|
||||
let(:private) { OpenSSL::PKey::RSA.generate(2048).to_s }
|
||||
|
||||
let(:pub_pri) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: public,
|
||||
private: private
|
||||
)
|
||||
}
|
||||
|
||||
let(:invalid_detail) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: nil,
|
||||
private: nil
|
||||
)
|
||||
}
|
||||
|
||||
let(:detail_group) {
|
||||
[ pub_pri]
|
||||
}
|
||||
|
||||
subject(:ssh_scanner) {
|
||||
described_class.new
|
||||
}
|
||||
|
||||
it { should respond_to :port }
|
||||
it { should respond_to :host }
|
||||
it { should respond_to :cred_details }
|
||||
it { should respond_to :connection_timeout }
|
||||
it { should respond_to :verbosity }
|
||||
it { should respond_to :stop_on_success }
|
||||
it { should respond_to :valid! }
|
||||
it { should respond_to :scan! }
|
||||
it { should respond_to :proxies }
|
||||
|
||||
|
||||
context 'validations' do
|
||||
context 'port' do
|
||||
|
||||
it 'is not valid for not set' do
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "is not a number"
|
||||
end
|
||||
|
||||
it 'is not valid for a non-number' do
|
||||
ssh_scanner.port = "a"
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "is not a number"
|
||||
end
|
||||
|
||||
it 'is not valid for a floating point' do
|
||||
ssh_scanner.port = 5.76
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "must be an integer"
|
||||
end
|
||||
|
||||
it 'is not valid for a negative number' do
|
||||
ssh_scanner.port = -8
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "must be greater than or equal to 1"
|
||||
end
|
||||
|
||||
it 'is not valid for 0' do
|
||||
ssh_scanner.port = 0
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "must be greater than or equal to 1"
|
||||
end
|
||||
|
||||
it 'is not valid for a number greater than 65535' do
|
||||
ssh_scanner.port = rand(1000) + 65535
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:port]).to include "must be less than or equal to 65535"
|
||||
end
|
||||
|
||||
it 'is valid for a legitimate port number' do
|
||||
ssh_scanner.port = rand(65534) + 1
|
||||
expect(ssh_scanner.errors[:port]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'host' do
|
||||
|
||||
it 'is not valid for not set' do
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "can't be blank"
|
||||
end
|
||||
|
||||
it 'is not valid for a non-string input' do
|
||||
ssh_scanner.host = 5
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "must be a string"
|
||||
end
|
||||
|
||||
it 'is not valid for an improper IP address' do
|
||||
ssh_scanner.host = '192.168.1.1.5'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "could not be resolved"
|
||||
end
|
||||
|
||||
it 'is not valid for an incomplete IP address' do
|
||||
ssh_scanner.host = '192.168'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "could not be resolved"
|
||||
end
|
||||
|
||||
it 'is not valid for an invalid IP address' do
|
||||
ssh_scanner.host = '192.300.675.123'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "could not be resolved"
|
||||
end
|
||||
|
||||
it 'is not valid for DNS name that cannot be resolved' do
|
||||
ssh_scanner.host = 'nosuchplace.metasploit.com'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:host]).to include "could not be resolved"
|
||||
end
|
||||
|
||||
it 'is valid for a valid IP address' do
|
||||
ssh_scanner.host = '127.0.0.1'
|
||||
expect(ssh_scanner.errors[:host]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for a DNS name it can resolve' do
|
||||
ssh_scanner.host = 'localhost'
|
||||
expect(ssh_scanner.errors[:host]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'cred_details' do
|
||||
it 'is not valid for not set' do
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:cred_details]).to include "can't be blank"
|
||||
end
|
||||
|
||||
it 'is not valid for a non-array input' do
|
||||
ssh_scanner.cred_details = rand(10)
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:cred_details]).to include "must be an array"
|
||||
end
|
||||
|
||||
it 'is not valid if any of the elements are not a Credential' do
|
||||
ssh_scanner.cred_details = [1,2]
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:cred_details]).to include "has invalid element 1"
|
||||
end
|
||||
|
||||
it 'is not valid if any of the CredDetails are invalid' do
|
||||
ssh_scanner.cred_details = [pub_pri, invalid_detail]
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
end
|
||||
|
||||
it 'is valid if all of the elements are valid' do
|
||||
ssh_scanner.cred_details = detail_group
|
||||
expect(ssh_scanner.errors[:cred_details]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'connection_timeout' do
|
||||
|
||||
it 'is not valid for not set' do
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to include "is not a number"
|
||||
end
|
||||
|
||||
it 'is not valid for a non-number' do
|
||||
ssh_scanner.connection_timeout = "a"
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to include "is not a number"
|
||||
end
|
||||
|
||||
it 'is not valid for a floating point' do
|
||||
ssh_scanner.connection_timeout = 5.76
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to include "must be an integer"
|
||||
end
|
||||
|
||||
it 'is not valid for a negative number' do
|
||||
ssh_scanner.connection_timeout = -8
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to include "must be greater than or equal to 1"
|
||||
end
|
||||
|
||||
it 'is not valid for 0' do
|
||||
ssh_scanner.connection_timeout = 0
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to include "must be greater than or equal to 1"
|
||||
end
|
||||
|
||||
it 'is valid for a legitimate number' do
|
||||
ssh_scanner.port = rand(1000) + 1
|
||||
expect(ssh_scanner.errors[:connection_timeout]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'verbosity' do
|
||||
|
||||
it 'is valid with :debug' do
|
||||
ssh_scanner.verbosity = :debug
|
||||
expect(ssh_scanner.errors[:verbosity]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid with :info' do
|
||||
ssh_scanner.verbosity = :info
|
||||
expect(ssh_scanner.errors[:verbosity]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid with :warn' do
|
||||
ssh_scanner.verbosity = :warn
|
||||
expect(ssh_scanner.errors[:verbosity]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid with :error' do
|
||||
ssh_scanner.verbosity = :error
|
||||
expect(ssh_scanner.errors[:verbosity]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid with :fatal' do
|
||||
ssh_scanner.verbosity = :fatal
|
||||
expect(ssh_scanner.errors[:verbosity]).to be_empty
|
||||
end
|
||||
|
||||
it 'is invalid with a random symbol' do
|
||||
ssh_scanner.verbosity = :foobar
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:verbosity]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is invalid with a string' do
|
||||
ssh_scanner.verbosity = 'debug'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:verbosity]).to include 'is not included in the list'
|
||||
end
|
||||
end
|
||||
|
||||
context 'stop_on_success' do
|
||||
|
||||
it 'is not valid for not set' do
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:stop_on_success]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is not valid for the string true' do
|
||||
ssh_scanner.stop_on_success = 'true'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:stop_on_success]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is not valid for the string false' do
|
||||
ssh_scanner.stop_on_success = 'false'
|
||||
expect(ssh_scanner).to_not be_valid
|
||||
expect(ssh_scanner.errors[:stop_on_success]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
ssh_scanner.stop_on_success = true
|
||||
expect(ssh_scanner.errors[:stop_on_success]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
ssh_scanner.stop_on_success = false
|
||||
expect(ssh_scanner.errors[:stop_on_success]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context '#valid!' do
|
||||
it 'raises a Metasploit::Framework::LoginScanner::Invalid when validations fail' do
|
||||
expect{ssh_scanner.valid!}.to raise_error Metasploit::Framework::LoginScanner::Invalid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#attempt_login' do
|
||||
before(:each) do
|
||||
ssh_scanner.host = '127.0.0.1'
|
||||
ssh_scanner.port = 22
|
||||
ssh_scanner.connection_timeout = 30
|
||||
ssh_scanner.verbosity = :fatal
|
||||
ssh_scanner.stop_on_success = true
|
||||
ssh_scanner.cred_details = detail_group
|
||||
end
|
||||
|
||||
it 'creates a Timeout based on the connection_timeout' do
|
||||
::Timeout.should_receive(:timeout).with(ssh_scanner.connection_timeout)
|
||||
ssh_scanner.attempt_login(pub_pri)
|
||||
end
|
||||
|
||||
it 'calls Net::SSH with the correct arguments' do
|
||||
opt_hash = {
|
||||
:auth_methods => ['publickey'],
|
||||
:port => ssh_scanner.port,
|
||||
:disable_agent => true,
|
||||
:key_data => private,
|
||||
:config => false,
|
||||
:verbose => ssh_scanner.verbosity,
|
||||
:proxies => nil
|
||||
}
|
||||
Net::SSH.should_receive(:start).with(
|
||||
ssh_scanner.host,
|
||||
public,
|
||||
opt_hash
|
||||
)
|
||||
ssh_scanner.attempt_login(pub_pri)
|
||||
end
|
||||
|
||||
context 'when it fails' do
|
||||
|
||||
it 'returns :connection_error for a Rex::ConnectionError' do
|
||||
Net::SSH.should_receive(:start) { raise Rex::ConnectionError }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :connection_error
|
||||
end
|
||||
|
||||
it 'returns :connection_error for a Rex::AddressInUse' do
|
||||
Net::SSH.should_receive(:start) { raise Rex::AddressInUse }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :connection_error
|
||||
end
|
||||
|
||||
it 'returns :connection_disconnect for a Net::SSH::Disconnect' do
|
||||
Net::SSH.should_receive(:start) { raise Net::SSH::Disconnect }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :connection_error
|
||||
end
|
||||
|
||||
it 'returns :connection_disconnect for a ::EOFError' do
|
||||
Net::SSH.should_receive(:start) { raise ::EOFError }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :connection_error
|
||||
end
|
||||
|
||||
it 'returns :connection_disconnect for a ::Timeout::Error' do
|
||||
Net::SSH.should_receive(:start) { raise ::Timeout::Error }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :connection_error
|
||||
end
|
||||
|
||||
it 'returns [:fail,nil] for a Net::SSH::Exception' do
|
||||
Net::SSH.should_receive(:start) { raise Net::SSH::Exception }
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :failed
|
||||
end
|
||||
|
||||
it 'returns [:fail,nil] if no socket returned' do
|
||||
Net::SSH.should_receive(:start).and_return nil
|
||||
expect(ssh_scanner.attempt_login(pub_pri).status).to eq :failed
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it succeeds' do
|
||||
|
||||
it 'gathers proof of the connections' do
|
||||
Net::SSH.should_receive(:start) {"fake_socket"}
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:gather_proof)
|
||||
my_scanner.attempt_login(pub_pri)
|
||||
end
|
||||
|
||||
it 'returns a success code and proof' do
|
||||
Net::SSH.should_receive(:start) {"fake_socket"}
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:gather_proof).and_return(public)
|
||||
expect(my_scanner.attempt_login(pub_pri).status).to eq :success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#scan!' do
|
||||
let(:success) {
|
||||
::Metasploit::Framework::LoginScanner::Result.new(
|
||||
private: public,
|
||||
proof: '',
|
||||
public: private,
|
||||
realm: nil,
|
||||
status: :success
|
||||
)
|
||||
}
|
||||
|
||||
let(:failure) {
|
||||
::Metasploit::Framework::LoginScanner::Result.new(
|
||||
private: private,
|
||||
proof: nil,
|
||||
public: public,
|
||||
realm: nil,
|
||||
status: :failed
|
||||
)
|
||||
}
|
||||
|
||||
before(:each) do
|
||||
ssh_scanner.host = '127.0.0.1'
|
||||
ssh_scanner.port = 22
|
||||
ssh_scanner.connection_timeout = 30
|
||||
ssh_scanner.verbosity = :fatal
|
||||
ssh_scanner.stop_on_success = false
|
||||
ssh_scanner.cred_details = detail_group
|
||||
end
|
||||
|
||||
it 'calls valid! before running' do
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:scan!).and_call_original
|
||||
my_scanner.scan!
|
||||
end
|
||||
|
||||
it 'call attempt_login once for each cred_detail' do
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:attempt_login).once.with(pub_pri).and_call_original
|
||||
my_scanner.scan!
|
||||
end
|
||||
|
||||
it 'adds the failed results to the failures attribute' do
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:attempt_login).once.with(pub_pri).and_return failure
|
||||
my_scanner.scan!
|
||||
expect(my_scanner.failures).to include failure
|
||||
end
|
||||
|
||||
it 'adds the success results to the successes attribute' do
|
||||
my_scanner = ssh_scanner
|
||||
my_scanner.should_receive(:attempt_login).once.with(pub_pri).and_return success
|
||||
my_scanner.scan!
|
||||
expect(my_scanner.successes).to include success
|
||||
end
|
||||
|
||||
context 'when stop_on_success is true' do
|
||||
before(:each) do
|
||||
ssh_scanner.host = '127.0.0.1'
|
||||
ssh_scanner.port = 22
|
||||
ssh_scanner.connection_timeout = 30
|
||||
ssh_scanner.verbosity = :fatal
|
||||
ssh_scanner.stop_on_success = true
|
||||
ssh_scanner.cred_details = detail_group
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -53,6 +53,7 @@ describe Metasploit::Framework::LoginScanner::SSH do
|
|||
it { should respond_to :stop_on_success }
|
||||
it { should respond_to :valid! }
|
||||
it { should respond_to :scan! }
|
||||
it { should respond_to :proxies }
|
||||
|
||||
|
||||
context 'validations' do
|
||||
|
@ -312,7 +313,8 @@ describe Metasploit::Framework::LoginScanner::SSH do
|
|||
:disable_agent => true,
|
||||
:password => private,
|
||||
:config => false,
|
||||
:verbose => ssh_scanner.verbosity
|
||||
:verbose => ssh_scanner.verbosity,
|
||||
:proxies => nil
|
||||
}
|
||||
Net::SSH.should_receive(:start).with(
|
||||
ssh_scanner.host,
|
||||
|
|
Loading…
Reference in New Issue