Merge pull request #102 from rapid7/feature/MSP-9707/smb-bruteforce-refactor
Feature/msp 9707/smb bruteforce refactor MSP-9707 #landbug/bundler_fix
commit
cc93dbbe29
|
@ -27,8 +27,6 @@ end
|
|||
|
||||
# Create a custom group
|
||||
group :local do
|
||||
# Use pry to help view and interact with objects in the framework
|
||||
gem 'pry', '~> 0.9'
|
||||
# Use pry-debugger to step through code during development
|
||||
gem 'pry-debugger', '~> 0.2'
|
||||
# Add the lab gem so that the 'lab' plugin will work again
|
||||
|
|
|
@ -17,6 +17,19 @@ module Metasploit
|
|||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::LoginScanner::NTLM
|
||||
|
||||
# Constants to be used in {Result#access_level}
|
||||
module AccessLevels
|
||||
# Administrative access. For SMB, this is defined as being
|
||||
# able to successfully Tree Connect to the `ADMIN$` share.
|
||||
# This definition is not without its problems, but suffices to
|
||||
# conclude that such a user will most likely be able to use
|
||||
# psexec.
|
||||
ADMINISTRATOR = "Administrator"
|
||||
# Guest access means our creds were accepted but the logon
|
||||
# session is not associated with a real user account.
|
||||
GUEST = "Guest"
|
||||
end
|
||||
|
||||
CAN_GET_SESSION = true
|
||||
DEFAULT_REALM = 'WORKSTATION'
|
||||
LIKELY_PORTS = [ 139, 445 ]
|
||||
|
@ -100,6 +113,27 @@ module Metasploit
|
|||
allow_nil: true
|
||||
|
||||
|
||||
# If login is successul and {Result#access_level} is not set
|
||||
# then arbitrary credentials are accepted. If it is set to
|
||||
# Guest, then arbitrary credentials are accepted, but given
|
||||
# Guest permissions.
|
||||
#
|
||||
# @param domain [String] Domain to authenticate against. Use an
|
||||
# empty string for local accounts.
|
||||
# @return [Result]
|
||||
def attempt_bogus_login(domain)
|
||||
if defined?(@result_for_bogus)
|
||||
return @result_for_bogus
|
||||
end
|
||||
cred = Credential.new(
|
||||
public: Rex::Text.rand_text_alpha(8),
|
||||
private: Rex::Text.rand_text_alpha(8),
|
||||
realm: domain
|
||||
)
|
||||
@result_for_bogus = attempt_login(cred)
|
||||
end
|
||||
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
|
||||
|
@ -121,7 +155,7 @@ module Metasploit
|
|||
|
||||
begin
|
||||
# TODO: OMG
|
||||
ok = simple.login(
|
||||
simple.login(
|
||||
smb_name,
|
||||
credential.public,
|
||||
credential.private,
|
||||
|
@ -140,16 +174,29 @@ module Metasploit
|
|||
}
|
||||
)
|
||||
|
||||
simple.connect("\\\\#{smb_name}\\IPC$")
|
||||
status = ok ? :success : :failed
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
# Windows SMB will return an error code during Session
|
||||
# Setup, but nix Samba requires a Tree Connect. Try admin$
|
||||
# first, since that will tell us if this user has local
|
||||
# admin access. Fall back to IPC$ which should be accessible
|
||||
# to any user with valid creds.
|
||||
begin
|
||||
simple.connect("\\\\#{host}\\admin$")
|
||||
access_level = AccessLevels::ADMINISTRATOR
|
||||
simple.disconnect("\\\\#{host}\\admin$")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
|
||||
simple.connect("\\\\#{host}\\IPC$")
|
||||
end
|
||||
|
||||
# If we made it this far without raising, we have a valid
|
||||
# login
|
||||
status = :success
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
|
||||
status = case e.get_error(e.error_code)
|
||||
when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES
|
||||
:correct
|
||||
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
|
||||
:failed
|
||||
else
|
||||
puts e.backtrace.join
|
||||
:failed
|
||||
end
|
||||
|
||||
|
@ -161,7 +208,11 @@ module Metasploit
|
|||
status = :connection_error
|
||||
end
|
||||
|
||||
Result.new(credential: credential, status: status, proof: proof)
|
||||
if status == :success && simple.client.auth_user.nil?
|
||||
access_level ||= AccessLevels::GUEST
|
||||
end
|
||||
|
||||
Result.new(credential: credential, status: status, proof: proof, access_level: access_level)
|
||||
end
|
||||
|
||||
def connect
|
||||
|
@ -206,6 +257,7 @@ module Metasploit
|
|||
self.smb_name = self.host if self.smb_name.nil?
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -736,6 +736,10 @@ class Error < ::RuntimeError
|
|||
super(*args)
|
||||
end
|
||||
|
||||
def error_name
|
||||
get_error(error_code)
|
||||
end
|
||||
|
||||
# returns an error string if it exists, otherwise just the error code
|
||||
def get_error(error)
|
||||
string = ''
|
||||
|
@ -807,7 +811,7 @@ end
|
|||
class ErrorCode < InvalidPacket
|
||||
def to_s
|
||||
'The server responded with error: ' +
|
||||
self.get_error(self.error_code) +
|
||||
self.error_name +
|
||||
" (Command=#{self.command} WordCount=#{self.word_count})"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/login_scanner/smb'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -15,8 +17,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
|
||||
attr_reader :accepts_bogus_domains
|
||||
|
||||
def proto
|
||||
'smb'
|
||||
end
|
||||
|
@ -50,30 +50,15 @@ class Metasploit3 < Msf::Auxiliary
|
|||
)
|
||||
deregister_options('RHOST','USERNAME','PASSWORD')
|
||||
|
||||
@accepts_guest_logins = {}
|
||||
|
||||
@correct_credentials_status_codes = [
|
||||
"STATUS_INVALID_LOGON_HOURS",
|
||||
"STATUS_INVALID_WORKSTATION",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_ACCOUNT_EXPIRED",
|
||||
"STATUS_ACCOUNT_DISABLED",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_PASSWORD_EXPIRED",
|
||||
"STATUS_PASSWORD_MUST_CHANGE",
|
||||
"STATUS_LOGON_TYPE_NOT_GRANTED"
|
||||
]
|
||||
|
||||
# These are normally advanced options, but for this module they have a
|
||||
# more active role, so make them regular options.
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SMBPass', [ false, "SMB Password" ]),
|
||||
OptString.new('SMBUser', [ false, "SMB Username" ]),
|
||||
OptString.new('SMBDomain', [ false, "SMB Domain", '']),
|
||||
OptBool.new('CHECK_ADMIN', [ false, "Check for Admin rights", false]),
|
||||
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]),
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false])
|
||||
OptString.new('SMBDomain', [ false, "SMB Domain", '' ]),
|
||||
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true ]),
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false ])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
@ -83,250 +68,124 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
domain = datastore['SMBDomain'] || ""
|
||||
|
||||
if accepts_bogus_logins?(domain)
|
||||
print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
return
|
||||
end
|
||||
@scanner = Metasploit::Framework::LoginScanner::SMB.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
connection_timeout: 5,
|
||||
)
|
||||
|
||||
unless datastore['RECORD_GUEST']
|
||||
if accepts_guest_logins?(domain)
|
||||
print_status("#{ip} - This system allows guest sessions with any credentials, these instances will not be recorded.")
|
||||
bogus_result = @scanner.attempt_bogus_login(domain)
|
||||
if bogus_result.success?
|
||||
if bogus_result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST
|
||||
print_status("#{ip} - This system allows guest sessions with any credentials")
|
||||
else
|
||||
print_error("#{ip} - This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
each_user_pass do |user, pass|
|
||||
result = try_user_pass(domain, user, pass)
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['SMBPass'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['SMBUser'],
|
||||
user_as_pass: datastore['USER_AS_PASS'],
|
||||
realm: domain,
|
||||
)
|
||||
|
||||
@scanner.cred_details = cred_collection
|
||||
|
||||
@scanner.scan! do |result|
|
||||
case result.status
|
||||
when :correct
|
||||
print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}', #{result.proof.error_name}"
|
||||
report_creds(ip, rport, result)
|
||||
:next_user
|
||||
when :success
|
||||
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}' #{result.access_level}"
|
||||
report_creds(ip, rport, result)
|
||||
:next_user
|
||||
when :connection_error
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Could not connect"
|
||||
:abort
|
||||
when :failed
|
||||
print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}', #{result.proof.error_name}"
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: result.credential.realm,
|
||||
status: :failed
|
||||
)
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def check_login_status(domain, user, pass)
|
||||
connect()
|
||||
status_code = ""
|
||||
begin
|
||||
simple.login(
|
||||
datastore['SMBName'],
|
||||
user,
|
||||
pass,
|
||||
domain,
|
||||
datastore['SMB::VerifySignature'],
|
||||
datastore['NTLM::UseNTLMv2'],
|
||||
datastore['NTLM::UseNTLM2_session'],
|
||||
datastore['NTLM::SendLM'],
|
||||
datastore['NTLM::UseLMKey'],
|
||||
datastore['NTLM::SendNTLM'],
|
||||
datastore['SMB::Native_OS'],
|
||||
datastore['SMB::Native_LM'],
|
||||
{:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
|
||||
)
|
||||
|
||||
# Windows SMB will return an error code during Session Setup, but nix Samba requires a Tree Connect:
|
||||
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
status_code = 'STATUS_SUCCESS'
|
||||
|
||||
if datastore['CHECK_ADMIN']
|
||||
status_code = :not_admin
|
||||
# Drop the existing connection to IPC$ in order to connect to admin$
|
||||
simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
begin
|
||||
simple.connect("\\\\#{datastore['RHOST']}\\admin$")
|
||||
status_code = :admin_access
|
||||
simple.disconnect("\\\\#{datastore['RHOST']}\\admin$")
|
||||
rescue
|
||||
status_code = :not_admin
|
||||
ensure
|
||||
begin
|
||||
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::NoReply
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
status_code = e.get_error(e.error_code)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
|
||||
status_code = e.error_reason
|
||||
rescue ::Rex::Proto::SMB::Exceptions::InvalidWordCount => e
|
||||
status_code = e.get_error(e.error_code)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::NoReply
|
||||
ensure
|
||||
disconnect()
|
||||
end
|
||||
|
||||
return status_code
|
||||
end
|
||||
|
||||
# If login is succesful and auth_user is unset
|
||||
# the login was as a guest user.
|
||||
def accepts_guest_logins?(domain)
|
||||
guest = false
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
|
||||
guest_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && simple.client.auth_user.nil?)
|
||||
|
||||
if guest_login
|
||||
@accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost)
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => 'accepts guest login from any account',
|
||||
:update => :unique_data
|
||||
)
|
||||
end
|
||||
|
||||
return guest_login
|
||||
end
|
||||
|
||||
# If login is successul and auth_user is set
|
||||
# then bogus creds are accepted.
|
||||
def accepts_bogus_logins?(domain)
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
bogus_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && !simple.client.auth_user.nil?)
|
||||
return bogus_login
|
||||
end
|
||||
|
||||
# This logic is not universal ie a local account will not care about workgroup
|
||||
# but remote domain authentication will so check each instance
|
||||
def accepts_bogus_domains?(user, pass, rhost)
|
||||
domain = Rex::Text.rand_text_alpha(8)
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
bogus_domain = valid_credentials?(status)
|
||||
if bogus_domain
|
||||
vprint_status "Domain is ignored"
|
||||
end
|
||||
|
||||
return valid_credentials?(status)
|
||||
end
|
||||
|
||||
def valid_credentials?(status)
|
||||
|
||||
case status
|
||||
when 'STATUS_SUCCESS', :admin_access, :not_admin
|
||||
return true
|
||||
when *@correct_credentials_status_codes
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def try_user_pass(domain, user, pass)
|
||||
# Note that unless PRESERVE_DOMAINS is true, we're more
|
||||
# than happy to pass illegal usernames that contain
|
||||
# slashes.
|
||||
if datastore["PRESERVE_DOMAINS"]
|
||||
d,u = domain_username_split(user)
|
||||
user = u
|
||||
domain = d if d
|
||||
end
|
||||
|
||||
user = user.to_s.gsub(/<BLANK>/i,"")
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
# Match original output message
|
||||
if domain.empty? || domain == "."
|
||||
domain_part = ""
|
||||
else
|
||||
domain_part = " \\\\#{domain}"
|
||||
end
|
||||
output_message = "#{rhost}:#{rport}#{domain_part} - ".gsub('%', '%%')
|
||||
output_message << "%s"
|
||||
output_message << " (#{smb_peer_os}) #{user} : #{pass} [#{status}]".gsub('%', '%%')
|
||||
|
||||
case status
|
||||
when 'STATUS_SUCCESS', :admin_access, :not_admin
|
||||
# Auth user indicates if the login was as a guest or not
|
||||
if(simple.client.auth_user)
|
||||
print_good(output_message % "SUCCESSFUL LOGIN")
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
report_creds(domain,user,pass,true)
|
||||
else
|
||||
if datastore['RECORD_GUEST']
|
||||
print_status(output_message % "GUEST LOGIN")
|
||||
report_creds(domain,user,pass,true)
|
||||
elsif datastore['VERBOSE']
|
||||
print_status(output_message % "GUEST LOGIN")
|
||||
end
|
||||
end
|
||||
|
||||
return :next_user
|
||||
|
||||
when *@correct_credentials_status_codes
|
||||
print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" )
|
||||
report_creds(domain,user,pass,false)
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
return :skip_user
|
||||
|
||||
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
else
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
end
|
||||
end
|
||||
|
||||
def validuser_case_sensitive?(domain, user, pass)
|
||||
if user == user.downcase
|
||||
user = user.upcase
|
||||
else
|
||||
user = user.downcase
|
||||
end
|
||||
|
||||
status = check_login_status(domain, user, pass)
|
||||
case_insensitive = valid_credentials?(status)
|
||||
if case_insensitive
|
||||
vprint_status("Username is case insensitive")
|
||||
end
|
||||
|
||||
return case_insensitive
|
||||
end
|
||||
|
||||
def note_creds(domain,user,pass,reason)
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => {:user => user, :pass => pass, :status => reason},
|
||||
:update => :unique_data
|
||||
def accepts_bogus_domains?(user, pass)
|
||||
bogus_domain = @scanner.attempt_login(
|
||||
Metasploit::Framework::Credential.new(
|
||||
public: user,
|
||||
private: pass,
|
||||
realm: Rex::Text.rand_text_alpha(8)
|
||||
)
|
||||
)
|
||||
|
||||
return bogus_domain.success?
|
||||
end
|
||||
|
||||
def report_creds(domain,user,pass,active)
|
||||
login_name = ""
|
||||
|
||||
if accepts_bogus_domains?(user,pass,rhost) || domain.blank?
|
||||
login_name = user
|
||||
else
|
||||
login_name = "#{domain}\\#{user}"
|
||||
def report_creds(ip, port, result)
|
||||
if !datastore['RECORD_GUEST']
|
||||
if result.access_level == Metasploit::Framework::LoginScanner::SMB::AccessLevels::GUEST
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
report_hash = {
|
||||
:host => rhost,
|
||||
:port => datastore['RPORT'],
|
||||
:sname => 'smb',
|
||||
:user => login_name,
|
||||
:pass => pass,
|
||||
:source_type => "user_supplied",
|
||||
:active => active
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: port,
|
||||
service_name: 'smb',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/
|
||||
report_hash.merge!({:type => 'smb_hash'})
|
||||
else
|
||||
report_hash.merge!({:type => 'password'})
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public,
|
||||
}.merge(service_data)
|
||||
|
||||
if domain.present?
|
||||
if accepts_bogus_domains?(result.credential.public, result.credential.private)
|
||||
print_brute(:level => :vstatus, :ip => ip, :msg => "Domain is ignored for user #{result.credential.public}")
|
||||
else
|
||||
credential_data.merge!(
|
||||
realm_key: Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
||||
realm_value: result.credential.realm
|
||||
)
|
||||
end
|
||||
end
|
||||
report_auth_info(report_hash)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
access_level: result.access_level,
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Credential::Login::Status::SUCCESSFUL
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,6 +72,9 @@ describe Metasploit::Framework::LoginScanner::SMB do
|
|||
end
|
||||
|
||||
context '#attempt_login' do
|
||||
before(:each) do
|
||||
login_scanner.stub_chain(:simple, :client, :auth_user, :nil?).and_return false
|
||||
end
|
||||
context 'when there is a connection error' do
|
||||
it 'returns a result with the connection_error status' do
|
||||
login_scanner.stub_chain(:simple, :login).and_raise ::Rex::ConnectionError
|
||||
|
@ -91,10 +94,13 @@ describe Metasploit::Framework::LoginScanner::SMB do
|
|||
0xC0000224, # => "STATUS_PASSWORD_MUST_CHANGE",
|
||||
].each do |code|
|
||||
it "returns a status of :correct" do
|
||||
exception = Rex::Proto::SMB::Exceptions::ErrorCode.new
|
||||
exception = Rex::Proto::SMB::Exceptions::LoginError.new
|
||||
exception.error_code = code
|
||||
|
||||
login_scanner.stub_chain(:simple, :login).and_raise exception
|
||||
login_scanner.stub_chain(:simple, :connect)
|
||||
login_scanner.stub_chain(:simple, :disconnect)
|
||||
login_scanner.stub_chain(:simple, :client, :auth_user, :nil?).and_return false
|
||||
|
||||
expect(login_scanner.attempt_login(pub_blank).status).to eq :correct
|
||||
end
|
||||
|
@ -105,16 +111,44 @@ describe Metasploit::Framework::LoginScanner::SMB do
|
|||
context 'when the login fails' do
|
||||
it 'returns a result object with a status of :failed' do
|
||||
login_scanner.stub_chain(:simple, :login).and_return false
|
||||
login_scanner.stub_chain(:simple, :connect)
|
||||
login_scanner.stub_chain(:simple, :connect).and_raise Rex::Proto::SMB::Exceptions::Error
|
||||
expect(login_scanner.attempt_login(pub_blank).status).to eq :failed
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the login succeeds' do
|
||||
it 'returns a result object with a status of :success' do
|
||||
login_scanner.stub_chain(:simple, :login).and_return true
|
||||
login_scanner.stub_chain(:simple, :connect)
|
||||
expect(login_scanner.attempt_login(pub_blank).status).to eq :success
|
||||
context 'and the user is local admin' do
|
||||
before(:each) do
|
||||
login_scanner.simple = double
|
||||
login_scanner.simple.stub(:connect).with(/.*admin\$/i)
|
||||
login_scanner.simple.stub(:connect).with(/.*ipc\$/i)
|
||||
login_scanner.simple.stub(:disconnect)
|
||||
end
|
||||
|
||||
it 'returns a result object with a status of :success' do
|
||||
login_scanner.stub_chain(:simple, :login).and_return true
|
||||
result = login_scanner.attempt_login(pub_blank)
|
||||
expect(result.status).to eq :success
|
||||
expect(result.access_level).to eq described_class::AccessLevels::ADMINISTRATOR
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the user is NOT local admin' do
|
||||
before(:each) do
|
||||
login_scanner.simple = double
|
||||
login_scanner.simple.stub(:connect).with(/.*admin\$/i).and_raise(
|
||||
# STATUS_ACCESS_DENIED
|
||||
Rex::Proto::SMB::Exceptions::ErrorCode.new.tap{|e|e.error_code = 0xC0000022}
|
||||
)
|
||||
login_scanner.simple.stub(:connect).with(/.*ipc\$/i)
|
||||
end
|
||||
|
||||
it 'returns a result object with a status of :success' do
|
||||
login_scanner.stub_chain(:simple, :login).and_return true
|
||||
result = login_scanner.attempt_login(pub_blank)
|
||||
expect(result.status).to eq :success
|
||||
expect(result.access_level).to_not eq described_class::AccessLevels::ADMINISTRATOR
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue