ruby_smb 3.3.9 → 3.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|