ruby_smb 1.1.0 → 2.0.4
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.travis.yml +3 -5
- data/Gemfile +6 -2
- data/examples/anonymous_auth.rb +3 -3
- data/examples/append_file.rb +10 -8
- data/examples/authenticate.rb +9 -5
- data/examples/delete_file.rb +8 -6
- data/examples/enum_registry_key.rb +5 -4
- data/examples/enum_registry_values.rb +5 -4
- data/examples/list_directory.rb +8 -6
- data/examples/negotiate.rb +51 -8
- data/examples/negotiate_with_netbios_service.rb +9 -5
- data/examples/net_share_enum_all.rb +6 -4
- data/examples/pipes.rb +11 -12
- data/examples/query_service_status.rb +64 -0
- data/examples/read_file.rb +8 -6
- data/examples/read_file_encryption.rb +56 -0
- data/examples/read_registry_key_value.rb +6 -5
- data/examples/rename_file.rb +9 -7
- data/examples/tree_connect.rb +7 -5
- data/examples/write_file.rb +9 -7
- data/lib/ruby_smb.rb +4 -0
- data/lib/ruby_smb/client.rb +246 -26
- data/lib/ruby_smb/client/authentication.rb +32 -18
- data/lib/ruby_smb/client/echo.rb +2 -4
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +156 -16
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +6 -8
- data/lib/ruby_smb/client/utils.rb +24 -17
- data/lib/ruby_smb/client/winreg.rb +1 -1
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +2 -0
- data/lib/ruby_smb/dcerpc/error.rb +3 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +209 -44
- data/lib/ruby_smb/dcerpc/request.rb +13 -0
- data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +9 -6
- data/lib/ruby_smb/dcerpc/svcctl.rb +479 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request.rb +48 -0
- data/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_request.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/control_service_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request.rb +35 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_request.rb +31 -0
- data/lib/ruby_smb/dcerpc/svcctl/open_service_w_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response.rb +44 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_request.rb +23 -0
- data/lib/ruby_smb/dcerpc/svcctl/query_service_status_response.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/service_status.rb +25 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/svcctl/start_service_w_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +98 -17
- data/lib/ruby_smb/dcerpc/winreg/create_key_request.rb +73 -0
- data/lib/ruby_smb/dcerpc/winreg/create_key_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +4 -4
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +1 -1
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +7 -6
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +10 -10
- data/lib/ruby_smb/dcerpc/winreg/save_key_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/winreg/save_key_response.rb +23 -0
- data/lib/ruby_smb/dispatcher/base.rb +1 -1
- data/lib/ruby_smb/dispatcher/socket.rb +5 -4
- data/lib/ruby_smb/error.rb +49 -6
- data/lib/ruby_smb/field/stringz16.rb +17 -1
- data/lib/ruby_smb/generic_packet.rb +11 -1
- data/lib/ruby_smb/nbss/session_header.rb +4 -4
- data/lib/ruby_smb/smb1/commands.rb +1 -1
- data/lib/ruby_smb/smb1/file.rb +13 -28
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb1/packet/write_andx_request.rb +1 -1
- data/lib/ruby_smb/smb1/pipe.rb +8 -8
- data/lib/ruby_smb/smb1/tree.rb +25 -12
- data/lib/ruby_smb/smb2/bit_field/session_flags.rb +2 -1
- data/lib/ruby_smb/smb2/bit_field/share_flags.rb +6 -4
- data/lib/ruby_smb/smb2/file.rb +59 -77
- data/lib/ruby_smb/smb2/negotiate_context.rb +108 -0
- data/lib/ruby_smb/smb2/packet.rb +2 -0
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +41 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +51 -14
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +50 -4
- data/lib/ruby_smb/smb2/packet/transform_header.rb +84 -0
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +92 -6
- data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -26
- data/lib/ruby_smb/smb2/pipe.rb +8 -20
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +44 -28
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +3 -1
- data/spec/lib/ruby_smb/client_spec.rb +1408 -70
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1396 -77
- data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +49 -12
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_request_spec.rb +191 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/change_service_config_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/close_service_handle_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_request_spec.rb +39 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/control_service_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_request_spec.rb +78 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_sc_manager_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_request_spec.rb +59 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/open_service_w_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_request_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_config_w_response_spec.rb +152 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_request_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/query_service_status_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/service_status_spec.rb +72 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_request_spec.rb +46 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl/start_service_w_response_spec.rb +30 -0
- data/spec/lib/ruby_smb/dcerpc/svcctl_spec.rb +512 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_request_spec.rb +110 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/create_key_response_spec.rb +44 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +9 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +0 -4
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +17 -17
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +11 -23
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_request_spec.rb +57 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/save_key_response_spec.rb +22 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +227 -41
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
- data/spec/lib/ruby_smb/error_spec.rb +88 -0
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
- data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
- data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
- data/spec/lib/ruby_smb/smb1/file_spec.rb +1 -3
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_legacy_response_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_request_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb1/packet/session_setup_response_spec.rb +1 -1
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +30 -5
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
- data/spec/lib/ruby_smb/smb2/bit_field/session_flags_spec.rb +9 -0
- data/spec/lib/ruby_smb/smb2/bit_field/share_flags_spec.rb +27 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +147 -71
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +332 -0
- data/spec/lib/ruby_smb/smb2/packet/compression_transform_header_spec.rb +108 -0
- data/spec/lib/ruby_smb/smb2/packet/negotiate_request_spec.rb +138 -3
- data/spec/lib/ruby_smb/smb2/packet/negotiate_response_spec.rb +120 -2
- data/spec/lib/ruby_smb/smb2/packet/transform_header_spec.rb +220 -0
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_request_spec.rb +339 -9
- data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +3 -30
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +9 -45
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +111 -9
- metadata +194 -75
- metadata.gz.sig +2 -1
@@ -6,6 +6,14 @@ module RubySMB
|
|
6
6
|
class TreeConnectResponse < RubySMB::GenericPacket
|
7
7
|
COMMAND = RubySMB::SMB2::Commands::TREE_CONNECT
|
8
8
|
|
9
|
+
# Share Types
|
10
|
+
# Physical disk share
|
11
|
+
SMB2_SHARE_TYPE_DISK = 0x01
|
12
|
+
# Named pipe share
|
13
|
+
SMB2_SHARE_TYPE_PIPE = 0x02
|
14
|
+
# Printer share
|
15
|
+
SMB2_SHARE_TYPE_PRINT = 0x03
|
16
|
+
|
9
17
|
endian :little
|
10
18
|
smb2_header :smb2_header
|
11
19
|
uint16 :structure_size, label: 'Structure Size', initial_value: 16
|
@@ -20,32 +28,6 @@ module RubySMB
|
|
20
28
|
smb2_header.flags.reply = 1
|
21
29
|
end
|
22
30
|
|
23
|
-
# Returns the ACCESS_MASK for the Maximal Share Access Rights. The packet
|
24
|
-
# defaults this to a {RubySMB::SMB2::BitField::DirectoryAccessMask}. If it is anything other than
|
25
|
-
# a directory that has been connected to, it will re-cast it as a {RubySMB::SMB2::BitField::FileAccessMask}
|
26
|
-
#
|
27
|
-
# @return [RubySMB::SMB2::BitField::DirectoryAccessMask] if a directory was connected to
|
28
|
-
# @return [RubySMB::SMB2::BitField::FileAccessMask] if anything else was connected to
|
29
|
-
# @raise [RubySMB::Error::InvalidBitField] if ACCESS_MASK bit field is not valid
|
30
|
-
def access_rights
|
31
|
-
if is_directory?
|
32
|
-
maximal_access
|
33
|
-
else
|
34
|
-
mask = maximal_access.to_binary_s
|
35
|
-
begin
|
36
|
-
RubySMB::SMB2::BitField::FileAccessMask.read(mask)
|
37
|
-
rescue IOError
|
38
|
-
raise RubySMB::Error::InvalidBitField, 'Invalid ACCESS_MASK for the Maximal Share Access Rights'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Checks if the remote Tree is a directory
|
44
|
-
#
|
45
|
-
# @return [Boolean]
|
46
|
-
def is_directory?
|
47
|
-
share_type == 0x01
|
48
|
-
end
|
49
31
|
end
|
50
32
|
end
|
51
33
|
end
|
data/lib/ruby_smb/smb2/pipe.rb
CHANGED
@@ -17,6 +17,8 @@ module RubySMB
|
|
17
17
|
extend RubySMB::Dcerpc::Srvsvc
|
18
18
|
when 'winreg'
|
19
19
|
extend RubySMB::Dcerpc::Winreg
|
20
|
+
when 'svcctl'
|
21
|
+
extend RubySMB::Dcerpc::Svcctl
|
20
22
|
end
|
21
23
|
super(tree: tree, response: response, name: name)
|
22
24
|
end
|
@@ -40,13 +42,12 @@ module RubySMB
|
|
40
42
|
raise RubySMB::Error::InvalidPacket.new(
|
41
43
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
42
44
|
expected_cmd: RubySMB::SMB2::Packet::IoctlResponse::COMMAND,
|
43
|
-
|
44
|
-
received_cmd: response.smb2_header.command
|
45
|
+
packet: response
|
45
46
|
)
|
46
47
|
end
|
47
48
|
|
48
49
|
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
49
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
50
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
50
51
|
end
|
51
52
|
response
|
52
53
|
end
|
@@ -89,6 +90,7 @@ module RubySMB
|
|
89
90
|
request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
|
90
91
|
request.ctl_code = 0x0011C017
|
91
92
|
request.flags.is_fsctl = 0x00000001
|
93
|
+
# TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
|
92
94
|
request.buffer = action.to_binary_s
|
93
95
|
|
94
96
|
ioctl_raw_response = @tree.client.send_recv(request)
|
@@ -97,26 +99,12 @@ module RubySMB
|
|
97
99
|
raise RubySMB::Error::InvalidPacket.new(
|
98
100
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
99
101
|
expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
|
100
|
-
|
101
|
-
received_cmd: ioctl_response.smb2_header.command
|
102
|
+
packet: ioctl_response
|
102
103
|
)
|
103
104
|
end
|
104
|
-
|
105
|
-
if ioctl_response.status_code == WindowsError::NTStatus::STATUS_PENDING
|
106
|
-
sleep 1
|
107
|
-
ioctl_raw_response = @tree.client.dispatcher.recv_packet
|
108
|
-
ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
|
109
|
-
unless ioctl_response.valid?
|
110
|
-
raise RubySMB::Error::InvalidPacket.new(
|
111
|
-
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
112
|
-
expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
|
113
|
-
received_proto: ioctl_response.smb2_header.protocol,
|
114
|
-
received_cmd: ioctl_response.smb2_header.command
|
115
|
-
)
|
116
|
-
end
|
117
|
-
elsif ![WindowsError::NTStatus::STATUS_SUCCESS,
|
105
|
+
unless [WindowsError::NTStatus::STATUS_SUCCESS,
|
118
106
|
WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
|
119
|
-
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
107
|
+
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
120
108
|
end
|
121
109
|
|
122
110
|
raw_data = ioctl_response.output_data
|
@@ -6,7 +6,7 @@ module RubySMB
|
|
6
6
|
endian :little
|
7
7
|
bit32 :protocol, label: 'Protocol ID Field', initial_value: RubySMB::SMB2::SMB2_PROTOCOL_ID
|
8
8
|
uint16 :structure_size, label: 'Header Structure Size', initial_value: 64
|
9
|
-
uint16 :credit_charge, label: 'Credit Charge', initial_value:
|
9
|
+
uint16 :credit_charge, label: 'Credit Charge', initial_value: 1
|
10
10
|
nt_status :nt_status, label: 'NT Status', initial_value: 0
|
11
11
|
uint16 :command, label: 'Command'
|
12
12
|
uint16 :credits, label: 'Credit Request/Response'
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -23,12 +23,18 @@ module RubySMB
|
|
23
23
|
# @return [Integer]
|
24
24
|
attr_accessor :id
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# Whether or not the share associated with this tree connect needs to be encrypted (SMB 3.x)
|
27
|
+
# @!attribute [rw] tree_connect_encrypt_data
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_accessor :tree_connect_encrypt_data
|
30
|
+
|
31
|
+
def initialize(client:, share:, response:, encrypt: false)
|
32
|
+
@client = client
|
33
|
+
@share = share
|
34
|
+
@id = response.smb2_header.tree_id
|
35
|
+
@permissions = response.maximal_access
|
36
|
+
@share_type = response.share_type
|
37
|
+
@tree_connect_encrypt_data = encrypt
|
32
38
|
end
|
33
39
|
|
34
40
|
# Disconnects this Tree from the current session
|
@@ -38,19 +44,26 @@ module RubySMB
|
|
38
44
|
def disconnect!
|
39
45
|
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
|
40
46
|
request = set_header_fields(request)
|
41
|
-
raw_response = client.send_recv(request)
|
47
|
+
raw_response = client.send_recv(request, encrypt: @tree_connect_encrypt_data)
|
42
48
|
response = RubySMB::SMB2::Packet::TreeDisconnectResponse.read(raw_response)
|
43
49
|
unless response.valid?
|
44
50
|
raise RubySMB::Error::InvalidPacket.new(
|
45
51
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
46
52
|
expected_cmd: RubySMB::SMB2::Packet::TreeDisconnectResponse::COMMAND,
|
47
|
-
|
48
|
-
received_cmd: response.smb2_header.command
|
53
|
+
packet: response
|
49
54
|
)
|
50
55
|
end
|
51
56
|
response.status_code
|
52
57
|
end
|
53
58
|
|
59
|
+
def open_pipe(opts)
|
60
|
+
# Make sure we don't modify the caller's hash options
|
61
|
+
opts = opts.dup
|
62
|
+
opts[:filename] = opts[:filename].dup
|
63
|
+
opts[:filename] = opts[:filename][1..-1] if opts[:filename].start_with? '\\'
|
64
|
+
open_file(opts)
|
65
|
+
end
|
66
|
+
|
54
67
|
def open_file(filename:, attributes: nil, options: nil, disposition: RubySMB::Dispositions::FILE_OPEN,
|
55
68
|
impersonation: RubySMB::ImpersonationLevels::SEC_IMPERSONATE, read: true, write: false, delete: false)
|
56
69
|
|
@@ -96,26 +109,25 @@ module RubySMB
|
|
96
109
|
create_request.create_disposition = disposition
|
97
110
|
create_request.name = filename
|
98
111
|
|
99
|
-
raw_response = client.send_recv(create_request)
|
112
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
100
113
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
101
114
|
unless response.valid?
|
102
115
|
raise RubySMB::Error::InvalidPacket.new(
|
103
116
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
104
117
|
expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
|
105
|
-
|
106
|
-
received_cmd: response.smb2_header.command
|
118
|
+
packet: response
|
107
119
|
)
|
108
120
|
end
|
109
121
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
110
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
122
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
111
123
|
end
|
112
124
|
|
113
125
|
case @share_type
|
114
|
-
when
|
115
|
-
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
116
|
-
when
|
126
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
|
127
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
|
128
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
|
117
129
|
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
118
|
-
# when
|
130
|
+
# when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
119
131
|
# it's a printer!
|
120
132
|
else
|
121
133
|
raise RubySMB::Error::RubySMBError, 'Unsupported share type'
|
@@ -141,21 +153,27 @@ module RubySMB
|
|
141
153
|
directory_request.file_information_class = type::CLASS_LEVEL
|
142
154
|
directory_request.file_id = file_id
|
143
155
|
directory_request.name = pattern
|
144
|
-
|
156
|
+
|
157
|
+
max_read = client.server_max_read_size
|
158
|
+
max_read = 65536 unless client.server_supports_multi_credit
|
159
|
+
credit_charge = 0
|
160
|
+
if client.server_supports_multi_credit
|
161
|
+
credit_charge = (max_read - 1) / 65536 + 1
|
162
|
+
end
|
163
|
+
directory_request.output_length = max_read
|
164
|
+
directory_request.smb2_header.credit_charge = credit_charge
|
145
165
|
|
146
166
|
directory_request = set_header_fields(directory_request)
|
147
167
|
|
148
168
|
files = []
|
149
|
-
|
150
169
|
loop do
|
151
|
-
response = client.send_recv(directory_request)
|
170
|
+
response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
|
152
171
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
153
172
|
unless directory_response.valid?
|
154
173
|
raise RubySMB::Error::InvalidPacket.new(
|
155
174
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
156
175
|
expected_cmd: RubySMB::SMB2::Packet::QueryDirectoryResponse::COMMAND,
|
157
|
-
|
158
|
-
received_cmd: directory_response.smb2_header.command
|
176
|
+
packet: directory_response
|
159
177
|
)
|
160
178
|
end
|
161
179
|
|
@@ -164,7 +182,7 @@ module RubySMB
|
|
164
182
|
break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
165
183
|
|
166
184
|
unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
167
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
185
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
168
186
|
end
|
169
187
|
|
170
188
|
files += directory_response.results(type)
|
@@ -193,18 +211,17 @@ module RubySMB
|
|
193
211
|
|
194
212
|
create_request = open_directory_packet(directory: directory, disposition: disposition,
|
195
213
|
impersonation: impersonation, read: read, write: write, delete: delete)
|
196
|
-
raw_response = client.send_recv(create_request)
|
214
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
197
215
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
198
216
|
unless response.valid?
|
199
217
|
raise RubySMB::Error::InvalidPacket.new(
|
200
218
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
201
219
|
expected_cmd: RubySMB::SMB2::Packet::CreateResponse::COMMAND,
|
202
|
-
|
203
|
-
received_cmd: response.smb2_header.command
|
220
|
+
packet: response
|
204
221
|
)
|
205
222
|
end
|
206
223
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
207
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
224
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
208
225
|
end
|
209
226
|
|
210
227
|
response
|
@@ -250,7 +267,6 @@ module RubySMB
|
|
250
267
|
# @return [RubySMB::SMB2::Packet] the modified packet.
|
251
268
|
def set_header_fields(request)
|
252
269
|
request.smb2_header.tree_id = id
|
253
|
-
request.smb2_header.credit_charge = 1
|
254
270
|
request.smb2_header.credits = 256
|
255
271
|
request
|
256
272
|
end
|
data/lib/ruby_smb/version.rb
CHANGED
data/ruby_smb.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.platform = Gem::Platform::RUBY
|
27
27
|
end
|
28
28
|
|
29
|
-
spec.required_ruby_version = '>= 2.
|
29
|
+
spec.required_ruby_version = '>= 2.5'
|
30
30
|
|
31
31
|
spec.add_development_dependency 'bundler'
|
32
32
|
spec.add_development_dependency 'fivemat'
|
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_runtime_dependency 'rubyntlm'
|
37
37
|
spec.add_runtime_dependency 'windows_error'
|
38
38
|
spec.add_runtime_dependency 'bindata'
|
39
|
+
spec.add_runtime_dependency 'openssl-ccm'
|
40
|
+
spec.add_runtime_dependency 'openssl-cmac'
|
39
41
|
end
|
@@ -6,11 +6,68 @@ RSpec.describe RubySMB::Client do
|
|
6
6
|
let(:username) { 'msfadmin' }
|
7
7
|
let(:password) { 'msfpasswd' }
|
8
8
|
subject(:client) { described_class.new(dispatcher, username: username, password: password) }
|
9
|
-
let(:smb1_client) { described_class.new(dispatcher, smb2: false, username: username, password: password) }
|
10
|
-
let(:smb2_client) { described_class.new(dispatcher, smb1: false, username: username, password: password) }
|
9
|
+
let(:smb1_client) { described_class.new(dispatcher, smb2: false, smb3: false, username: username, password: password) }
|
10
|
+
let(:smb2_client) { described_class.new(dispatcher, smb1: false, smb3: false, username: username, password: password) }
|
11
|
+
let(:smb3_client) { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }
|
11
12
|
let(:empty_packet) { RubySMB::SMB1::Packet::EmptyPacket.new }
|
12
13
|
let(:error_packet) { RubySMB::SMB2::Packet::ErrorPacket.new }
|
13
14
|
|
15
|
+
it { is_expected.to respond_to :dispatcher }
|
16
|
+
it { is_expected.to respond_to :domain }
|
17
|
+
it { is_expected.to respond_to :local_workstation }
|
18
|
+
it { is_expected.to respond_to :ntlm_client }
|
19
|
+
it { is_expected.to respond_to :password }
|
20
|
+
it { is_expected.to respond_to :peer_native_os }
|
21
|
+
it { is_expected.to respond_to :peer_native_lm }
|
22
|
+
it { is_expected.to respond_to :primary_domain }
|
23
|
+
it { is_expected.to respond_to :default_name }
|
24
|
+
it { is_expected.to respond_to :default_domain }
|
25
|
+
it { is_expected.to respond_to :dns_host_name }
|
26
|
+
it { is_expected.to respond_to :dns_domain_name }
|
27
|
+
it { is_expected.to respond_to :dns_tree_name }
|
28
|
+
it { is_expected.to respond_to :os_version }
|
29
|
+
it { is_expected.to respond_to :dialect }
|
30
|
+
it { is_expected.to respond_to :sequence_counter }
|
31
|
+
it { is_expected.to respond_to :session_id }
|
32
|
+
it { is_expected.to respond_to :signing_required }
|
33
|
+
it { is_expected.to respond_to :smb1 }
|
34
|
+
it { is_expected.to respond_to :smb2 }
|
35
|
+
it { is_expected.to respond_to :smb3 }
|
36
|
+
it { is_expected.to respond_to :smb2_message_id }
|
37
|
+
it { is_expected.to respond_to :username }
|
38
|
+
it { is_expected.to respond_to :user_id }
|
39
|
+
it { is_expected.to respond_to :max_buffer_size }
|
40
|
+
it { is_expected.to respond_to :server_max_buffer_size }
|
41
|
+
it { is_expected.to respond_to :server_max_write_size }
|
42
|
+
it { is_expected.to respond_to :server_max_read_size }
|
43
|
+
it { is_expected.to respond_to :server_max_transact_size }
|
44
|
+
it { is_expected.to respond_to :preauth_integrity_hash_algorithm }
|
45
|
+
it { is_expected.to respond_to :preauth_integrity_hash_value }
|
46
|
+
it { is_expected.to respond_to :encryption_algorithm }
|
47
|
+
it { is_expected.to respond_to :client_encryption_key }
|
48
|
+
it { is_expected.to respond_to :server_encryption_key }
|
49
|
+
it { is_expected.to respond_to :session_encrypt_data }
|
50
|
+
it { is_expected.to respond_to :server_encryption_algorithms }
|
51
|
+
it { is_expected.to respond_to :server_compression_algorithms }
|
52
|
+
it { is_expected.to respond_to :negotiated_smb_version }
|
53
|
+
it { is_expected.to respond_to :session_key }
|
54
|
+
it { is_expected.to respond_to :tree_connects }
|
55
|
+
it { is_expected.to respond_to :open_files }
|
56
|
+
it { is_expected.to respond_to :use_ntlmv2 }
|
57
|
+
it { is_expected.to respond_to :usentlm2_session }
|
58
|
+
it { is_expected.to respond_to :send_lm }
|
59
|
+
it { is_expected.to respond_to :use_lanman_key }
|
60
|
+
it { is_expected.to respond_to :send_ntlm }
|
61
|
+
it { is_expected.to respond_to :spnopt }
|
62
|
+
it { is_expected.to respond_to :evasion_opts }
|
63
|
+
it { is_expected.to respond_to :native_os }
|
64
|
+
it { is_expected.to respond_to :native_lm }
|
65
|
+
it { is_expected.to respond_to :verify_signature }
|
66
|
+
it { is_expected.to respond_to :auth_user }
|
67
|
+
it { is_expected.to respond_to :last_file_id }
|
68
|
+
it { is_expected.to respond_to :pid }
|
69
|
+
it { is_expected.to respond_to :server_supports_multi_credit }
|
70
|
+
|
14
71
|
describe '#initialize' do
|
15
72
|
it 'should raise an ArgumentError without a valid dispatcher' do
|
16
73
|
expect { described_class.new(nil) }.to raise_error(ArgumentError)
|
@@ -26,14 +83,21 @@ RSpec.describe RubySMB::Client do
|
|
26
83
|
|
27
84
|
it 'accepts an argument to disable smb1 support' do
|
28
85
|
expect(smb2_client.smb1).to be false
|
86
|
+
expect(smb3_client.smb1).to be false
|
29
87
|
end
|
30
88
|
|
31
89
|
it 'accepts an argument to disable smb2 support' do
|
32
90
|
expect(smb1_client.smb2).to be false
|
91
|
+
expect(smb3_client.smb2).to be false
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'accepts an argument to disable smb3 support' do
|
95
|
+
expect(smb1_client.smb3).to be false
|
96
|
+
expect(smb2_client.smb3).to be false
|
33
97
|
end
|
34
98
|
|
35
|
-
it 'raises an exception if
|
36
|
-
expect { described_class.new(dispatcher, smb1: false, smb2: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
99
|
+
it 'raises an exception if SMB1, SMB2 and SMB3 are disabled' do
|
100
|
+
expect { described_class.new(dispatcher, smb1: false, smb2: false, smb3: false, username: username, password: password) }.to raise_error(ArgumentError, 'You must enable at least one Protocol')
|
37
101
|
end
|
38
102
|
|
39
103
|
it 'sets the username attribute' do
|
@@ -44,6 +108,11 @@ RSpec.describe RubySMB::Client do
|
|
44
108
|
expect(client.password).to eq password
|
45
109
|
end
|
46
110
|
|
111
|
+
it 'sets the session_encrypt_data attribute' do
|
112
|
+
client = described_class.new(dispatcher, username: username, password: password, always_encrypt: true)
|
113
|
+
expect(client.session_encrypt_data).to eq true
|
114
|
+
end
|
115
|
+
|
47
116
|
it 'creates an NTLM client' do
|
48
117
|
expect(client.ntlm_client).to be_a Net::NTLM::Client
|
49
118
|
end
|
@@ -58,7 +127,7 @@ RSpec.describe RubySMB::Client do
|
|
58
127
|
expect(opt[:workstation]).to eq(local_workstation)
|
59
128
|
expect(opt[:domain]).to eq(domain)
|
60
129
|
flags = Net::NTLM::Client::DEFAULT_FLAGS |
|
61
|
-
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000
|
130
|
+
Net::NTLM::FLAGS[:TARGET_INFO] | 0x02000000 ^ Net::NTLM::FLAGS[:OEM]
|
62
131
|
expect(opt[:flags]).to eq(flags)
|
63
132
|
end
|
64
133
|
|
@@ -74,30 +143,367 @@ RSpec.describe RubySMB::Client do
|
|
74
143
|
it 'sets the max_buffer_size to MAX_BUFFER_SIZE' do
|
75
144
|
expect(client.max_buffer_size).to eq RubySMB::Client::MAX_BUFFER_SIZE
|
76
145
|
end
|
146
|
+
|
147
|
+
it 'sets the server_supports_multi_credit to false' do
|
148
|
+
expect(client.server_supports_multi_credit).to be false
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'sets the pid to a random value' do
|
152
|
+
100.times do
|
153
|
+
previous_pid = client.pid
|
154
|
+
client = described_class.new(dispatcher, username: username, password: password)
|
155
|
+
expect(client.pid).to_not eq(previous_pid)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '#echo' do
|
161
|
+
let(:response) { double('Echo Response') }
|
162
|
+
before :example do
|
163
|
+
allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'with SMB1' do
|
167
|
+
it 'calls #smb1_echo with the expected arguments' do
|
168
|
+
allow(smb1_client).to receive(:smb1_echo).and_return(response)
|
169
|
+
count = 3
|
170
|
+
data = 'testing...'
|
171
|
+
smb1_client.echo(count: count, data: data)
|
172
|
+
expect(smb1_client).to have_received(:smb1_echo).with(count: count, data: data)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'with SMB2' do
|
177
|
+
it 'calls #smb2_echo without arguments' do
|
178
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
179
|
+
smb2_client.echo
|
180
|
+
expect(smb2_client).to have_received(:smb2_echo)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'with SMB3' do
|
185
|
+
it 'calls #smb2_echo without arguments' do
|
186
|
+
allow(smb3_client).to receive(:smb2_echo).and_return(response)
|
187
|
+
smb3_client.echo
|
188
|
+
expect(smb3_client).to have_received(:smb2_echo)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns the expected status code' do
|
193
|
+
allow(smb2_client).to receive(:smb2_echo).and_return(response)
|
194
|
+
expect(smb2_client.echo).to eq(WindowsError::NTStatus::STATUS_SUCCESS)
|
195
|
+
end
|
77
196
|
end
|
78
197
|
|
79
198
|
describe '#send_recv' do
|
80
199
|
let(:smb1_request) { RubySMB::SMB1::Packet::TreeConnectRequest.new }
|
81
200
|
let(:smb2_request) { RubySMB::SMB2::Packet::TreeConnectRequest.new }
|
201
|
+
let(:smb2_header) { RubySMB::SMB2::SMB2Header.new }
|
82
202
|
|
83
203
|
before(:each) do
|
84
|
-
|
85
|
-
|
204
|
+
allow(client).to receive(:is_status_pending?).and_return(false)
|
205
|
+
allow(dispatcher).to receive(:send_packet).and_return(nil)
|
206
|
+
allow(dispatcher).to receive(:recv_packet).and_return('A')
|
207
|
+
allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_return(smb2_header)
|
86
208
|
end
|
87
209
|
|
88
|
-
|
89
|
-
|
90
|
-
|
210
|
+
context 'when signing' do
|
211
|
+
it 'calls #smb1_sign if it is an SMB1 packet' do
|
212
|
+
expect(client).to receive(:smb1_sign).with(smb1_request).and_call_original
|
213
|
+
client.send_recv(smb1_request)
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'with an SMB2 packet' do
|
217
|
+
it 'does not sign a SessionSetupRequest packet' do
|
218
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
219
|
+
expect(smb2_client).to_not receive(:smb2_sign)
|
220
|
+
expect(smb2_client).to_not receive(:smb3_sign)
|
221
|
+
smb2_client.send_recv(RubySMB::SMB2::Packet::SessionSetupRequest.new)
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'calls #smb2_sign if it is an SMB2 client' do
|
225
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
226
|
+
expect(smb2_client).to receive(:smb2_sign).with(smb2_request).and_call_original
|
227
|
+
smb2_client.send_recv(smb2_request)
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'calls #smb3_sign if it is an SMB3 client' do
|
231
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
232
|
+
expect(smb3_client).to receive(:smb3_sign).with(smb2_request).and_call_original
|
233
|
+
smb3_client.send_recv(smb2_request)
|
234
|
+
end
|
235
|
+
end
|
91
236
|
end
|
92
237
|
|
93
|
-
it '
|
94
|
-
expect(
|
238
|
+
it 'sends the expected packet and gets the response' do
|
239
|
+
expect(dispatcher).to receive(:send_packet).with(smb1_request)
|
240
|
+
expect(dispatcher).to receive(:recv_packet)
|
95
241
|
client.send_recv(smb1_request)
|
96
242
|
end
|
97
243
|
|
98
|
-
|
99
|
-
|
100
|
-
|
244
|
+
context 'with SMB1' do
|
245
|
+
it 'does not check if it is a STATUS_PENDING response' do
|
246
|
+
expect(smb1_client).to_not receive(:is_status_pending?)
|
247
|
+
smb1_client.send_recv(smb1_request)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'set the #uid SMB header when #user_id is defined' do
|
251
|
+
smb1_client.user_id = 333
|
252
|
+
smb1_client.send_recv(smb1_request)
|
253
|
+
expect(smb1_request.smb_header.uid).to eq(333)
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'does not set the #uid SMB header when #user_id is not defined' do
|
257
|
+
smb1_client.send_recv(smb1_request)
|
258
|
+
expect(smb1_request.smb_header.uid).to eq(0)
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'set the #pid SMB header when #pid is defined' do
|
262
|
+
smb1_client.pid = 333
|
263
|
+
smb1_client.send_recv(smb1_request)
|
264
|
+
expect(smb1_request.smb_header.pid_low).to eq(333)
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'does not set the #pid SMB header when #pid is not defined' do
|
268
|
+
smb1_client.pid = nil
|
269
|
+
smb1_client.send_recv(smb1_request)
|
270
|
+
expect(smb1_request.smb_header.pid_low).to eq(0)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'with SMB2' do
|
275
|
+
context 'when receiving a STATUS_PENDING response' do
|
276
|
+
it 'waits 1 second and reads/decrypts again' do
|
277
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(true, false)
|
278
|
+
expect(smb2_client).to receive(:sleep).with(1)
|
279
|
+
expect(dispatcher).to receive(:recv_packet).twice
|
280
|
+
smb2_client.send_recv(smb2_request)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'with SMB3 and encryption' do
|
286
|
+
before :example do
|
287
|
+
smb3_client.dialect = '0x0300'
|
288
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(false)
|
289
|
+
end
|
290
|
+
|
291
|
+
context 'with a SessionSetupRequest' do
|
292
|
+
it 'does not encrypt/decrypt' do
|
293
|
+
request = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
294
|
+
expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
|
295
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
|
296
|
+
smb3_client.send_recv(request)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
context 'with a NegotiateRequest' do
|
301
|
+
it 'does not encrypt/decrypt' do
|
302
|
+
request = RubySMB::SMB2::Packet::NegotiateRequest.new
|
303
|
+
expect(smb3_client).to receive(:send_packet).with(request, encrypt: false)
|
304
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: false)
|
305
|
+
smb3_client.send_recv(request)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'encrypts and decrypts' do
|
310
|
+
expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
|
311
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: true)
|
312
|
+
smb3_client.send_recv(smb2_request)
|
313
|
+
end
|
314
|
+
|
315
|
+
context 'when receiving a STATUS_PENDING response' do
|
316
|
+
it 'waits 1 second and reads/decrypts again' do
|
317
|
+
allow(smb3_client).to receive(:is_status_pending?).and_return(true, false)
|
318
|
+
expect(smb3_client).to receive(:sleep).with(1)
|
319
|
+
expect(smb3_client).to receive(:send_packet).with(smb2_request, encrypt: true)
|
320
|
+
expect(smb3_client).to receive(:recv_packet).with(encrypt: true).twice
|
321
|
+
smb3_client.send_recv(smb2_request)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'increments the sequence counter if signing is required and the session key exist' do
|
327
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
328
|
+
smb2_client.signing_required = true
|
329
|
+
smb2_client.session_key = 'key'
|
330
|
+
smb2_client.sequence_counter = 0
|
331
|
+
smb2_client.send_recv(smb2_request)
|
332
|
+
expect(smb2_client.sequence_counter).to eq(1)
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'updates #smb2_message_id with SMB2 header #credit_charge if the server supports multi credits' do
|
336
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
337
|
+
smb2_client.smb2_message_id = 0
|
338
|
+
smb2_client.server_supports_multi_credit = true
|
339
|
+
smb2_header.credit_charge = 5
|
340
|
+
smb2_client.send_recv(smb2_request)
|
341
|
+
expect(smb2_client.smb2_message_id).to eq(5)
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'does not update #msb2_message_id with SMB2 header #credit_charge if the server does not support multi credits' do
|
345
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
346
|
+
smb2_client.smb2_message_id = 0
|
347
|
+
smb2_client.server_supports_multi_credit = false
|
348
|
+
smb2_header.credit_charge = 5
|
349
|
+
smb2_client.send_recv(smb2_request)
|
350
|
+
expect(smb2_client.smb2_message_id).to eq(1)
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'ignores errors thrown when parsing the SMB2 header' do
|
354
|
+
allow(RubySMB::SMB2::SMB2Header).to receive(:read).and_raise(IOError)
|
355
|
+
expect { smb2_client.send_recv(smb2_request) }.to_not raise_error
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
describe '#is_status_pending?' do
|
360
|
+
let(:response) {
|
361
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
362
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
363
|
+
res.smb2_header.flags.async_command = 1
|
364
|
+
res
|
365
|
+
}
|
366
|
+
|
367
|
+
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
368
|
+
expect(client.is_status_pending?(response.smb2_header)).to be true
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
372
|
+
response.smb2_header.flags.async_command = 0
|
373
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
377
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
378
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe '#can_be_encrypted?' do
|
383
|
+
it 'returns true if the packet can be encrypted' do
|
384
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
385
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'returns false if it is an SMB1 packet' do
|
389
|
+
packet = RubySMB::SMB1::Packet::LogoffRequest.new
|
390
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
391
|
+
end
|
392
|
+
|
393
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
|
394
|
+
it "returns false if the packet is a #{klass}" do
|
395
|
+
packet = klass.new
|
396
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
describe '#encryption_supported?' do
|
402
|
+
['0x0300', '0x0302', '0x0311'].each do |dialect|
|
403
|
+
it "returns true if the dialect is #{dialect}" do
|
404
|
+
client.dialect = dialect
|
405
|
+
expect(client.encryption_supported?).to be true
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
it "returns false if the dialect does not support encryption" do
|
410
|
+
client.dialect = '0x0202'
|
411
|
+
expect(client.encryption_supported?).to be false
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe '#send_packet' do
|
416
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
417
|
+
before :example do
|
418
|
+
allow(dispatcher).to receive(:send_packet)
|
419
|
+
client.dialect = '0x0300'
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'does not encrypt the packet' do
|
423
|
+
expect(client).to_not receive(:smb3_encrypt)
|
424
|
+
client.send_packet(packet)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'sends the packet through the dispatcher' do
|
428
|
+
client.send_packet(packet)
|
429
|
+
expect(dispatcher).to have_received(:send_packet).with(packet)
|
430
|
+
end
|
431
|
+
|
432
|
+
context 'with encryption' do
|
433
|
+
it 'creates a Transform request' do
|
434
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
435
|
+
client.send_packet(packet, encrypt: true)
|
436
|
+
end
|
437
|
+
|
438
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
439
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
440
|
+
expect { client.send_packet(packet, encrypt: true) }.to raise_error(
|
441
|
+
RubySMB::Error::EncryptionError,
|
442
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
443
|
+
)
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'sends the encrypted packet' do
|
447
|
+
encrypted_packet = double('Encrypted packet')
|
448
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
449
|
+
client.send_packet(packet, encrypt: true)
|
450
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
describe '#recv_packet' do
|
456
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
457
|
+
before :example do
|
458
|
+
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
459
|
+
client.dialect = '0x0300'
|
460
|
+
allow(client).to receive(:smb3_decrypt)
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'reads the response packet' do
|
464
|
+
client.recv_packet
|
465
|
+
expect(dispatcher).to have_received(:recv_packet)
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'raises an CommunicationError exception if an error occurs while receiving the response' do
|
469
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
470
|
+
expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
|
471
|
+
end
|
472
|
+
|
473
|
+
context 'with encryption' do
|
474
|
+
it 'raises an EncryptionError exception if an error occurs while receiving the response' do
|
475
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
476
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
477
|
+
RubySMB::Error::EncryptionError,
|
478
|
+
'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
|
479
|
+
'The server supports encryption but was not able to handle the encrypted request.'
|
480
|
+
)
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'parses the response as a Transform response packet' do
|
484
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
485
|
+
client.recv_packet(encrypt: true)
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
489
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
490
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'decrypts the Transform response packet' do
|
494
|
+
transform = double('Transform header packet')
|
495
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
496
|
+
client.recv_packet(encrypt: true)
|
497
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
498
|
+
end
|
499
|
+
|
500
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
501
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
|
502
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
503
|
+
RubySMB::Error::EncryptionError,
|
504
|
+
'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
|
505
|
+
)
|
506
|
+
end
|
101
507
|
end
|
102
508
|
end
|
103
509
|
|
@@ -240,6 +646,44 @@ RSpec.describe RubySMB::Client do
|
|
240
646
|
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
241
647
|
end
|
242
648
|
end
|
649
|
+
|
650
|
+
context 'with SMB3' do
|
651
|
+
let(:raw_response) { double('Raw response') }
|
652
|
+
let(:logoff_response) {
|
653
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
654
|
+
}
|
655
|
+
before :example do
|
656
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
657
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
658
|
+
allow(smb3_client).to receive(:wipe_state!)
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'creates a LogoffRequest packet' do
|
662
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
663
|
+
smb3_client.logoff!
|
664
|
+
end
|
665
|
+
|
666
|
+
it 'calls #send_recv' do
|
667
|
+
expect(smb3_client).to receive(:send_recv)
|
668
|
+
smb3_client.logoff!
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
672
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
673
|
+
smb3_client.logoff!
|
674
|
+
end
|
675
|
+
|
676
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
677
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
678
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
679
|
+
end
|
680
|
+
|
681
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
682
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
683
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
684
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
685
|
+
end
|
686
|
+
end
|
243
687
|
end
|
244
688
|
|
245
689
|
context 'NetBIOS Session Service' do
|
@@ -310,7 +754,7 @@ RSpec.describe RubySMB::Client do
|
|
310
754
|
expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
|
311
755
|
expect(session_packet.called_name).to eq called_name
|
312
756
|
expect(session_packet.calling_name).to eq calling_name
|
313
|
-
expect(session_packet.session_header.
|
757
|
+
expect(session_packet.session_header.stream_protocol_length).to eq(
|
314
758
|
session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
|
315
759
|
)
|
316
760
|
end
|
@@ -365,7 +809,8 @@ RSpec.describe RubySMB::Client do
|
|
365
809
|
smb1_extended_response.to_binary_s
|
366
810
|
}
|
367
811
|
|
368
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
812
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
813
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
369
814
|
|
370
815
|
describe '#smb1_negotiate_request' do
|
371
816
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -373,33 +818,158 @@ RSpec.describe RubySMB::Client do
|
|
373
818
|
end
|
374
819
|
|
375
820
|
it 'sets the default SMB1 Dialect' do
|
376
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
821
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
822
|
+
buffer_format: 2,
|
823
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
824
|
+
)
|
377
825
|
end
|
378
826
|
|
379
827
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
380
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
828
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
829
|
+
buffer_format: 2,
|
830
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
831
|
+
)
|
381
832
|
end
|
382
833
|
|
383
834
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
384
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
835
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
836
|
+
buffer_format: 2,
|
837
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
838
|
+
)
|
385
839
|
end
|
386
840
|
|
387
841
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
388
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
842
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
843
|
+
buffer_format: 2,
|
844
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
845
|
+
)
|
846
|
+
end
|
847
|
+
|
848
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
849
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
850
|
+
buffer_format: 2,
|
851
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
852
|
+
)
|
853
|
+
end
|
854
|
+
|
855
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
856
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
857
|
+
buffer_format: 2,
|
858
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
859
|
+
)
|
860
|
+
end
|
861
|
+
|
862
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
863
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
864
|
+
buffer_format: 2,
|
865
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
866
|
+
)
|
389
867
|
end
|
390
868
|
end
|
391
869
|
|
392
|
-
describe '#
|
870
|
+
describe '#smb2_3_negotiate_request' do
|
393
871
|
it 'return an SMB2 Negotiate Request packet' do
|
394
|
-
expect(client.
|
872
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
395
873
|
end
|
396
874
|
|
397
|
-
it 'sets the default SMB2 Dialect' do
|
398
|
-
expect(client.
|
875
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
876
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
877
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
878
|
+
)
|
879
|
+
end
|
880
|
+
|
881
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
882
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
883
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
884
|
+
)
|
399
885
|
end
|
400
886
|
|
401
887
|
it 'sets the Message ID to 0' do
|
402
|
-
expect(client.
|
888
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
889
|
+
end
|
890
|
+
|
891
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
892
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
893
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
894
|
+
)
|
895
|
+
end
|
896
|
+
|
897
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
898
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
899
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
900
|
+
)
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
describe '#add_smb3_to_negotiate_request' do
|
905
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
906
|
+
|
907
|
+
it 'adds the default SMB3 dialects' do
|
908
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
909
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
910
|
+
)
|
911
|
+
end
|
912
|
+
|
913
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
914
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
915
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
916
|
+
end
|
917
|
+
|
918
|
+
it 'sets encryption capability flag' do
|
919
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
920
|
+
end
|
921
|
+
|
922
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
923
|
+
before :example do
|
924
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
925
|
+
end
|
926
|
+
|
927
|
+
it 'adds 3 Negotiate Contexts' do
|
928
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
929
|
+
end
|
930
|
+
|
931
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
932
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
933
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
934
|
+
end
|
935
|
+
expect(nc.length).to eq(1)
|
936
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
937
|
+
end
|
938
|
+
|
939
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
940
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
941
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
942
|
+
end
|
943
|
+
expect(nc.length).to eq(1)
|
944
|
+
expect(nc.first.data.ciphers).to eq(
|
945
|
+
[
|
946
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
947
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
948
|
+
]
|
949
|
+
)
|
950
|
+
end
|
951
|
+
|
952
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
953
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
954
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
955
|
+
end
|
956
|
+
expect(nc.length).to eq(1)
|
957
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
958
|
+
[
|
959
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
960
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
961
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
962
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
963
|
+
]
|
964
|
+
)
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
969
|
+
it 'does not add any Negotiate Context' do
|
970
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
971
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
972
|
+
end
|
403
973
|
end
|
404
974
|
end
|
405
975
|
|
@@ -414,10 +984,15 @@ RSpec.describe RubySMB::Client do
|
|
414
984
|
client.negotiate_request
|
415
985
|
end
|
416
986
|
|
417
|
-
it 'calls #
|
418
|
-
expect(smb2_client).to receive(:
|
987
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
988
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
419
989
|
smb2_client.negotiate_request
|
420
990
|
end
|
991
|
+
|
992
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
993
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
994
|
+
smb3_client.negotiate_request
|
995
|
+
end
|
421
996
|
end
|
422
997
|
|
423
998
|
describe '#negotiate_response' do
|
@@ -464,12 +1039,28 @@ RSpec.describe RubySMB::Client do
|
|
464
1039
|
end
|
465
1040
|
end
|
466
1041
|
|
467
|
-
context 'with
|
1042
|
+
context 'with only SMB3' do
|
1043
|
+
it 'returns a properly formed packet' do
|
1044
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
it 'raises an exception if the Response is invalid' do
|
1048
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
1052
|
+
bogus_response = smb2_response
|
1053
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
1054
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1055
|
+
end
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
468
1059
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
469
1060
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
470
1061
|
end
|
471
1062
|
|
472
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
1063
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
473
1064
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
474
1065
|
end
|
475
1066
|
end
|
@@ -477,9 +1068,10 @@ RSpec.describe RubySMB::Client do
|
|
477
1068
|
|
478
1069
|
describe '#parse_negotiate_response' do
|
479
1070
|
context 'when SMB1 was Negotiated' do
|
480
|
-
it 'turns off SMB2 support' do
|
1071
|
+
it 'turns off SMB2 and SMB3 support' do
|
481
1072
|
client.parse_negotiate_response(smb1_extended_response)
|
482
1073
|
expect(client.smb2).to be false
|
1074
|
+
expect(client.smb3).to be false
|
483
1075
|
end
|
484
1076
|
|
485
1077
|
it 'sets whether or not signing is required' do
|
@@ -502,12 +1094,18 @@ RSpec.describe RubySMB::Client do
|
|
502
1094
|
it 'returns the string \'SMB1\'' do
|
503
1095
|
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
504
1096
|
end
|
1097
|
+
|
1098
|
+
it 'sets #negotiated_smb_version to 1' do
|
1099
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1100
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1101
|
+
end
|
505
1102
|
end
|
506
1103
|
|
507
1104
|
context 'when SMB2 was negotiated' do
|
508
|
-
it 'turns off SMB1 support' do
|
1105
|
+
it 'turns off SMB1 and SMB3 support' do
|
509
1106
|
client.parse_negotiate_response(smb2_response)
|
510
1107
|
expect(client.smb1).to be false
|
1108
|
+
expect(client.smb3).to be false
|
511
1109
|
end
|
512
1110
|
|
513
1111
|
it 'sets whether or not signing is required' do
|
@@ -525,10 +1123,142 @@ RSpec.describe RubySMB::Client do
|
|
525
1123
|
it 'returns the string \'SMB2\'' do
|
526
1124
|
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
527
1125
|
end
|
1126
|
+
|
1127
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1128
|
+
smb2_response.capabilities.large_mtu = 1
|
1129
|
+
client.parse_negotiate_response(smb2_response)
|
1130
|
+
expect(client.server_supports_multi_credit).to be true
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
|
1134
|
+
smb2_response.dialect_revision = 0x0202
|
1135
|
+
smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
|
1136
|
+
client.parse_negotiate_response(smb2_response)
|
1137
|
+
expect(client.server_supports_multi_credit).to be false
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
context 'when SMB3 was negotiated' do
|
1142
|
+
it 'turns off SMB1 and SMB2 support' do
|
1143
|
+
client.parse_negotiate_response(smb3_response)
|
1144
|
+
expect(client.smb1).to be false
|
1145
|
+
expect(client.smb2).to be false
|
1146
|
+
end
|
1147
|
+
|
1148
|
+
it 'sets whether or not signing is required' do
|
1149
|
+
smb3_response.security_mode.signing_required = 1
|
1150
|
+
client.parse_negotiate_response(smb3_response)
|
1151
|
+
expect(client.signing_required).to be true
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
it 'sets #dialect to the negotiated dialect' do
|
1155
|
+
client.parse_negotiate_response(smb3_response)
|
1156
|
+
expect(client.dialect).to eq '0x0300'
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
it 'returns the string \'SMB2\'' do
|
1160
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1164
|
+
smb3_response.capabilities.large_mtu = 1
|
1165
|
+
client.parse_negotiate_response(smb3_response)
|
1166
|
+
expect(client.server_supports_multi_credit).to be true
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
context 'when the server supports encryption' do
|
1170
|
+
before :example do
|
1171
|
+
smb3_response.capabilities.encryption = 1
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
it 'sets the expected encryption algorithm' do
|
1175
|
+
client.parse_negotiate_response(smb3_response)
|
1176
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
it 'keeps session encryption enabled if it was already' do
|
1180
|
+
client.session_encrypt_data = true
|
1181
|
+
client.parse_negotiate_response(smb3_response)
|
1182
|
+
expect(client.session_encrypt_data).to be true
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
it 'keeps session encryption disabled if it was already' do
|
1186
|
+
client.session_encrypt_data = false
|
1187
|
+
client.parse_negotiate_response(smb3_response)
|
1188
|
+
expect(client.session_encrypt_data).to be false
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
context 'when the server does not support encryption' do
|
1193
|
+
before :example do
|
1194
|
+
smb3_response.capabilities.encryption = 0
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
it 'disables session encryption if it was already enabled' do
|
1198
|
+
client.session_encrypt_data = true
|
1199
|
+
client.parse_negotiate_response(smb3_response)
|
1200
|
+
expect(client.session_encrypt_data).to be false
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
it 'keeps session encryption disabled if it was already' do
|
1204
|
+
client.session_encrypt_data = false
|
1205
|
+
client.parse_negotiate_response(smb3_response)
|
1206
|
+
expect(client.session_encrypt_data).to be false
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1212
|
+
it 'only turns off SMB1 support' do
|
1213
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1214
|
+
client.parse_negotiate_response(smb2_response)
|
1215
|
+
expect(client.smb1).to be false
|
1216
|
+
expect(client.smb2).to be true
|
1217
|
+
expect(client.smb3).to be true
|
1218
|
+
end
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
context 'when the negotiation failed' do
|
1222
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1223
|
+
before :example do
|
1224
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
it 'raises the expected exception with SMB2' do
|
1228
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1229
|
+
RubySMB::Error::NegotiationFailure,
|
1230
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1231
|
+
)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
it 'raises the expected exception with SMB3' do
|
1235
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1236
|
+
RubySMB::Error::NegotiationFailure,
|
1237
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1238
|
+
)
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
|
1242
|
+
context 'with an unknown status code' do
|
1243
|
+
it 'raises the expected exception' do
|
1244
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1245
|
+
RubySMB::Error::NegotiationFailure,
|
1246
|
+
'Unable to negotiate with remote host'
|
1247
|
+
)
|
1248
|
+
end
|
1249
|
+
end
|
528
1250
|
end
|
529
1251
|
end
|
530
1252
|
|
531
1253
|
describe '#negotiate' do
|
1254
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1255
|
+
before :example do
|
1256
|
+
allow(client).to receive(:negotiate_request)
|
1257
|
+
allow(client).to receive(:send_recv)
|
1258
|
+
allow(client).to receive(:negotiate_response)
|
1259
|
+
allow(client).to receive(:parse_negotiate_response)
|
1260
|
+
end
|
1261
|
+
|
532
1262
|
it 'calls the backing methods' do
|
533
1263
|
expect(client).to receive(:negotiate_request)
|
534
1264
|
expect(client).to receive(:send_recv)
|
@@ -537,18 +1267,247 @@ RSpec.describe RubySMB::Client do
|
|
537
1267
|
client.negotiate
|
538
1268
|
end
|
539
1269
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
1270
|
+
context 'with SMB1' do
|
1271
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1272
|
+
request_packet = client.smb1_negotiate_request
|
1273
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1274
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1275
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1276
|
+
client.negotiate
|
1277
|
+
end
|
547
1278
|
end
|
548
1279
|
|
549
|
-
|
550
|
-
|
551
|
-
|
1280
|
+
context "with 0x0311 dialect" do
|
1281
|
+
it 'calls #parse_negotiate_response and updates the preauth hash' do
|
1282
|
+
client.dialect = '0x0311'
|
1283
|
+
request_packet = client.smb2_3_negotiate_request
|
1284
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1285
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1286
|
+
expect(client).to receive(:parse_negotiate_response).with(smb3_response)
|
1287
|
+
expect(client).to receive(:update_preauth_hash).with(request_packet)
|
1288
|
+
expect(client).to receive(:update_preauth_hash).with(smb3_response)
|
1289
|
+
client.negotiate
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
context 'with a wildcard revision number response' do
|
1294
|
+
before :example do
|
1295
|
+
client.dialect = '0x02ff'
|
1296
|
+
allow(client).to receive(:smb2_message_id=) do
|
1297
|
+
client.dialect = '0x0202'
|
1298
|
+
end
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
it 'increments the message ID' do
|
1302
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1303
|
+
client.negotiate
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
it 're-negotiates' do
|
1307
|
+
expect(client).to receive(:negotiate_request).twice
|
1308
|
+
expect(client).to receive(:send_recv).twice
|
1309
|
+
expect(client).to receive(:negotiate_response).twice
|
1310
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1311
|
+
client.negotiate
|
1312
|
+
end
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
context 'when an error occurs' do
|
1316
|
+
before :example do
|
1317
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1318
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1319
|
+
client.smb1 = false
|
1320
|
+
client.smb2 = false
|
1321
|
+
client.smb3 = false
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
context 'with SMB1' do
|
1325
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1326
|
+
|
1327
|
+
it 'raise the expected exception' do
|
1328
|
+
client.smb1 = true
|
1329
|
+
expect { client.negotiate }.to raise_error(
|
1330
|
+
RubySMB::Error::NegotiationFailure,
|
1331
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1332
|
+
)
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
|
1336
|
+
context 'with SMB2' do
|
1337
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1338
|
+
|
1339
|
+
it 'raise the expected exception' do
|
1340
|
+
client.smb2 = true
|
1341
|
+
expect { client.negotiate }.to raise_error(
|
1342
|
+
RubySMB::Error::NegotiationFailure,
|
1343
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1344
|
+
)
|
1345
|
+
end
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
context 'with SMB3' do
|
1349
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1350
|
+
|
1351
|
+
it 'raise the expected exception' do
|
1352
|
+
client.smb3 = true
|
1353
|
+
expect { client.negotiate }.to raise_error(
|
1354
|
+
RubySMB::Error::NegotiationFailure,
|
1355
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1356
|
+
)
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
describe '#parse_smb3_capabilities' do
|
1362
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1363
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1364
|
+
let(:nc_encryption) do
|
1365
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1366
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1367
|
+
)
|
1368
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1369
|
+
nc
|
1370
|
+
end
|
1371
|
+
let(:nc_integrity) do
|
1372
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1373
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1374
|
+
)
|
1375
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1376
|
+
nc
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
before :example do
|
1380
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1381
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1382
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1383
|
+
end
|
1384
|
+
|
1385
|
+
context 'when selecting the integrity hash algorithm' do
|
1386
|
+
context 'with one algorithm' do
|
1387
|
+
it 'selects the expected algorithm' do
|
1388
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1389
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1390
|
+
end
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
context 'with multiple algorithms' do
|
1394
|
+
it 'selects the first algorithm' do
|
1395
|
+
nc = smb3_response.find_negotiate_context(
|
1396
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1397
|
+
)
|
1398
|
+
nc.data.hash_algorithms << 3
|
1399
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1400
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
context 'without integrity negotiate context' do
|
1405
|
+
it 'raises the expected exception' do
|
1406
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1407
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1408
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1409
|
+
RubySMB::Error::EncryptionError,
|
1410
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1411
|
+
)
|
1412
|
+
end
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
context 'with an unknown integrity hash algorithm' do
|
1416
|
+
it 'raises the expected exception' do
|
1417
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1418
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1419
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1420
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1421
|
+
)
|
1422
|
+
nc.data.hash_algorithms << 5
|
1423
|
+
smb3_response.add_negotiate_context(nc)
|
1424
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1425
|
+
RubySMB::Error::EncryptionError,
|
1426
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1427
|
+
)
|
1428
|
+
end
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
context 'when selecting the encryption algorithm' do
|
1433
|
+
context 'with one algorithm' do
|
1434
|
+
it 'selects the expected algorithm' do
|
1435
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1436
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
|
1440
|
+
context 'with multiple algorithms' do
|
1441
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1442
|
+
nc = smb3_response.find_negotiate_context(
|
1443
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1444
|
+
)
|
1445
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1446
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1447
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1448
|
+
end
|
1449
|
+
|
1450
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1451
|
+
nc = smb3_response.find_negotiate_context(
|
1452
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1453
|
+
)
|
1454
|
+
nc.data.ciphers << 3
|
1455
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1456
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
it 'keep tracks of the server supported algorithms' do
|
1460
|
+
nc = smb3_response.find_negotiate_context(
|
1461
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1462
|
+
)
|
1463
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1464
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1465
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1466
|
+
end
|
1467
|
+
end
|
1468
|
+
|
1469
|
+
context 'without encryption context' do
|
1470
|
+
it 'raises the expected exception' do
|
1471
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1472
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1473
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1474
|
+
RubySMB::Error::EncryptionError,
|
1475
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1476
|
+
)
|
1477
|
+
end
|
1478
|
+
end
|
1479
|
+
|
1480
|
+
context 'with an unknown encryption algorithm' do
|
1481
|
+
it 'raises the expected exception' do
|
1482
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1483
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1484
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1485
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1486
|
+
)
|
1487
|
+
nc.data.ciphers << 14
|
1488
|
+
smb3_response.add_negotiate_context(nc)
|
1489
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1490
|
+
RubySMB::Error::EncryptionError,
|
1491
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1492
|
+
)
|
1493
|
+
end
|
1494
|
+
end
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
context 'when selecting the compression algorithm' do
|
1498
|
+
it 'keep tracks of the server supported algorithms' do
|
1499
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1500
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1501
|
+
)
|
1502
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1503
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1504
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1505
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1506
|
+
smb3_response.add_negotiate_context(nc)
|
1507
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1508
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1509
|
+
end
|
1510
|
+
end
|
552
1511
|
end
|
553
1512
|
end
|
554
1513
|
end
|
@@ -875,6 +1834,39 @@ RSpec.describe RubySMB::Client do
|
|
875
1834
|
smb2_client.smb2_authenticate
|
876
1835
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
877
1836
|
end
|
1837
|
+
|
1838
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1839
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1840
|
+
smb2_client.dialect = dialect
|
1841
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1842
|
+
smb2_client.smb2_authenticate
|
1843
|
+
end
|
1844
|
+
end
|
1845
|
+
|
1846
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1847
|
+
smb2_client.dialect = '0x0311'
|
1848
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1849
|
+
smb2_client.smb2_authenticate
|
1850
|
+
end
|
1851
|
+
|
1852
|
+
context 'when setting the session_encrypt_data parameter' do
|
1853
|
+
before :example do
|
1854
|
+
smb2_client.smb3 = true
|
1855
|
+
smb2_client.session_encrypt_data = false
|
1856
|
+
end
|
1857
|
+
|
1858
|
+
it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
|
1859
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1860
|
+
smb2_client.smb2_authenticate
|
1861
|
+
expect(smb2_client.session_encrypt_data).to be true
|
1862
|
+
end
|
1863
|
+
|
1864
|
+
it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
|
1865
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1866
|
+
smb2_client.smb2_authenticate
|
1867
|
+
expect(smb2_client.session_encrypt_data).to be false
|
1868
|
+
end
|
1869
|
+
end
|
878
1870
|
end
|
879
1871
|
|
880
1872
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -890,20 +1882,34 @@ RSpec.describe RubySMB::Client do
|
|
890
1882
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
891
1883
|
end
|
892
1884
|
|
893
|
-
it '
|
894
|
-
expect(smb2_client.smb2_ntlmssp_negotiate_packet.
|
895
|
-
end
|
896
|
-
|
897
|
-
it 'increments client#smb2_message_id' do
|
898
|
-
expect { smb2_client.smb2_ntlmssp_negotiate_packet }.to change(smb2_client, :smb2_message_id).to(2)
|
1885
|
+
it 'enables signing' do
|
1886
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
899
1887
|
end
|
900
1888
|
end
|
901
1889
|
|
902
1890
|
describe '#smb2_ntlmssp_negotiate' do
|
1891
|
+
before :example do
|
1892
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1893
|
+
allow(smb2_client).to receive(:send_recv)
|
1894
|
+
end
|
1895
|
+
|
903
1896
|
it 'sends the request packet and receives a response' do
|
904
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
905
|
-
expect(
|
906
|
-
|
1897
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1898
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1899
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1900
|
+
end
|
1901
|
+
|
1902
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1903
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1904
|
+
smb2_client.dialect = dialect
|
1905
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1906
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1907
|
+
end
|
1908
|
+
end
|
1909
|
+
|
1910
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1911
|
+
smb2_client.dialect = '0x0311'
|
1912
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
907
1913
|
smb2_client.smb2_ntlmssp_negotiate
|
908
1914
|
end
|
909
1915
|
end
|
@@ -961,13 +1967,35 @@ RSpec.describe RubySMB::Client do
|
|
961
1967
|
it 'sets the session ID on the request packet' do
|
962
1968
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
963
1969
|
end
|
1970
|
+
|
1971
|
+
it 'enables signing' do
|
1972
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1973
|
+
end
|
964
1974
|
end
|
965
1975
|
|
966
1976
|
describe '#smb2_ntlmssp_authenticate' do
|
1977
|
+
before :example do
|
1978
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1979
|
+
allow(smb2_client).to receive(:send_recv)
|
1980
|
+
end
|
1981
|
+
|
967
1982
|
it 'sends the request packet and receives a response' do
|
968
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
969
|
-
expect(
|
970
|
-
|
1983
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1984
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1985
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1986
|
+
end
|
1987
|
+
|
1988
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1989
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1990
|
+
smb2_client.dialect = dialect
|
1991
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1992
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1993
|
+
end
|
1994
|
+
end
|
1995
|
+
|
1996
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1997
|
+
smb2_client.dialect = '0x0311'
|
1998
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
971
1999
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
972
2000
|
end
|
973
2001
|
end
|
@@ -1103,6 +2131,108 @@ RSpec.describe RubySMB::Client do
|
|
1103
2131
|
end
|
1104
2132
|
end
|
1105
2133
|
end
|
2134
|
+
|
2135
|
+
describe '#smb3_sign' do
|
2136
|
+
context 'if signing is required and we have a session key' do
|
2137
|
+
let(:request) {
|
2138
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2139
|
+
packet.smb2_header.flags.signed = 1
|
2140
|
+
packet.smb2_header.signature = "\x00" * 16
|
2141
|
+
packet
|
2142
|
+
}
|
2143
|
+
let(:session_key) { 'Session Key' }
|
2144
|
+
before :example do
|
2145
|
+
smb3_client.session_key = session_key
|
2146
|
+
smb3_client.signing_required = true
|
2147
|
+
end
|
2148
|
+
|
2149
|
+
['0x0300', '0x0302'].each do |dialect|
|
2150
|
+
context "with #{dialect} dialect" do
|
2151
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2152
|
+
smb3_client.dialect = dialect
|
2153
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2154
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2155
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2156
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2157
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2158
|
+
end
|
2159
|
+
end
|
2160
|
+
end
|
2161
|
+
|
2162
|
+
context "with 0x0311 dialect" do
|
2163
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2164
|
+
smb3_client.dialect = '0x0311'
|
2165
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2166
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2167
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2168
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2169
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2170
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2171
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2172
|
+
end
|
2173
|
+
end
|
2174
|
+
|
2175
|
+
context 'with an incompatible dialect' do
|
2176
|
+
it 'raises the expected exception' do
|
2177
|
+
smb3_client.dialect = '0x0202'
|
2178
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2179
|
+
RubySMB::Error::SigningError,
|
2180
|
+
'Dialect is incompatible with SMBv3 signing'
|
2181
|
+
)
|
2182
|
+
end
|
2183
|
+
end
|
2184
|
+
end
|
2185
|
+
|
2186
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2187
|
+
let(:request) {
|
2188
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2189
|
+
packet.smb2_header.flags.signed = 1
|
2190
|
+
packet.smb2_header.signature = "\x00" * 16
|
2191
|
+
packet
|
2192
|
+
}
|
2193
|
+
let(:session_key) { 'Session Key' }
|
2194
|
+
before :example do
|
2195
|
+
smb3_client.session_key = session_key
|
2196
|
+
smb3_client.signing_required = false
|
2197
|
+
end
|
2198
|
+
|
2199
|
+
['0x0300', '0x0302'].each do |dialect|
|
2200
|
+
context "with #{dialect} dialect" do
|
2201
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2202
|
+
smb3_client.dialect = dialect
|
2203
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2204
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2205
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2206
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2207
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2208
|
+
end
|
2209
|
+
end
|
2210
|
+
end
|
2211
|
+
|
2212
|
+
context "with 0x0311 dialect" do
|
2213
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2214
|
+
smb3_client.dialect = '0x0311'
|
2215
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2216
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2217
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2218
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2219
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2220
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2221
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2222
|
+
end
|
2223
|
+
end
|
2224
|
+
|
2225
|
+
context 'with an incompatible dialect' do
|
2226
|
+
it 'raises the expected exception' do
|
2227
|
+
smb3_client.dialect = '0x0202'
|
2228
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2229
|
+
RubySMB::Error::SigningError,
|
2230
|
+
'Dialect is incompatible with SMBv3 signing'
|
2231
|
+
)
|
2232
|
+
end
|
2233
|
+
end
|
2234
|
+
end
|
2235
|
+
end
|
1106
2236
|
end
|
1107
2237
|
|
1108
2238
|
context '#increment_smb_message_id' do
|
@@ -1156,7 +2286,10 @@ RSpec.describe RubySMB::Client do
|
|
1156
2286
|
|
1157
2287
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1158
2288
|
response.smb_header.nt_status = 0xc0000015
|
1159
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2289
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2290
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2291
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2292
|
+
)
|
1160
2293
|
end
|
1161
2294
|
|
1162
2295
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1177,11 +2310,14 @@ RSpec.describe RubySMB::Client do
|
|
1177
2310
|
}
|
1178
2311
|
|
1179
2312
|
describe '#smb2_tree_connect' do
|
1180
|
-
it 'builds and sends
|
2313
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1181
2314
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
2315
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2316
|
+
expect(req).to eq(request)
|
2317
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2318
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2319
|
+
response.to_binary_s
|
2320
|
+
end
|
1185
2321
|
smb2_client.smb2_tree_connect(path)
|
1186
2322
|
end
|
1187
2323
|
|
@@ -1200,11 +2336,20 @@ RSpec.describe RubySMB::Client do
|
|
1200
2336
|
|
1201
2337
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1202
2338
|
response.smb2_header.nt_status = 0xc0000015
|
1203
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2339
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2340
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2341
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2342
|
+
)
|
1204
2343
|
end
|
1205
2344
|
|
1206
2345
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
1207
|
-
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response)
|
2346
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2347
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2348
|
+
end
|
2349
|
+
|
2350
|
+
it 'creates a new with encryption set if the response requires it' do
|
2351
|
+
response.share_flags.encrypt = 1
|
2352
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1208
2353
|
smb2_client.smb2_tree_from_response(path, response)
|
1209
2354
|
end
|
1210
2355
|
end
|
@@ -1214,7 +2359,7 @@ RSpec.describe RubySMB::Client do
|
|
1214
2359
|
let(:named_pipe){ double("Named Pipe") }
|
1215
2360
|
|
1216
2361
|
before :example do
|
1217
|
-
allow(tree).to receive(:
|
2362
|
+
allow(tree).to receive(:open_pipe).and_return(named_pipe)
|
1218
2363
|
allow(named_pipe).to receive(:net_share_enum_all)
|
1219
2364
|
end
|
1220
2365
|
|
@@ -1229,8 +2374,8 @@ RSpec.describe RubySMB::Client do
|
|
1229
2374
|
smb1_client.net_share_enum_all(sock.peeraddr)
|
1230
2375
|
end
|
1231
2376
|
|
1232
|
-
it 'it calls the Tree #
|
1233
|
-
expect(tree).to receive(:
|
2377
|
+
it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
|
2378
|
+
expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
|
1234
2379
|
smb1_client.net_share_enum_all(sock.peeraddr)
|
1235
2380
|
end
|
1236
2381
|
|
@@ -1252,8 +2397,8 @@ RSpec.describe RubySMB::Client do
|
|
1252
2397
|
smb2_client.net_share_enum_all(sock.peeraddr)
|
1253
2398
|
end
|
1254
2399
|
|
1255
|
-
it 'it calls the Tree #
|
1256
|
-
expect(tree).to receive(:
|
2400
|
+
it 'it calls the Tree #open_pipe method to open "srvsvc" named pipe' do
|
2401
|
+
expect(tree).to receive(:open_pipe).with(filename: "srvsvc", write: true, read: true).and_return(named_pipe)
|
1257
2402
|
smb2_client.net_share_enum_all(sock.peeraddr)
|
1258
2403
|
end
|
1259
2404
|
|
@@ -1301,7 +2446,7 @@ RSpec.describe RubySMB::Client do
|
|
1301
2446
|
end
|
1302
2447
|
|
1303
2448
|
it 'raise an InvalidPacket exception when the response is not valid' do
|
1304
|
-
echo_response.smb_header.command = RubySMB::SMB1::Commands::
|
2449
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
1305
2450
|
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1306
2451
|
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
1307
2452
|
end
|
@@ -1334,7 +2479,7 @@ RSpec.describe RubySMB::Client do
|
|
1334
2479
|
before :example do
|
1335
2480
|
allow(ipc_tree).to receive_messages(
|
1336
2481
|
:share => share,
|
1337
|
-
:
|
2482
|
+
:open_pipe => named_pipe
|
1338
2483
|
)
|
1339
2484
|
allow(client).to receive(:tree_connect).and_return(ipc_tree)
|
1340
2485
|
end
|
@@ -1356,9 +2501,9 @@ RSpec.describe RubySMB::Client do
|
|
1356
2501
|
expect(client).to have_received(:tree_connect).with(share)
|
1357
2502
|
end
|
1358
2503
|
|
1359
|
-
it 'open \'winreg\'
|
2504
|
+
it 'open \'winreg\' pipe on the IPC$ Tree' do
|
1360
2505
|
client.connect_to_winreg(host)
|
1361
|
-
expect(ipc_tree).to have_received(:
|
2506
|
+
expect(ipc_tree).to have_received(:open_pipe).with(filename: "winreg", write: true, read: true)
|
1362
2507
|
end
|
1363
2508
|
|
1364
2509
|
it 'returns the expected opened named pipe' do
|
@@ -1473,5 +2618,198 @@ RSpec.describe RubySMB::Client do
|
|
1473
2618
|
end
|
1474
2619
|
end
|
1475
2620
|
end
|
2621
|
+
|
2622
|
+
describe '#update_preauth_hash' do
|
2623
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2624
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2625
|
+
RubySMB::Error::EncryptionError,
|
2626
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2627
|
+
)
|
2628
|
+
end
|
2629
|
+
|
2630
|
+
it 'computes the hash value' do
|
2631
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2632
|
+
data = 'Previous hash'
|
2633
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2634
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2635
|
+
]
|
2636
|
+
client.preauth_integrity_hash_algorithm = algo
|
2637
|
+
client.preauth_integrity_hash_value = data
|
2638
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2639
|
+
client.update_preauth_hash(packet)
|
2640
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2641
|
+
end
|
2642
|
+
end
|
2643
|
+
|
2644
|
+
context 'Encryption' do
|
2645
|
+
describe '#smb3_encrypt' do
|
2646
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2647
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2648
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2649
|
+
|
2650
|
+
before :example do
|
2651
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2652
|
+
allow(transform_packet).to receive(:encrypt)
|
2653
|
+
client.session_key = session_key
|
2654
|
+
end
|
2655
|
+
|
2656
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2657
|
+
client.client_encryption_key = 'key'
|
2658
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2659
|
+
expect(client.client_encryption_key).to eq('key')
|
2660
|
+
client.smb3_encrypt(data)
|
2661
|
+
end
|
2662
|
+
|
2663
|
+
['0x0300', '0x0302'].each do |dialect|
|
2664
|
+
context "with #{dialect} dialect" do
|
2665
|
+
before :example do
|
2666
|
+
client.dialect = dialect
|
2667
|
+
end
|
2668
|
+
|
2669
|
+
it 'generates the client encryption key with the expected parameters' do
|
2670
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2671
|
+
session_key,
|
2672
|
+
"SMB2AESCCM\x00",
|
2673
|
+
"ServerIn \x00"
|
2674
|
+
).and_call_original
|
2675
|
+
client.smb3_encrypt(data)
|
2676
|
+
end
|
2677
|
+
end
|
2678
|
+
end
|
2679
|
+
|
2680
|
+
context 'with 0x0311 dialect' do
|
2681
|
+
it 'generates the client encryption key with the expected parameters' do
|
2682
|
+
client.preauth_integrity_hash_value = ''
|
2683
|
+
client.dialect = '0x0311'
|
2684
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2685
|
+
session_key,
|
2686
|
+
"SMBC2SCipherKey\x00",
|
2687
|
+
''
|
2688
|
+
).and_call_original
|
2689
|
+
client.smb3_encrypt(data)
|
2690
|
+
end
|
2691
|
+
end
|
2692
|
+
|
2693
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2694
|
+
client.dialect = '0x0202'
|
2695
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2696
|
+
end
|
2697
|
+
|
2698
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2699
|
+
client.dialect = '0x0300'
|
2700
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2701
|
+
client.session_id = 123
|
2702
|
+
client.smb3_encrypt(data)
|
2703
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2704
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2705
|
+
end
|
2706
|
+
|
2707
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2708
|
+
client.dialect = '0x0302'
|
2709
|
+
expected_enc_key =
|
2710
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2711
|
+
client.smb3_encrypt(data)
|
2712
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2713
|
+
end
|
2714
|
+
|
2715
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2716
|
+
client.dialect = '0x0311'
|
2717
|
+
client.session_key =
|
2718
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2719
|
+
client.preauth_integrity_hash_value =
|
2720
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2721
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2722
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2723
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2724
|
+
expected_enc_key =
|
2725
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2726
|
+
client.smb3_encrypt(data)
|
2727
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2728
|
+
end
|
2729
|
+
end
|
2730
|
+
|
2731
|
+
describe '#smb3_decrypt' do
|
2732
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2733
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2734
|
+
|
2735
|
+
before :example do
|
2736
|
+
allow(transform_packet).to receive(:decrypt)
|
2737
|
+
client.session_key = session_key
|
2738
|
+
end
|
2739
|
+
|
2740
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2741
|
+
client.server_encryption_key = 'key'
|
2742
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2743
|
+
expect(client.server_encryption_key).to eq('key')
|
2744
|
+
client.smb3_decrypt(transform_packet)
|
2745
|
+
end
|
2746
|
+
|
2747
|
+
['0x0300', '0x0302'].each do |dialect|
|
2748
|
+
context "with #{dialect} dialect" do
|
2749
|
+
before :example do
|
2750
|
+
client.dialect = dialect
|
2751
|
+
end
|
2752
|
+
|
2753
|
+
it 'generates the client encryption key with the expected parameters' do
|
2754
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2755
|
+
session_key,
|
2756
|
+
"SMB2AESCCM\x00",
|
2757
|
+
"ServerOut\x00"
|
2758
|
+
).and_call_original
|
2759
|
+
client.smb3_decrypt(transform_packet)
|
2760
|
+
end
|
2761
|
+
end
|
2762
|
+
end
|
2763
|
+
|
2764
|
+
context 'with 0x0311 dialect' do
|
2765
|
+
it 'generates the client encryption key with the expected parameters' do
|
2766
|
+
client.preauth_integrity_hash_value = ''
|
2767
|
+
client.dialect = '0x0311'
|
2768
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2769
|
+
session_key,
|
2770
|
+
"SMBS2CCipherKey\x00",
|
2771
|
+
''
|
2772
|
+
).and_call_original
|
2773
|
+
client.smb3_decrypt(transform_packet)
|
2774
|
+
end
|
2775
|
+
end
|
2776
|
+
|
2777
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2778
|
+
client.dialect = '0x0202'
|
2779
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2780
|
+
end
|
2781
|
+
|
2782
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2783
|
+
client.dialect = '0x0300'
|
2784
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2785
|
+
client.session_id = 123
|
2786
|
+
client.smb3_decrypt(transform_packet)
|
2787
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2788
|
+
end
|
2789
|
+
|
2790
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2791
|
+
client.dialect = '0x0302'
|
2792
|
+
expected_enc_key =
|
2793
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2794
|
+
client.smb3_decrypt(transform_packet)
|
2795
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2796
|
+
end
|
2797
|
+
|
2798
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2799
|
+
client.dialect = '0x0311'
|
2800
|
+
client.session_key =
|
2801
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2802
|
+
client.preauth_integrity_hash_value =
|
2803
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2804
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2805
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2806
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2807
|
+
expected_enc_key =
|
2808
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2809
|
+
client.smb3_decrypt(transform_packet)
|
2810
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2811
|
+
end
|
2812
|
+
end
|
2813
|
+
end
|
1476
2814
|
end
|
1477
2815
|
|