Add specs, validations for LoginScanner::SMB
parent
ee6a9f99b3
commit
3831042dca
|
@ -273,4 +273,4 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,6 +49,7 @@ module Metasploit
|
|||
self.send_lm = true if self.send_lm.nil?
|
||||
self.send_ntlm = true if self.send_ntlm.nil?
|
||||
self.send_spn = true if self.send_spn.nil?
|
||||
self.use_lmkey = false if self.use_lmkey.nil?
|
||||
self.use_ntlm2_session = true if self.use_ntlm2_session.nil?
|
||||
self.use_ntlmv2 = true if self.use_ntlmv2.nil?
|
||||
self.windows_authentication = false if self.windows_authentication.nil?
|
||||
|
@ -58,4 +59,4 @@ module Metasploit
|
|||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,23 +12,27 @@ module Metasploit
|
|||
|
||||
included do
|
||||
# @!attribute send_lm
|
||||
# @return [Boolean] Whether or not to always send the LANMAN response(except if using NTLM2 Session)
|
||||
# @return [Boolean] Whether to always send the LANMAN response(except if using NTLM2 Session)
|
||||
attr_accessor :send_lm
|
||||
|
||||
# @!attribute send_ntlm
|
||||
# @return [Boolean] Whether or not to use NTLM responses
|
||||
# @return [Boolean] Whether to use NTLM responses
|
||||
attr_accessor :send_ntlm
|
||||
|
||||
# @!attribute send_spn
|
||||
# @return [Boolean] Whether or not to support SPN for newer Windows OSes
|
||||
# @return [Boolean] Whether to support SPN for newer Windows OSes
|
||||
attr_accessor :send_spn
|
||||
|
||||
# @!attribute use_lmkey
|
||||
# @return [Boolean] Whether to negotiate with a LANMAN key
|
||||
attr_accessor :use_lmkey
|
||||
|
||||
# @!attribute send_lm
|
||||
# @return [Boolean] Whether or not to force the use of NTLM2 session
|
||||
# @return [Boolean] Whether to force the use of NTLM2 session
|
||||
attr_accessor :use_ntlm2_session
|
||||
|
||||
# @!attribute send_lm
|
||||
# @return [Boolean] Whether or not to force the use of NTLMv2 instead of NTLM2 Session
|
||||
# @return [Boolean] Whether to force the use of NTLMv2 instead of NTLM2 Session
|
||||
attr_accessor :use_ntlmv2
|
||||
|
||||
validates :send_lm,
|
||||
|
@ -40,6 +44,9 @@ module Metasploit
|
|||
validates :send_spn,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
validates :use_lmkey,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
validates :use_ntlm2_session,
|
||||
inclusion: { in: [true, false] }
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
require 'rex/proto/smb'
|
||||
require 'metasploit/framework'
|
||||
require 'metasploit/framework/smb'
|
||||
require 'metasploit/framework/tcp/client'
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'metasploit/framework/login_scanner/ntlm'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
|
@ -8,36 +13,188 @@ module Metasploit
|
|||
# This is the LoginScanner class for dealing with the Server Messaging
|
||||
# Block protocol.
|
||||
class SMB
|
||||
include Metasploit::Framework::Tcp::Client
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::Tcp
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
include Metasploit::Framework::LoginScanner::NTLM
|
||||
|
||||
|
||||
module StatusCodes
|
||||
CORRECT_CREDENTIAL_STATUS_CODES = [
|
||||
"STATUS_ACCOUNT_DISABLED",
|
||||
"STATUS_ACCOUNT_EXPIRED",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_INVALID_LOGON_HOURS",
|
||||
"STATUS_INVALID_WORKSTATION",
|
||||
"STATUS_LOGON_TYPE_NOT_GRANTED",
|
||||
"STATUS_PASSWORD_EXPIRED",
|
||||
"STATUS_PASSWORD_MUST_CHANGE",
|
||||
].freeze.map(&:freeze)
|
||||
end
|
||||
|
||||
|
||||
# @!attribute simple
|
||||
# @return [Rex::Proto::SMB::SimpleClient]
|
||||
attr_accessor :simple
|
||||
|
||||
attr_accessor :smb_chunk_size
|
||||
attr_accessor :smb_name
|
||||
attr_accessor :smb_native_lm
|
||||
attr_accessor :smb_native_os
|
||||
attr_accessor :smb_obscure_trans_pipe_level
|
||||
attr_accessor :smb_pad_data_level
|
||||
attr_accessor :smb_pad_file_level
|
||||
attr_accessor :smb_pipe_evasion
|
||||
|
||||
# UNUSED
|
||||
#attr_accessor :smb_pipe_read_max_size
|
||||
#attr_accessor :smb_pipe_read_min_size
|
||||
#attr_accessor :smb_pipe_write_max_size
|
||||
#attr_accessor :smb_pipe_write_min_size
|
||||
attr_accessor :smb_verify_signature
|
||||
|
||||
attr_accessor :smb_direct
|
||||
|
||||
validates :smb_chunk_size,
|
||||
numericality:
|
||||
{
|
||||
only_integer: true,
|
||||
greater_than_or_equal_to: 0
|
||||
}
|
||||
|
||||
validates :smb_obscure_trans_pipe_level,
|
||||
inclusion:
|
||||
{
|
||||
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
|
||||
}
|
||||
|
||||
validates :smb_pad_data_level,
|
||||
inclusion:
|
||||
{
|
||||
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
|
||||
}
|
||||
|
||||
validates :smb_pad_file_level,
|
||||
inclusion:
|
||||
{
|
||||
in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX
|
||||
}
|
||||
|
||||
validates :smb_pipe_evasion,
|
||||
inclusion: { in: [true, false, nil] },
|
||||
allow_nil: true
|
||||
|
||||
# UNUSED
|
||||
#validates :smb_pipe_read_max_size, numericality: { only_integer: true }
|
||||
#validates :smb_pipe_read_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
#validates :smb_pipe_write_max_size, numericality: { only_integer: true }
|
||||
#validates :smb_pipe_write_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :smb_verify_signature,
|
||||
inclusion: { in: [true, false, nil] },
|
||||
allow_nil: true
|
||||
|
||||
|
||||
# (see Base#attempt_login)
|
||||
def attempt_login(credential)
|
||||
socket = connect
|
||||
|
||||
client = Rex::Proto::SMB::Client.new(socket)
|
||||
client.negotiate
|
||||
begin
|
||||
connect
|
||||
rescue ::Rex::ConnectionError => e
|
||||
return Result.new(credential:credential, status: :connection_error, proof: e)
|
||||
end
|
||||
proof = nil
|
||||
|
||||
begin
|
||||
ok = client.session_setup(credential.public, credential.private, credential.realm||".")
|
||||
# TODO: OMG
|
||||
ok = simple.login(
|
||||
smb_name,
|
||||
credential.public,
|
||||
credential.private,
|
||||
credential.realm || "",
|
||||
smb_verify_signature,
|
||||
use_ntlmv2,
|
||||
use_ntlm2_session,
|
||||
send_lm,
|
||||
use_lmkey,
|
||||
send_ntlm,
|
||||
smb_native_os,
|
||||
smb_native_lm,
|
||||
{
|
||||
use_spn: send_spn,
|
||||
name: host
|
||||
}
|
||||
)
|
||||
|
||||
simple.connect("\\\\#{smb_name}\\IPC$")
|
||||
status = ok ? :success : :failed
|
||||
rescue Rex::Proto::SMB::Exceptions::Error
|
||||
p $!
|
||||
rescue ::Rex::Proto::SMB::Exceptions::Error => 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
|
||||
proof = e
|
||||
rescue ::Rex::ConnectionError
|
||||
status = :connection_error
|
||||
end
|
||||
|
||||
Result.new(credential: credential, status: status)
|
||||
ensure
|
||||
socket.close unless socket.closed?
|
||||
Result.new(credential: credential, status: status, proof: proof)
|
||||
end
|
||||
|
||||
def connect
|
||||
disconnect
|
||||
self.sock = super
|
||||
|
||||
c = Rex::Proto::SMB::SimpleClient.new(sock, smb_direct)
|
||||
|
||||
c.client.evasion_opts['pad_data'] = smb_pad_data_level
|
||||
c.client.evasion_opts['pad_file'] = smb_pad_file_level
|
||||
c.client.evasion_opts['obscure_trans_pipe'] = smb_obscure_trans_pipe_level
|
||||
|
||||
self.simple = c
|
||||
c
|
||||
end
|
||||
|
||||
def set_sane_defaults
|
||||
self.connection_timeout = 10
|
||||
self.max_send_size = 0
|
||||
self.stop_on_success = false
|
||||
self.send_delay = 0
|
||||
end
|
||||
self.connection_timeout = 10 if self.connection_timeout.nil?
|
||||
self.max_send_size = 0 if self.max_send_size.nil?
|
||||
self.send_delay = 0 if self.send_delay.nil?
|
||||
self.send_lm = true if self.send_lm.nil?
|
||||
self.send_ntlm = true if self.send_ntlm.nil?
|
||||
self.send_spn = true if self.send_spn.nil?
|
||||
self.smb_chunk_size = 0 if self.smb_chunk_size.nil?
|
||||
self.smb_name = "*SMBSERVER" if self.smb_name.nil?
|
||||
self.smb_native_lm = "Windows 2000 5.0" if self.smb_native_os.nil?
|
||||
self.smb_native_os = "Windows 2000 2195" if self.smb_native_os.nil?
|
||||
self.smb_obscure_trans_pipe_level = 0 if self.smb_obscure_trans_pipe_level.nil?
|
||||
self.smb_pad_data_level = 0 if self.smb_pad_data_level.nil?
|
||||
self.smb_pad_file_level = 0 if self.smb_pad_file_level.nil?
|
||||
self.smb_pipe_evasion = false if self.smb_pipe_evasion.nil?
|
||||
#self.smb_pipe_read_max_size = 1024 if self.smb_pipe_read_max_size.nil?
|
||||
#self.smb_pipe_read_min_size = 0 if self.smb_pipe_read_min_size.nil?
|
||||
#self.smb_pipe_write_max_size = 1024 if self.smb_pipe_write_max_size.nil?
|
||||
#self.smb_pipe_write_min_size = 0 if self.smb_pipe_write_min_size.nil?
|
||||
self.smb_verify_signature = false if self.smb_verify_signature.nil?
|
||||
|
||||
self.use_lmkey = true if self.use_lmkey.nil?
|
||||
self.use_ntlm2_session = true if self.use_ntlm2_session.nil?
|
||||
self.use_ntlmv2 = true if self.use_ntlmv2.nil?
|
||||
|
||||
self.smb_name = self.host if self.smb_name.nil?
|
||||
|
||||
# Disable direct SMB when SMBDirect has not been set and the
|
||||
# destination port is configured as 139
|
||||
if self.smb_direct.nil?
|
||||
self.smb_direct = case self.port
|
||||
when 139 then false
|
||||
when 445 then true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -185,4 +185,4 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
require 'spec_helper'
|
||||
require 'metasploit/framework/login_scanner/smb'
|
||||
|
||||
describe Metasploit::Framework::LoginScanner::SMB do
|
||||
let(:public) { 'root' }
|
||||
let(:private) { 'toor' }
|
||||
|
||||
let(:pub_blank) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: public,
|
||||
private: ''
|
||||
)
|
||||
}
|
||||
|
||||
let(:pub_pub) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: public,
|
||||
private: public
|
||||
)
|
||||
}
|
||||
|
||||
let(:pub_pri) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: public,
|
||||
private: private
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
subject(:login_scanner) { described_class.new }
|
||||
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::Base'
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::NTLM'
|
||||
|
||||
it { should respond_to :smb_chunk_size }
|
||||
it { should respond_to :smb_name }
|
||||
it { should respond_to :smb_native_lm }
|
||||
it { should respond_to :smb_native_os }
|
||||
it { should respond_to :smb_obscure_trans_pipe_level }
|
||||
it { should respond_to :smb_pad_data_level }
|
||||
it { should respond_to :smb_pad_file_level }
|
||||
it { should respond_to :smb_pipe_evasion }
|
||||
|
||||
context 'validations' do
|
||||
context '#smb_verify_signature' do
|
||||
it 'is not valid for the string true' do
|
||||
login_scanner.smb_verify_signature = 'true'
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:smb_verify_signature]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is not valid for the string false' do
|
||||
login_scanner.smb_verify_signature = 'false'
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:smb_verify_signature]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
login_scanner.smb_verify_signature = true
|
||||
expect(login_scanner.errors[:smb_verify_signature]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
login_scanner.smb_verify_signature = false
|
||||
expect(login_scanner.errors[:smb_verify_signature]).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#attempt_login' do
|
||||
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
|
||||
expect(login_scanner.attempt_login(pub_blank).status).to eq :connection_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the credentials are correct, but we cannot login' do
|
||||
[
|
||||
0xC000006E, # => "STATUS_ACCOUNT_RESTRICTION",
|
||||
0xC000006F, # => "STATUS_INVALID_LOGON_HOURS",
|
||||
0xC0000070, # => "STATUS_INVALID_WORKSTATION",
|
||||
0xC0000071, # => "STATUS_PASSWORD_EXPIRED",
|
||||
0xC0000072, # => "STATUS_ACCOUNT_DISABLED",
|
||||
0xC000015B, # => "STATUS_LOGON_TYPE_NOT_GRANTED",
|
||||
0xC0000193, # => "STATUS_ACCOUNT_EXPIRED",
|
||||
0xC0000224, # => "STATUS_PASSWORD_MUST_CHANGE",
|
||||
].each do |code|
|
||||
it "returns a status of :correct" do
|
||||
exception = Rex::Proto::SMB::Exceptions::ErrorCode.new
|
||||
exception.error_code = code
|
||||
|
||||
login_scanner.stub_chain(:simple, :login).and_raise exception
|
||||
|
||||
expect(login_scanner.attempt_login(pub_blank).status).to eq :correct
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
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)
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
||||
|
||||
shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
||||
|
||||
subject(:login_scanner) { described_class.new }
|
||||
|
||||
it { should respond_to :send_lm }
|
||||
it { should respond_to :send_ntlm }
|
||||
it { should respond_to :send_spn }
|
||||
it { should respond_to :use_lmkey }
|
||||
it { should respond_to :use_ntlm2_session }
|
||||
it { should respond_to :use_ntlmv2 }
|
||||
|
||||
|
@ -23,12 +24,12 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
expect(login_scanner.errors[:send_lm]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
it 'is valid for true class' do
|
||||
login_scanner.send_lm = true
|
||||
expect(login_scanner.errors[:send_lm]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
it 'is valid for false class' do
|
||||
login_scanner.send_lm = false
|
||||
expect(login_scanner.errors[:send_lm]).to be_empty
|
||||
end
|
||||
|
@ -47,12 +48,12 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
expect(login_scanner.errors[:send_ntlm]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
it 'is valid for true class' do
|
||||
login_scanner.send_ntlm = true
|
||||
expect(login_scanner.errors[:send_ntlm]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
it 'is valid for false class' do
|
||||
login_scanner.send_ntlm = false
|
||||
expect(login_scanner.errors[:send_ntlm]).to be_empty
|
||||
end
|
||||
|
@ -71,17 +72,41 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
expect(login_scanner.errors[:send_spn]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
it 'is valid for true class' do
|
||||
login_scanner.send_spn = true
|
||||
expect(login_scanner.errors[:send_spn]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
it 'is valid for false class' do
|
||||
login_scanner.send_spn = false
|
||||
expect(login_scanner.errors[:send_spn]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context '#use_lmkey' do
|
||||
it 'is not valid for the string true' do
|
||||
login_scanner.use_lmkey = 'true'
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:use_lmkey]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is not valid for the string false' do
|
||||
login_scanner.use_lmkey = 'false'
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:use_lmkey]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
login_scanner.use_lmkey = true
|
||||
expect(login_scanner.errors[:use_lmkey]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
login_scanner.use_lmkey = false
|
||||
expect(login_scanner.errors[:use_lmkey]).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context '#use_ntlm2_session' do
|
||||
it 'is not valid for the string true' do
|
||||
login_scanner.use_ntlm2_session = 'true'
|
||||
|
@ -95,12 +120,12 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
expect(login_scanner.errors[:use_ntlm2_session]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
it 'is valid for true class' do
|
||||
login_scanner.use_ntlm2_session = true
|
||||
expect(login_scanner.errors[:use_ntlm2_session]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
it 'is valid for false class' do
|
||||
login_scanner.use_ntlm2_session = false
|
||||
expect(login_scanner.errors[:use_ntlm2_session]).to be_empty
|
||||
end
|
||||
|
@ -119,12 +144,12 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
expect(login_scanner.errors[:use_ntlmv2]).to include 'is not included in the list'
|
||||
end
|
||||
|
||||
it 'is valid for true class' do
|
||||
it 'is valid for true class' do
|
||||
login_scanner.use_ntlmv2 = true
|
||||
expect(login_scanner.errors[:use_ntlmv2]).to be_empty
|
||||
end
|
||||
|
||||
it 'is valid for false class' do
|
||||
it 'is valid for false class' do
|
||||
login_scanner.use_ntlmv2 = false
|
||||
expect(login_scanner.errors[:use_ntlmv2]).to be_empty
|
||||
end
|
||||
|
@ -132,4 +157,4 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::NTLM' do
|
|||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue