ruby_smb 3.3.9 → 3.3.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/metasploit-framework_acceptance.yml +47 -0
- data/lib/ruby_smb/client.rb +5 -5
- data/lib/ruby_smb/dcerpc/client.rb +2 -4
- data/lib/ruby_smb/dcerpc/icpr.rb +2 -2
- data/lib/ruby_smb/dcerpc/request.rb +4 -1
- data/lib/ruby_smb/dcerpc/samr/samr_get_members_in_group_request.rb +23 -0
- data/lib/ruby_smb/dcerpc/samr/samr_get_members_in_group_response.rb +34 -0
- data/lib/ruby_smb/dcerpc/samr/samr_open_group_request.rb +26 -0
- data/lib/ruby_smb/dcerpc/samr/samr_open_group_response.rb +24 -0
- data/lib/ruby_smb/dcerpc/samr.rb +71 -1
- data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request.rb +0 -3
- data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/wkssvc.rb +118 -3
- data/lib/ruby_smb/gss/provider/ntlm.rb +5 -9
- data/lib/ruby_smb/ntlm/client.rb +3 -74
- data/lib/ruby_smb/ntlm.rb +0 -37
- data/lib/ruby_smb/server/server_client/tree_connect.rb +2 -2
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +0 -2
- data/ruby_smb.gemspec +9 -2
- data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_request_spec.rb +0 -8
- data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_get_info_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_identity_handle.rb +7 -0
- data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_request_spec.rb +71 -0
- data/spec/lib/ruby_smb/dcerpc/wkssvc/netr_wksta_user_enum_response_spec.rb +65 -0
- data/spec/lib/ruby_smb/dcerpc/wkssvc_spec.rb +58 -1
- data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +119 -4
- data/spec/lib/ruby_smb/ntlm/client/session_spec.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +19 -7
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/ntlm/custom/string_encoder.rb +0 -22
- data/lib/ruby_smb/utils.rb +0 -15
data/lib/ruby_smb/ntlm/client.rb
CHANGED
@@ -1,78 +1,7 @@
|
|
1
1
|
module RubySMB::NTLM
|
2
|
-
module Message
|
3
|
-
def deflag
|
4
|
-
security_buffers.inject(head_size) do |cur, a|
|
5
|
-
a[1].offset = cur
|
6
|
-
cur += a[1].data_size
|
7
|
-
has_flag?(:UNICODE) ? cur + cur % 2 : cur
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def serialize
|
12
|
-
deflag
|
13
|
-
@alist.map { |n, f| f.serialize }.join + security_buffers.map { |n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '') }.join
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
2
|
class Client < Net::NTLM::Client
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
type3_opts = {
|
22
|
-
:lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
|
23
|
-
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
|
24
|
-
:domain => domain,
|
25
|
-
:user => username,
|
26
|
-
:workstation => workstation,
|
27
|
-
:flag => (challenge_message.flag & client.flags)
|
28
|
-
}
|
29
|
-
t3 = Net::NTLM::Message::Type3.create type3_opts
|
30
|
-
t3.extend(Message)
|
31
|
-
if negotiate_key_exchange?
|
32
|
-
t3.enable(:session_key)
|
33
|
-
rc4 = OpenSSL::Cipher.new("rc4")
|
34
|
-
rc4.encrypt
|
35
|
-
rc4.key = user_session_key
|
36
|
-
sk = rc4.update exported_session_key
|
37
|
-
sk << rc4.final
|
38
|
-
t3.session_key = sk
|
39
|
-
end
|
40
|
-
t3
|
41
|
-
end
|
42
|
-
|
43
|
-
def is_anonymous?
|
44
|
-
username == '' && password == ''
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def use_oem_strings?
|
50
|
-
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
51
|
-
!challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
|
52
|
-
end
|
53
|
-
|
54
|
-
def ntlmv2_hash
|
55
|
-
@ntlmv2_hash ||= RubySMB::NTLM.ntlmv2_hash(username, password, domain, {:client_challenge => client_challenge, :unicode => !use_oem_strings?})
|
56
|
-
end
|
57
|
-
|
58
|
-
def calculate_user_session_key!
|
59
|
-
if is_anonymous?
|
60
|
-
# see MS-NLMP section 3.4
|
61
|
-
@user_session_key = "\x00".b * 16
|
62
|
-
else
|
63
|
-
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def init_context(resp = nil, channel_binding = nil)
|
69
|
-
if resp.nil?
|
70
|
-
@session = nil
|
71
|
-
type1_message
|
72
|
-
else
|
73
|
-
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
|
74
|
-
@session.authenticate!
|
75
|
-
end
|
76
|
-
end
|
3
|
+
# There was a bunch of code in here that was necessary in versions up to and including rubyntlm version 0.6.3.
|
4
|
+
# The class is kept because there are references to it that should be kept in place in case future alterations to
|
5
|
+
# rubyntlm are required.
|
77
6
|
end
|
78
7
|
end
|
data/lib/ruby_smb/ntlm.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'ruby_smb/ntlm/custom/string_encoder'
|
2
|
-
|
3
1
|
module RubySMB
|
4
2
|
module NTLM
|
5
3
|
# [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
|
@@ -58,41 +56,6 @@ module RubySMB
|
|
58
56
|
"Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
|
59
57
|
end
|
60
58
|
end
|
61
|
-
|
62
|
-
class << self
|
63
|
-
|
64
|
-
# Generate a NTLMv2 Hash
|
65
|
-
# @param [String] user The username
|
66
|
-
# @param [String] password The password
|
67
|
-
# @param [String] target The domain or workstation to authenticate to
|
68
|
-
# @option opt :unicode (false) Unicode encode the domain
|
69
|
-
def ntlmv2_hash(user, password, target, opt={})
|
70
|
-
if Net::NTLM.is_ntlm_hash? password
|
71
|
-
decoded_password = Net::NTLM::EncodeUtil.decode_utf16le(password)
|
72
|
-
ntlmhash = [decoded_password.upcase[33,65]].pack('H32')
|
73
|
-
else
|
74
|
-
ntlmhash = Net::NTLM.ntlm_hash(password, opt)
|
75
|
-
end
|
76
|
-
|
77
|
-
if opt[:unicode]
|
78
|
-
# Uppercase operation on username containing non-ASCII characters
|
79
|
-
# after being unicode encoded with `EncodeUtil.encode_utf16le`
|
80
|
-
# doesn't play well. Upcase should be done before encoding.
|
81
|
-
user_upcase = Net::NTLM::EncodeUtil.decode_utf16le(user).upcase
|
82
|
-
user_upcase = Net::NTLM::EncodeUtil.encode_utf16le(user_upcase)
|
83
|
-
else
|
84
|
-
user_upcase = user.upcase
|
85
|
-
end
|
86
|
-
userdomain = user_upcase + target
|
87
|
-
|
88
|
-
unless opt[:unicode]
|
89
|
-
userdomain = Net::NTLM::EncodeUtil.encode_utf16le(userdomain)
|
90
|
-
end
|
91
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
59
|
end
|
97
60
|
end
|
98
61
|
|
@@ -7,7 +7,7 @@ module RubySMB
|
|
7
7
|
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/b062f3e3-1b65-4a9a-854a-0ee432499d8f
|
8
8
|
response = RubySMB::SMB1::Packet::TreeConnectResponse.new
|
9
9
|
|
10
|
-
share_name =
|
10
|
+
share_name = request.data_block.path.split('\\', 4).last
|
11
11
|
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
|
12
12
|
if share_provider.nil?
|
13
13
|
logger.warn("Received TREE_CONNECT request for non-existent share: #{share_name}")
|
@@ -51,7 +51,7 @@ module RubySMB
|
|
51
51
|
return response
|
52
52
|
end
|
53
53
|
|
54
|
-
share_name =
|
54
|
+
share_name = request.path.encode.split('\\', 4).last
|
55
55
|
share_provider = @server.shares.transform_keys(&:downcase)[share_name.downcase]
|
56
56
|
|
57
57
|
if share_provider.nil?
|
data/lib/ruby_smb/version.rb
CHANGED
data/lib/ruby_smb.rb
CHANGED
@@ -6,13 +6,11 @@ require 'openssl/ccm'
|
|
6
6
|
require 'openssl/cmac'
|
7
7
|
require 'windows_error'
|
8
8
|
require 'windows_error/nt_status'
|
9
|
-
require 'ruby_smb/ntlm/custom/string_encoder'
|
10
9
|
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
|
11
10
|
#
|
12
11
|
# [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
13
12
|
# [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
14
13
|
module RubySMB
|
15
|
-
require 'ruby_smb/utils'
|
16
14
|
require 'ruby_smb/error'
|
17
15
|
require 'ruby_smb/create_actions'
|
18
16
|
require 'ruby_smb/dispositions'
|
data/ruby_smb.gemspec
CHANGED
@@ -6,7 +6,14 @@ require 'ruby_smb/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'ruby_smb'
|
8
8
|
spec.version = RubySMB::VERSION
|
9
|
-
spec.authors = [
|
9
|
+
spec.authors = [
|
10
|
+
'Metasploit Hackers',
|
11
|
+
'David Maloney',
|
12
|
+
'James Lee',
|
13
|
+
'Dev Mohanty',
|
14
|
+
'Christophe De La Fuente',
|
15
|
+
'Spencer McIntyre'
|
16
|
+
]
|
10
17
|
spec.email = ['msfdev@metasploit.com']
|
11
18
|
spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
|
12
19
|
spec.description = ''
|
@@ -33,7 +40,7 @@ Gem::Specification.new do |spec|
|
|
33
40
|
spec.add_development_dependency 'rake'
|
34
41
|
spec.add_development_dependency 'yard'
|
35
42
|
|
36
|
-
spec.add_runtime_dependency 'rubyntlm'
|
43
|
+
spec.add_runtime_dependency 'rubyntlm', '>= 0.6.5'
|
37
44
|
spec.add_runtime_dependency 'windows_error', '>= 0.1.4'
|
38
45
|
spec.add_runtime_dependency 'bindata', '2.4.15'
|
39
46
|
spec.add_runtime_dependency 'openssl-ccm'
|
@@ -1,11 +1,3 @@
|
|
1
|
-
RSpec.describe RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle do
|
2
|
-
subject(:packet) { described_class.new }
|
3
|
-
|
4
|
-
it 'is a Ndr::NdrWideStringPtr' do
|
5
|
-
expect(packet).to be_a(RubySMB::Dcerpc::Ndr::NdrWideStringPtr)
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
1
|
RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaGetInfoRequest do
|
10
2
|
subject(:packet) { described_class.new }
|
11
3
|
|
@@ -305,7 +305,7 @@ RSpec.describe RubySMB::Dcerpc::Wkssvc::LpwkstaInfo do
|
|
305
305
|
it 'is little endian' do
|
306
306
|
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
307
307
|
end
|
308
|
-
it '
|
308
|
+
it 'is a Ndr::NdrStruct' do
|
309
309
|
expect(described_class).to be < RubySMB::Dcerpc::Ndr::NdrStruct
|
310
310
|
end
|
311
311
|
describe '#level' do
|
@@ -0,0 +1,71 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaUserEnumRequest do
|
2
|
+
subject(:packet) { described_class.new }
|
3
|
+
|
4
|
+
def random_str(nb = 8)
|
5
|
+
nb.times.map { rand('a'.ord..'z'.ord).chr }.join
|
6
|
+
end
|
7
|
+
|
8
|
+
it { is_expected.to respond_to :server_name }
|
9
|
+
it { is_expected.to respond_to :user_info }
|
10
|
+
it { is_expected.to respond_to :preferred_max_length }
|
11
|
+
it { is_expected.to respond_to :result_handle }
|
12
|
+
it { is_expected.to respond_to :opnum }
|
13
|
+
|
14
|
+
it 'is little endian' do
|
15
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
16
|
+
end
|
17
|
+
it 'is a BinData::Record' do
|
18
|
+
expect(packet).to be_a(BinData::Record)
|
19
|
+
end
|
20
|
+
describe '#server_name' do
|
21
|
+
it 'is a WkssvcIdentifyHandle structure' do
|
22
|
+
expect(packet.server_name).to be_a RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle
|
23
|
+
end
|
24
|
+
end
|
25
|
+
describe '#user_info' do
|
26
|
+
it 'is a WkstaUserEnumStructure structure' do
|
27
|
+
expect(packet.user_info).to be_a RubySMB::Dcerpc::Wkssvc::WkstaUserEnumStructure
|
28
|
+
end
|
29
|
+
end
|
30
|
+
describe '#preferred_max_length' do
|
31
|
+
it 'is a NdrUint32 structure' do
|
32
|
+
expect(packet.preferred_max_length).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'has a default value of 0xFFFFFFFF' do
|
36
|
+
expect(packet.preferred_max_length).to eq(0xFFFFFFFF)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe '#result_handle' do
|
40
|
+
it 'is a NdrUint32Ptr structure' do
|
41
|
+
expect(packet.result_handle).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'has a default value of 0' do
|
45
|
+
expect(packet.result_handle).to eq(0)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
describe '#initialize_instance' do
|
49
|
+
it 'sets #opnum to NETR_WKSTA_USER_ENUM constant' do
|
50
|
+
expect(packet.opnum).to eq(RubySMB::Dcerpc::Wkssvc::NETR_WKSTA_USER_ENUM)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
it 'reads itself' do
|
54
|
+
packet = described_class.new(
|
55
|
+
server_name: 'TestServer',
|
56
|
+
user_info: {
|
57
|
+
level: RubySMB::Dcerpc::Wkssvc::WKSTA_USER_INFO_0,
|
58
|
+
info: {
|
59
|
+
wkui0_entries_read: 1,
|
60
|
+
wkui0_buffer: [{
|
61
|
+
wkui0_username: random_str
|
62
|
+
}],
|
63
|
+
},
|
64
|
+
},
|
65
|
+
preferred_max_length: 0xFFFFFFFF,
|
66
|
+
result_handle: 0
|
67
|
+
)
|
68
|
+
binary = packet.to_binary_s
|
69
|
+
expect(described_class.read(binary)).to eq(packet)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Wkssvc::NetrWkstaUserEnumResponse do
|
2
|
+
subject(:packet) { described_class.new }
|
3
|
+
|
4
|
+
def random_str(nb = 8)
|
5
|
+
nb.times.map { rand('a'.ord..'z'.ord).chr }.join
|
6
|
+
end
|
7
|
+
|
8
|
+
it { is_expected.to respond_to :user_info }
|
9
|
+
it { is_expected.to respond_to :total_entries }
|
10
|
+
it { is_expected.to respond_to :result_handle }
|
11
|
+
it { is_expected.to respond_to :error_status }
|
12
|
+
|
13
|
+
it 'is little endian' do
|
14
|
+
expect(described_class.fields.instance_variable_get(:@hints)[:endian]).to eq :little
|
15
|
+
end
|
16
|
+
it 'is a BinData::Record' do
|
17
|
+
expect(packet).to be_a(BinData::Record)
|
18
|
+
end
|
19
|
+
describe '#user_info' do
|
20
|
+
it 'is a WkstaUserEnumStructure structure' do
|
21
|
+
expect(packet.user_info).to be_a RubySMB::Dcerpc::Wkssvc::WkstaUserEnumStructure
|
22
|
+
end
|
23
|
+
end
|
24
|
+
describe '#total_entries' do
|
25
|
+
it 'is a NdrUint32Ptr structure' do
|
26
|
+
expect(packet.total_entries).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
|
27
|
+
end
|
28
|
+
end
|
29
|
+
describe '#result_handle' do
|
30
|
+
it 'is a NdrUint32Ptr structure' do
|
31
|
+
expect(packet.result_handle).to be_a RubySMB::Dcerpc::Ndr::NdrUint32Ptr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
describe '#error_status' do
|
35
|
+
it 'is a NdrUint32 structure' do
|
36
|
+
expect(packet.error_status).to be_a RubySMB::Dcerpc::Ndr::NdrUint32
|
37
|
+
end
|
38
|
+
end
|
39
|
+
describe '#initialize_instance' do
|
40
|
+
it 'sets #opnum to NETR_WKSTA_USER_ENUM constant' do
|
41
|
+
expect(packet.opnum).to eq(RubySMB::Dcerpc::Wkssvc::NETR_WKSTA_USER_ENUM)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
it 'reads itself' do
|
45
|
+
packet = described_class.new(
|
46
|
+
user_info: {
|
47
|
+
level: RubySMB::Dcerpc::Wkssvc::WKSTA_USER_INFO_1,
|
48
|
+
info: {
|
49
|
+
wkui1_entries_read: 1,
|
50
|
+
wkui1_buffer: [{
|
51
|
+
wkui1_username: random_str,
|
52
|
+
wkui1_logon_domain: random_str,
|
53
|
+
wkui1_oth_domains: random_str,
|
54
|
+
wkui1_logon_server: random_str
|
55
|
+
}],
|
56
|
+
},
|
57
|
+
},
|
58
|
+
total_entries: 1,
|
59
|
+
result_handle: 0,
|
60
|
+
error_status: 0
|
61
|
+
)
|
62
|
+
binary = packet.to_binary_s
|
63
|
+
expect(described_class.read(binary)).to eq(packet)
|
64
|
+
end
|
65
|
+
end
|
@@ -1,3 +1,11 @@
|
|
1
|
+
RSpec.describe RubySMB::Dcerpc::Wkssvc::WkssvcIdentifyHandle do
|
2
|
+
subject(:packet) { described_class.new }
|
3
|
+
|
4
|
+
it 'is a Ndr::NdrWideStringzPtr' do
|
5
|
+
expect(packet).to be_a(RubySMB::Dcerpc::Ndr::NdrWideStringzPtr)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
1
9
|
RSpec.describe RubySMB::Dcerpc::Wkssvc do
|
2
10
|
let(:wkssvc) do
|
3
11
|
RubySMB::SMB1::Pipe.new(
|
@@ -23,7 +31,7 @@ RSpec.describe RubySMB::Dcerpc::Wkssvc do
|
|
23
31
|
it 'sets the request with the expected values' do
|
24
32
|
wkssvc.netr_wksta_get_info
|
25
33
|
expect(described_class::NetrWkstaGetInfoRequest).to have_received(:new).with(
|
26
|
-
server_name:
|
34
|
+
server_name: '',
|
27
35
|
level: described_class::WKSTA_INFO_100
|
28
36
|
)
|
29
37
|
end
|
@@ -67,4 +75,53 @@ RSpec.describe RubySMB::Dcerpc::Wkssvc do
|
|
67
75
|
end
|
68
76
|
end
|
69
77
|
end
|
78
|
+
|
79
|
+
describe '#netr_wksta_user_enum' do
|
80
|
+
let(:wkst_netr_wksta_user_enum_request) { double('NetrWkstaUserEnumRequest') }
|
81
|
+
let(:response) { double('Response') }
|
82
|
+
let(:wkst_netr_wksta_user_enum_response) { double('NetrWkstaUserEnumResponse') }
|
83
|
+
let(:info) { double('info') }
|
84
|
+
before :example do
|
85
|
+
allow(described_class::NetrWkstaUserEnumRequest).to receive(:new).and_return(wkst_netr_wksta_user_enum_request)
|
86
|
+
allow(wkssvc).to receive(:dcerpc_request).and_return(response)
|
87
|
+
allow(described_class::NetrWkstaUserEnumResponse).to receive(:read).and_return(wkst_netr_wksta_user_enum_response)
|
88
|
+
allow(wkst_netr_wksta_user_enum_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
|
89
|
+
allow(wkst_netr_wksta_user_enum_response).to receive_message_chain(:user_info, :info => info)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'sets the request with the expected values' do
|
93
|
+
wkssvc.netr_wksta_user_enum
|
94
|
+
expect(described_class::NetrWkstaUserEnumRequest).to have_received(:new).with(
|
95
|
+
server_name: '',
|
96
|
+
user_info: {
|
97
|
+
level: described_class::WKSTA_USER_INFO_0,
|
98
|
+
tag: described_class::WKSTA_USER_INFO_0,
|
99
|
+
info: {
|
100
|
+
wkui0_entries_read: 0,
|
101
|
+
},
|
102
|
+
},
|
103
|
+
preferred_max_length: 0xFFFFFFFF,
|
104
|
+
result_handle: 0
|
105
|
+
)
|
106
|
+
end
|
107
|
+
it 'send the expected request structure' do
|
108
|
+
wkssvc.netr_wksta_user_enum
|
109
|
+
expect(wkssvc).to have_received(:dcerpc_request).with(wkst_netr_wksta_user_enum_request)
|
110
|
+
end
|
111
|
+
context 'when an IOError occurs while parsing the response' do
|
112
|
+
it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
|
113
|
+
allow(described_class::NetrWkstaUserEnumResponse).to receive(:read).and_raise(IOError)
|
114
|
+
expect { wkssvc.netr_wksta_user_enum }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
|
118
|
+
it 'raises a RubySMB::Dcerpc::Error::WinregError' do
|
119
|
+
allow(wkst_netr_wksta_user_enum_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
|
120
|
+
expect { wkssvc.netr_wksta_user_enum }.to raise_error(RubySMB::Dcerpc::Error::WkssvcError)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
it 'returns the expected handler' do
|
124
|
+
expect(wkssvc.netr_wksta_user_enum).to eq(info)
|
125
|
+
end
|
126
|
+
end
|
70
127
|
end
|
@@ -9,8 +9,11 @@ RSpec.describe RubySMB::Gss::Provider::NTLM::Authenticator do
|
|
9
9
|
msg.domain = domain
|
10
10
|
end
|
11
11
|
end
|
12
|
+
let(:type2_msg) do
|
13
|
+
Net::NTLM::Message::Type2.new
|
14
|
+
end
|
12
15
|
let(:type3_msg) do
|
13
|
-
|
16
|
+
type2_msg.response({user: username, password: password, domain: domain}, {ntlmv2: true})
|
14
17
|
end
|
15
18
|
|
16
19
|
before(:each) do
|
@@ -65,9 +68,121 @@ RSpec.describe RubySMB::Gss::Provider::NTLM::Authenticator do
|
|
65
68
|
end
|
66
69
|
|
67
70
|
describe '#process_ntlm_type3' do
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
+
context 'when the message is anonymous' do
|
72
|
+
let(:type3_msg) do
|
73
|
+
type2_msg.response({user: '', password: ''}, {ntlmv2: true})
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when anonymous access is disabled' do
|
77
|
+
before(:each) do
|
78
|
+
expect(provider).to_not receive(:allow_guests)
|
79
|
+
expect(provider).to receive(:allow_anonymous).and_return(false)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should process a NTLM type 3 message and return STATUS_LOGON_FAILURE' do
|
83
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
84
|
+
expect(status).to be_a WindowsError::ErrorCode
|
85
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
86
|
+
end
|
87
|
+
|
88
|
+
after(:each) do
|
89
|
+
expect(authenticator.session_key).to be_nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when anonymous access is enabled' do
|
94
|
+
before(:each) do
|
95
|
+
expect(provider).to_not receive(:allow_guests)
|
96
|
+
expect(provider).to receive(:allow_anonymous).and_return(true)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should process a NTLM type 3 message and return STATUS_SUCCESS' do
|
100
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
101
|
+
expect(status).to be_a WindowsError::ErrorCode
|
102
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_SUCCESS
|
103
|
+
end
|
104
|
+
|
105
|
+
after(:each) do
|
106
|
+
expect(authenticator.session_key).to eq "\x00".b * 16
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when the message is a guest' do
|
112
|
+
let(:type3_msg) do
|
113
|
+
type2_msg.response({user: 'Spencer', password: password}, {ntlmv2: true})
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'when guest access is disabled' do
|
117
|
+
before(:each) do
|
118
|
+
expect(provider).to_not receive(:allow_anonymous)
|
119
|
+
expect(provider).to receive(:allow_guests).and_return(false)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should process a NTLM type 3 message and return STATUS_LOGON_FAILURE' do
|
123
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
124
|
+
expect(status).to be_a WindowsError::ErrorCode
|
125
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
126
|
+
end
|
127
|
+
|
128
|
+
after(:each) do
|
129
|
+
expect(authenticator.session_key).to be_nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'when guest access is enabled' do
|
134
|
+
before(:each) do
|
135
|
+
expect(provider).to_not receive(:allow_anonymous)
|
136
|
+
expect(provider).to receive(:allow_guests).and_return(true)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should process a NTLM type 3 message and return STATUS_SUCCESS' do
|
140
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
141
|
+
expect(status).to be_a WindowsError::ErrorCode
|
142
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_SUCCESS
|
143
|
+
end
|
144
|
+
|
145
|
+
after(:each) do
|
146
|
+
expect(authenticator.session_key).to eq "\x00".b * 16
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'when the message is a known user' do
|
152
|
+
before(:each) do
|
153
|
+
authenticator.instance_variable_set(:@server_challenge, type2_msg[:challenge].serialize)
|
154
|
+
end
|
155
|
+
|
156
|
+
context 'when the password is correct' do
|
157
|
+
it 'should process a NTLM type 3 message and return STATUS_SUCCESS' do
|
158
|
+
type3_msg.user.force_encoding('UTF-16LE')
|
159
|
+
type3_msg.domain.force_encoding('UTF-16LE')
|
160
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
161
|
+
expect(status).to be_a WindowsError::ErrorCode
|
162
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_SUCCESS
|
163
|
+
end
|
164
|
+
|
165
|
+
after(:each) do
|
166
|
+
expect(authenticator.session_key).to be_a String
|
167
|
+
expect(authenticator.session_key.length).to eq 16
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when the password is wrong' do
|
172
|
+
let(:type3_msg) do
|
173
|
+
type2_msg.response({user: username, password: 'Wrong' + password, domain: domain}, {ntlmv2: true})
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should process a NTLM type 3 message and return STATUS_LOGON_FAILURE' do
|
177
|
+
status = authenticator.process_ntlm_type3(type3_msg)
|
178
|
+
expect(status).to be_a WindowsError::ErrorCode
|
179
|
+
expect(status).to eq WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
180
|
+
end
|
181
|
+
|
182
|
+
after(:each) do
|
183
|
+
expect(authenticator.session_key).to be nil
|
184
|
+
end
|
185
|
+
end
|
71
186
|
end
|
72
187
|
end
|
73
188
|
|
@@ -24,7 +24,7 @@ RSpec.describe RubySMB::NTLM::Client::Session do
|
|
24
24
|
|
25
25
|
it 'returns a Type3 message' do
|
26
26
|
expect(session.authenticate!).to be_a Net::NTLM::Message::Type3
|
27
|
-
expect(session.authenticate!).to be_a
|
27
|
+
expect(session.authenticate!).to be_a Net::NTLM::Message
|
28
28
|
end
|
29
29
|
|
30
30
|
context 'when it is anonymous' do
|
data.tar.gz.sig
CHANGED
Binary file
|