Add specs, validations for LoginScanner::SMB

bug/bundler_fix
James Lee 2014-05-09 18:58:49 -05:00
parent ee6a9f99b3
commit 3831042dca
No known key found for this signature in database
GPG Key ID: 2D6094C7CEA0A321
7 changed files with 350 additions and 37 deletions

View File

@ -273,4 +273,4 @@ module Metasploit
end
end
end
end
end

View File

@ -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

View File

@ -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] }

View File

@ -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

View File

@ -185,4 +185,4 @@ module Metasploit
end
end
end
end
end

View File

@ -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

View File

@ -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