ruby_smb 1.0.5 → 2.0.3
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 -2
- data/Gemfile +6 -2
- data/README.md +35 -47
- 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 +29 -0
- data/examples/enum_registry_values.rb +31 -0
- 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 +13 -13
- 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 +33 -0
- 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 -1
- data/lib/ruby_smb/client.rb +239 -21
- data/lib/ruby_smb/client/authentication.rb +27 -8
- data/lib/ruby_smb/client/encryption.rb +62 -0
- data/lib/ruby_smb/client/negotiation.rb +154 -12
- data/lib/ruby_smb/client/signing.rb +19 -0
- data/lib/ruby_smb/client/tree_connect.rb +4 -4
- data/lib/ruby_smb/client/utils.rb +8 -7
- data/lib/ruby_smb/client/winreg.rb +46 -0
- data/lib/ruby_smb/crypto.rb +30 -0
- data/lib/ruby_smb/dcerpc.rb +40 -0
- data/lib/ruby_smb/dcerpc/bind.rb +2 -2
- data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
- data/lib/ruby_smb/dcerpc/error.rb +6 -0
- data/lib/ruby_smb/dcerpc/ndr.rb +260 -16
- data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
- data/lib/ruby_smb/dcerpc/request.rb +41 -9
- data/lib/ruby_smb/dcerpc/rpc_security_attributes.rb +34 -0
- data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +38 -0
- data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
- data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
- 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 +421 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
- data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
- 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 +45 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
- data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
- data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
- data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
- data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +40 -0
- data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
- data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
- 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 +28 -1
- data/lib/ruby_smb/field/stringz16.rb +17 -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 +8 -14
- 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 +81 -3
- data/lib/ruby_smb/smb1/tree.rb +12 -3
- 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 +51 -61
- 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/error_packet.rb +2 -4
- 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 +80 -3
- data/lib/ruby_smb/smb2/smb2_header.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +32 -20
- data/lib/ruby_smb/version.rb +1 -1
- data/ruby_smb.gemspec +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +1583 -102
- data/spec/lib/ruby_smb/crypto_spec.rb +25 -0
- data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
- data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +1729 -0
- data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
- data/spec/lib/ruby_smb/dcerpc/rpc_security_attributes_spec.rb +161 -0
- data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +135 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
- data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
- 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/close_key_request_spec.rb +28 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -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 +104 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +95 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +35 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +138 -0
- data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
- 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 +884 -0
- data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
- data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +12 -12
- data/spec/lib/ruby_smb/error_spec.rb +59 -0
- data/spec/lib/ruby_smb/field/stringz16_spec.rb +12 -0
- data/spec/lib/ruby_smb/nbss/session_header_spec.rb +4 -11
- data/spec/lib/ruby_smb/smb1/file_spec.rb +9 -1
- 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 +216 -147
- 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 +146 -68
- 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/error_packet_spec.rb +3 -24
- 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 +226 -148
- data/spec/lib/ruby_smb/smb2/smb2_header_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +88 -9
- metadata +257 -81
- metadata.gz.sig +0 -0
- data/lib/ruby_smb/smb1/dcerpc.rb +0 -72
- data/lib/ruby_smb/smb2/dcerpc.rb +0 -75
@@ -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
@@ -3,13 +3,26 @@ module RubySMB
|
|
3
3
|
# Represents a pipe on the Remote server that we can perform
|
4
4
|
# various I/O operations on.
|
5
5
|
class Pipe < File
|
6
|
-
require 'ruby_smb/
|
6
|
+
require 'ruby_smb/dcerpc'
|
7
7
|
|
8
|
-
include RubySMB::
|
8
|
+
include RubySMB::Dcerpc
|
9
9
|
|
10
10
|
STATUS_CONNECTED = 0x00000003
|
11
11
|
STATUS_CLOSING = 0x00000004
|
12
12
|
|
13
|
+
def initialize(tree:, response:, name:)
|
14
|
+
raise ArgumentError, 'No Name Provided' if name.nil?
|
15
|
+
case name
|
16
|
+
when 'srvsvc'
|
17
|
+
extend RubySMB::Dcerpc::Srvsvc
|
18
|
+
when 'winreg'
|
19
|
+
extend RubySMB::Dcerpc::Winreg
|
20
|
+
when 'svcctl'
|
21
|
+
extend RubySMB::Dcerpc::Svcctl
|
22
|
+
end
|
23
|
+
super(tree: tree, response: response, name: name)
|
24
|
+
end
|
25
|
+
|
13
26
|
# Performs a peek operation on the named pipe
|
14
27
|
#
|
15
28
|
# @param peek_size [Integer] Amount of data to peek
|
@@ -35,7 +48,7 @@ module RubySMB
|
|
35
48
|
end
|
36
49
|
|
37
50
|
unless response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW or response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
38
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
51
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
39
52
|
end
|
40
53
|
response
|
41
54
|
end
|
@@ -67,6 +80,70 @@ module RubySMB
|
|
67
80
|
state == STATUS_CONNECTED
|
68
81
|
end
|
69
82
|
|
83
|
+
def dcerpc_request(stub_packet, options={})
|
84
|
+
options.merge!(endpoint: stub_packet.class.name.split('::').at(-2))
|
85
|
+
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: stub_packet.opnum }, options)
|
86
|
+
dcerpc_request.stub.read(stub_packet.to_binary_s)
|
87
|
+
ioctl_send_recv(dcerpc_request, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def ioctl_send_recv(action, options={})
|
91
|
+
request = set_header_fields(RubySMB::SMB2::Packet::IoctlRequest.new(options))
|
92
|
+
request.ctl_code = 0x0011C017
|
93
|
+
request.flags.is_fsctl = 0x00000001
|
94
|
+
# TODO: handle fragmentation when the request size > MAX_XMIT_FRAG
|
95
|
+
request.buffer = action.to_binary_s
|
96
|
+
|
97
|
+
ioctl_raw_response = @tree.client.send_recv(request)
|
98
|
+
ioctl_response = RubySMB::SMB2::Packet::IoctlResponse.read(ioctl_raw_response)
|
99
|
+
unless ioctl_response.valid?
|
100
|
+
raise RubySMB::Error::InvalidPacket.new(
|
101
|
+
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
102
|
+
expected_cmd: RubySMB::SMB2::Packet::IoctlRequest::COMMAND,
|
103
|
+
received_proto: ioctl_response.smb2_header.protocol,
|
104
|
+
received_cmd: ioctl_response.smb2_header.command
|
105
|
+
)
|
106
|
+
end
|
107
|
+
unless [WindowsError::NTStatus::STATUS_SUCCESS,
|
108
|
+
WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW].include?(ioctl_response.status_code)
|
109
|
+
raise RubySMB::Error::UnexpectedStatusCode, ioctl_response.status_code
|
110
|
+
end
|
111
|
+
|
112
|
+
raw_data = ioctl_response.output_data
|
113
|
+
if ioctl_response.status_code == WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW
|
114
|
+
raw_data << read(bytes: @tree.client.max_buffer_size - ioctl_response.output_count)
|
115
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
116
|
+
unless dcerpc_response.pdu_header.pfc_flags.first_frag == 1
|
117
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not the first fragment"
|
118
|
+
end
|
119
|
+
stub_data = dcerpc_response.stub.to_s
|
120
|
+
|
121
|
+
loop do
|
122
|
+
break if dcerpc_response.pdu_header.pfc_flags.last_frag == 1
|
123
|
+
raw_data = read(bytes: @tree.client.max_buffer_size)
|
124
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
125
|
+
stub_data << dcerpc_response.stub.to_s
|
126
|
+
end
|
127
|
+
stub_data
|
128
|
+
else
|
129
|
+
dcerpc_response = dcerpc_response_from_raw_response(raw_data)
|
130
|
+
dcerpc_response.stub.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def dcerpc_response_from_raw_response(raw_data)
|
138
|
+
dcerpc_response = RubySMB::Dcerpc::Response.read(raw_data)
|
139
|
+
unless dcerpc_response.pdu_header.ptype == RubySMB::Dcerpc::PTypes::RESPONSE
|
140
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Not a Response packet"
|
141
|
+
end
|
142
|
+
dcerpc_response
|
143
|
+
rescue IOError
|
144
|
+
raise RubySMB::Dcerpc::Error::InvalidPacket, "Error reading the DCERPC response"
|
145
|
+
end
|
146
|
+
|
70
147
|
end
|
71
148
|
end
|
72
149
|
end
|
@@ -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,7 +44,7 @@ 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(
|
@@ -96,7 +102,7 @@ module RubySMB
|
|
96
102
|
create_request.create_disposition = disposition
|
97
103
|
create_request.name = filename
|
98
104
|
|
99
|
-
raw_response = client.send_recv(create_request)
|
105
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
100
106
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
101
107
|
unless response.valid?
|
102
108
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -107,15 +113,15 @@ module RubySMB
|
|
107
113
|
)
|
108
114
|
end
|
109
115
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
110
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
116
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
111
117
|
end
|
112
118
|
|
113
119
|
case @share_type
|
114
|
-
when
|
115
|
-
RubySMB::SMB2::File.new(name: filename, tree: self, response: response)
|
116
|
-
when
|
120
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_DISK
|
121
|
+
RubySMB::SMB2::File.new(name: filename, tree: self, response: response, encrypt: @tree_connect_encrypt_data)
|
122
|
+
when RubySMB::SMB2::Packet::TreeConnectResponse::SMB2_SHARE_TYPE_PIPE
|
117
123
|
RubySMB::SMB2::Pipe.new(name: filename, tree: self, response: response)
|
118
|
-
# when
|
124
|
+
# when RubySMB::SMB2::TreeConnectResponse::SMB2_SHARE_TYPE_PRINT
|
119
125
|
# it's a printer!
|
120
126
|
else
|
121
127
|
raise RubySMB::Error::RubySMBError, 'Unsupported share type'
|
@@ -141,14 +147,21 @@ module RubySMB
|
|
141
147
|
directory_request.file_information_class = type::CLASS_LEVEL
|
142
148
|
directory_request.file_id = file_id
|
143
149
|
directory_request.name = pattern
|
144
|
-
|
150
|
+
|
151
|
+
max_read = client.server_max_read_size
|
152
|
+
max_read = 65536 unless client.server_supports_multi_credit
|
153
|
+
credit_charge = 0
|
154
|
+
if client.server_supports_multi_credit
|
155
|
+
credit_charge = (max_read - 1) / 65536 + 1
|
156
|
+
end
|
157
|
+
directory_request.output_length = max_read
|
158
|
+
directory_request.smb2_header.credit_charge = credit_charge
|
145
159
|
|
146
160
|
directory_request = set_header_fields(directory_request)
|
147
161
|
|
148
162
|
files = []
|
149
|
-
|
150
163
|
loop do
|
151
|
-
response = client.send_recv(directory_request)
|
164
|
+
response = client.send_recv(directory_request, encrypt: @tree_connect_encrypt_data)
|
152
165
|
directory_response = RubySMB::SMB2::Packet::QueryDirectoryResponse.read(response)
|
153
166
|
unless directory_response.valid?
|
154
167
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -164,7 +177,7 @@ module RubySMB
|
|
164
177
|
break if status_code == WindowsError::NTStatus::STATUS_NO_MORE_FILES
|
165
178
|
|
166
179
|
unless status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
167
|
-
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
180
|
+
raise RubySMB::Error::UnexpectedStatusCode, status_code
|
168
181
|
end
|
169
182
|
|
170
183
|
files += directory_response.results(type)
|
@@ -193,7 +206,7 @@ module RubySMB
|
|
193
206
|
|
194
207
|
create_request = open_directory_packet(directory: directory, disposition: disposition,
|
195
208
|
impersonation: impersonation, read: read, write: write, delete: delete)
|
196
|
-
raw_response = client.send_recv(create_request)
|
209
|
+
raw_response = client.send_recv(create_request, encrypt: @tree_connect_encrypt_data)
|
197
210
|
response = RubySMB::SMB2::Packet::CreateResponse.read(raw_response)
|
198
211
|
unless response.valid?
|
199
212
|
raise RubySMB::Error::InvalidPacket.new(
|
@@ -204,7 +217,7 @@ module RubySMB
|
|
204
217
|
)
|
205
218
|
end
|
206
219
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
207
|
-
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
220
|
+
raise RubySMB::Error::UnexpectedStatusCode, response.status_code
|
208
221
|
end
|
209
222
|
|
210
223
|
response
|
@@ -250,7 +263,6 @@ module RubySMB
|
|
250
263
|
# @return [RubySMB::SMB2::Packet] the modified packet.
|
251
264
|
def set_header_fields(request)
|
252
265
|
request.smb2_header.tree_id = id
|
253
|
-
request.smb2_header.credit_charge = 1
|
254
266
|
request.smb2_header.credits = 256
|
255
267
|
request
|
256
268
|
end
|
data/lib/ruby_smb/version.rb
CHANGED
data/ruby_smb.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'ruby_smb/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'ruby_smb'
|
8
8
|
spec.version = RubySMB::VERSION
|
9
|
-
spec.authors = ['David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
-
spec.email = ['
|
9
|
+
spec.authors = ['Metasploit Hackers', 'David Maloney', 'James Lee', 'Dev Mohanty', 'Christophe De La Fuente']
|
10
|
+
spec.email = ['msfdev@metasploit.com']
|
11
11
|
spec.summary = 'A pure Ruby implementation of the SMB Protocol Family'
|
12
12
|
spec.description = ''
|
13
13
|
spec.homepage = 'https://github.com/rapid7/ruby_smb'
|
@@ -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,362 @@ 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 #msb2_message_id with SMB2 header #credit_charge if the dialect is not 0x0202' do
|
336
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
337
|
+
smb2_client.smb2_message_id = 0
|
338
|
+
smb2_client.dialect = '0x0210'
|
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 dialect is 0x0202' do
|
345
|
+
allow(smb2_client).to receive(:is_status_pending?).and_return(false)
|
346
|
+
smb2_client.smb2_message_id = 0
|
347
|
+
smb2_client.dialect = '0x0202'
|
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
|
+
end
|
353
|
+
|
354
|
+
describe '#is_status_pending?' do
|
355
|
+
let(:response) {
|
356
|
+
res = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
357
|
+
res.smb2_header.nt_status= WindowsError::NTStatus::STATUS_PENDING.value
|
358
|
+
res.smb2_header.flags.async_command = 1
|
359
|
+
res
|
360
|
+
}
|
361
|
+
|
362
|
+
it 'returns true when the response has a STATUS_PENDING status code and the async_command flag set' do
|
363
|
+
expect(client.is_status_pending?(response.smb2_header)).to be true
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'returns false when the response has a STATUS_PENDING status code and the async_command flag not set' do
|
367
|
+
response.smb2_header.flags.async_command = 0
|
368
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'returns false when the response has no STATUS_PENDING status code but the async_command flag set' do
|
372
|
+
response.smb2_header.nt_status= WindowsError::NTStatus::STATUS_SUCCESS.value
|
373
|
+
expect(client.is_status_pending?(response.smb2_header)).to be false
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe '#can_be_encrypted?' do
|
378
|
+
it 'returns true if the packet can be encrypted' do
|
379
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
380
|
+
expect(client.can_be_encrypted?(packet)).to be true
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'returns false if it is an SMB1 packet' do
|
384
|
+
packet = RubySMB::SMB1::Packet::LogoffRequest.new
|
385
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
386
|
+
end
|
387
|
+
|
388
|
+
[RubySMB::SMB2::Packet::SessionSetupRequest, RubySMB::SMB2::Packet::NegotiateRequest].each do |klass|
|
389
|
+
it "returns false if the packet is a #{klass}" do
|
390
|
+
packet = klass.new
|
391
|
+
expect(client.can_be_encrypted?(packet)).to be false
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe '#encryption_supported?' do
|
397
|
+
['0x0300', '0x0302', '0x0311'].each do |dialect|
|
398
|
+
it "returns true if the dialect is #{dialect}" do
|
399
|
+
client.dialect = dialect
|
400
|
+
expect(client.encryption_supported?).to be true
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
it "returns false if the dialect does not support encryption" do
|
405
|
+
client.dialect = '0x0202'
|
406
|
+
expect(client.encryption_supported?).to be false
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe '#send_packet' do
|
411
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
412
|
+
before :example do
|
413
|
+
allow(dispatcher).to receive(:send_packet)
|
414
|
+
client.dialect = '0x0300'
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'does not encrypt the packet' do
|
418
|
+
expect(client).to_not receive(:smb3_encrypt)
|
419
|
+
client.send_packet(packet)
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'sends the packet through the dispatcher' do
|
423
|
+
client.send_packet(packet)
|
424
|
+
expect(dispatcher).to have_received(:send_packet).with(packet)
|
425
|
+
end
|
426
|
+
|
427
|
+
context 'with encryption' do
|
428
|
+
it 'creates a Transform request' do
|
429
|
+
expect(client).to receive(:smb3_encrypt).with(packet.to_binary_s)
|
430
|
+
client.send_packet(packet, encrypt: true)
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'raises an EncryptionError exception if an error occurs while encrypting' do
|
434
|
+
allow(client).to receive(:smb3_encrypt).and_raise(RubySMB::Error::RubySMBError.new('Error'))
|
435
|
+
expect { client.send_packet(packet, encrypt: true) }.to raise_error(
|
436
|
+
RubySMB::Error::EncryptionError,
|
437
|
+
"Error while encrypting #{packet.class.name} packet (SMB 0x0300): Error"
|
438
|
+
)
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'sends the encrypted packet' do
|
442
|
+
encrypted_packet = double('Encrypted packet')
|
443
|
+
allow(client).to receive(:smb3_encrypt).and_return(encrypted_packet)
|
444
|
+
client.send_packet(packet, encrypt: true)
|
445
|
+
expect(dispatcher).to have_received(:send_packet).with(encrypted_packet)
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
describe '#recv_packet' do
|
451
|
+
let(:packet) { RubySMB::SMB2::Packet::SessionSetupRequest.new }
|
452
|
+
before :example do
|
453
|
+
allow(dispatcher).to receive(:recv_packet).and_return(packet.to_binary_s)
|
454
|
+
client.dialect = '0x0300'
|
455
|
+
allow(client).to receive(:smb3_decrypt)
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'reads the response packet' do
|
459
|
+
client.recv_packet
|
460
|
+
expect(dispatcher).to have_received(:recv_packet)
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'raises an CommunicationError exception if an error occurs while receiving the response' do
|
464
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
465
|
+
expect { client.recv_packet }.to raise_error(RubySMB::Error::CommunicationError)
|
466
|
+
end
|
467
|
+
|
468
|
+
context 'with encryption' do
|
469
|
+
it 'raises an EncryptionError exception if an error occurs while receiving the response' do
|
470
|
+
allow(dispatcher).to receive(:recv_packet).and_raise(RubySMB::Error::CommunicationError)
|
471
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
472
|
+
RubySMB::Error::EncryptionError,
|
473
|
+
'Communication error with the remote host: RubySMB::Error::CommunicationError. '\
|
474
|
+
'The server supports encryption but was not able to handle the encrypted request.'
|
475
|
+
)
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'parses the response as a Transform response packet' do
|
479
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).with(packet.to_binary_s)
|
480
|
+
client.recv_packet(encrypt: true)
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'raises an InvalidPacket exception if an error occurs while parsing the response' do
|
484
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_raise(IOError)
|
485
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(RubySMB::Error::InvalidPacket, 'Not a SMB2 TransformHeader packet')
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'decrypts the Transform response packet' do
|
489
|
+
transform = double('Transform header packet')
|
490
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:read).and_return(transform)
|
491
|
+
client.recv_packet(encrypt: true)
|
492
|
+
expect(client).to have_received(:smb3_decrypt).with(transform)
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'raises an EncryptionError exception if an error occurs while decrypting' do
|
496
|
+
allow(client).to receive(:smb3_decrypt).and_raise(RubySMB::Error::RubySMBError)
|
497
|
+
expect { client.recv_packet(encrypt: true) }.to raise_error(
|
498
|
+
RubySMB::Error::EncryptionError,
|
499
|
+
'Error while decrypting RubySMB::SMB2::Packet::TransformHeader packet (SMB 0x0300}): RubySMB::Error::RubySMBError'
|
500
|
+
)
|
501
|
+
end
|
101
502
|
end
|
102
503
|
end
|
103
504
|
|
@@ -240,6 +641,44 @@ RSpec.describe RubySMB::Client do
|
|
240
641
|
expect {smb2_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
241
642
|
end
|
242
643
|
end
|
644
|
+
|
645
|
+
context 'with SMB3' do
|
646
|
+
let(:raw_response) { double('Raw response') }
|
647
|
+
let(:logoff_response) {
|
648
|
+
RubySMB::SMB2::Packet::LogoffResponse.new(smb_header: {:command => RubySMB::SMB2::Commands::LOGOFF} )
|
649
|
+
}
|
650
|
+
before :example do
|
651
|
+
allow(smb3_client).to receive(:send_recv).and_return(raw_response)
|
652
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
653
|
+
allow(smb3_client).to receive(:wipe_state!)
|
654
|
+
end
|
655
|
+
|
656
|
+
it 'creates a LogoffRequest packet' do
|
657
|
+
expect(RubySMB::SMB2::Packet::LogoffRequest).to receive(:new).and_call_original
|
658
|
+
smb3_client.logoff!
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'calls #send_recv' do
|
662
|
+
expect(smb3_client).to receive(:send_recv)
|
663
|
+
smb3_client.logoff!
|
664
|
+
end
|
665
|
+
|
666
|
+
it 'reads the raw response as a LogoffResponse packet' do
|
667
|
+
expect(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).with(raw_response)
|
668
|
+
smb3_client.logoff!
|
669
|
+
end
|
670
|
+
|
671
|
+
it 'raise an InvalidPacket exception when the response is an error packet' do
|
672
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(RubySMB::SMB2::Packet::ErrorPacket.new)
|
673
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
674
|
+
end
|
675
|
+
|
676
|
+
it 'raise an InvalidPacket exception when the response is not a LOGOFF command' do
|
677
|
+
logoff_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
678
|
+
allow(RubySMB::SMB2::Packet::LogoffResponse).to receive(:read).and_return(logoff_response)
|
679
|
+
expect {smb3_client.logoff!}.to raise_error(RubySMB::Error::InvalidPacket)
|
680
|
+
end
|
681
|
+
end
|
243
682
|
end
|
244
683
|
|
245
684
|
context 'NetBIOS Session Service' do
|
@@ -310,7 +749,7 @@ RSpec.describe RubySMB::Client do
|
|
310
749
|
expect(session_packet.session_header.session_packet_type).to eq RubySMB::Nbss::SESSION_REQUEST
|
311
750
|
expect(session_packet.called_name).to eq called_name
|
312
751
|
expect(session_packet.calling_name).to eq calling_name
|
313
|
-
expect(session_packet.session_header.
|
752
|
+
expect(session_packet.session_header.stream_protocol_length).to eq(
|
314
753
|
session_packet.called_name.to_binary_s.size + session_packet.calling_name.to_binary_s.size
|
315
754
|
)
|
316
755
|
end
|
@@ -365,7 +804,8 @@ RSpec.describe RubySMB::Client do
|
|
365
804
|
smb1_extended_response.to_binary_s
|
366
805
|
}
|
367
806
|
|
368
|
-
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new }
|
807
|
+
let(:smb2_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x200) }
|
808
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x300) }
|
369
809
|
|
370
810
|
describe '#smb1_negotiate_request' do
|
371
811
|
it 'returns an SMB1 Negotiate Request packet' do
|
@@ -373,33 +813,158 @@ RSpec.describe RubySMB::Client do
|
|
373
813
|
end
|
374
814
|
|
375
815
|
it 'sets the default SMB1 Dialect' do
|
376
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
816
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
817
|
+
buffer_format: 2,
|
818
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
819
|
+
)
|
377
820
|
end
|
378
821
|
|
379
822
|
it 'sets the SMB2.02 dialect if SMB2 support is enabled' do
|
380
|
-
expect(client.smb1_negotiate_request.dialects).to include(
|
823
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
824
|
+
buffer_format: 2,
|
825
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
826
|
+
)
|
381
827
|
end
|
382
828
|
|
383
829
|
it 'excludes the SMB2.02 Dialect if SMB2 support is disabled' do
|
384
|
-
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
830
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
831
|
+
buffer_format: 2,
|
832
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_DEFAULT
|
833
|
+
)
|
385
834
|
end
|
386
835
|
|
387
836
|
it 'excludes the default SMB1 Dialect if SMB1 support is disabled' do
|
388
|
-
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
837
|
+
expect(smb2_client.smb1_negotiate_request.dialects).to_not include(
|
838
|
+
buffer_format: 2,
|
839
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT
|
840
|
+
)
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'sets the SMB wildcard dialect if SMB2 support is enabled' do
|
844
|
+
expect(client.smb1_negotiate_request.dialects).to include(
|
845
|
+
buffer_format: 2,
|
846
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
847
|
+
)
|
848
|
+
end
|
849
|
+
|
850
|
+
it 'sets the SMB wildcard dialect if SMB3 support is enabled' do
|
851
|
+
expect(smb3_client.smb1_negotiate_request.dialects).to include(
|
852
|
+
buffer_format: 2,
|
853
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
854
|
+
)
|
855
|
+
end
|
856
|
+
|
857
|
+
it 'excludes the SMB wildcard dialect if both SMB2 and SMB3 supports are disabled' do
|
858
|
+
expect(smb1_client.smb1_negotiate_request.dialects).to_not include(
|
859
|
+
buffer_format: 2,
|
860
|
+
dialect_string: RubySMB::Client::SMB1_DIALECT_SMB2_WILDCARD
|
861
|
+
)
|
389
862
|
end
|
390
863
|
end
|
391
864
|
|
392
|
-
describe '#
|
865
|
+
describe '#smb2_3_negotiate_request' do
|
393
866
|
it 'return an SMB2 Negotiate Request packet' do
|
394
|
-
expect(client.
|
867
|
+
expect(client.smb2_3_negotiate_request).to be_a(RubySMB::SMB2::Packet::NegotiateRequest)
|
395
868
|
end
|
396
869
|
|
397
|
-
it 'sets the default SMB2 Dialect' do
|
398
|
-
expect(client.
|
870
|
+
it 'sets the default SMB2 Dialect if SMB2 support is enabled' do
|
871
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
872
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
873
|
+
)
|
874
|
+
end
|
875
|
+
|
876
|
+
it 'does not set the default SMB2 Dialect if SMB2 support is disabled' do
|
877
|
+
expect(smb3_client.smb2_3_negotiate_request.dialects).to_not include(
|
878
|
+
*(RubySMB::Client::SMB2_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
879
|
+
)
|
399
880
|
end
|
400
881
|
|
401
882
|
it 'sets the Message ID to 0' do
|
402
|
-
expect(client.
|
883
|
+
expect(client.smb2_3_negotiate_request.smb2_header.message_id).to eq 0
|
884
|
+
end
|
885
|
+
|
886
|
+
it 'adds SMB3 dialects if if SMB3 support is enabled' do
|
887
|
+
expect(client.smb2_3_negotiate_request.dialects).to include(
|
888
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
889
|
+
)
|
890
|
+
end
|
891
|
+
|
892
|
+
it 'does not set the default SMB3 Dialect if SMB3 support is disabled' do
|
893
|
+
expect(smb2_client.smb2_3_negotiate_request.dialects).to_not include(
|
894
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
895
|
+
)
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
describe '#add_smb3_to_negotiate_request' do
|
900
|
+
let(:negotiate_request) { RubySMB::SMB2::Packet::NegotiateRequest.new }
|
901
|
+
|
902
|
+
it 'adds the default SMB3 dialects' do
|
903
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).dialects).to include(
|
904
|
+
*(RubySMB::Client::SMB3_DIALECT_DEFAULT.map {|d| d.to_i(16)})
|
905
|
+
)
|
906
|
+
end
|
907
|
+
|
908
|
+
it 'raises the expected exception when the dialects is not an array of strings' do
|
909
|
+
dialects = ['0x0300', 0x0302, '0x0311']
|
910
|
+
expect { client.add_smb3_to_negotiate_request(negotiate_request, dialects) }.to raise_error(ArgumentError)
|
911
|
+
end
|
912
|
+
|
913
|
+
it 'sets encryption capability flag' do
|
914
|
+
expect(client.add_smb3_to_negotiate_request(negotiate_request).capabilities.encryption).to eq(1)
|
915
|
+
end
|
916
|
+
|
917
|
+
context 'when the negotiate packet includes the 0x0311 dialect' do
|
918
|
+
before :example do
|
919
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0311'])
|
920
|
+
end
|
921
|
+
|
922
|
+
it 'adds 3 Negotiate Contexts' do
|
923
|
+
expect(negotiate_request.negotiate_context_info.negotiate_context_count).to eq(3)
|
924
|
+
end
|
925
|
+
|
926
|
+
it 'adds a Preauth Integrity Negotiate Context with the expected hash algorithms' do
|
927
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
928
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
929
|
+
end
|
930
|
+
expect(nc.length).to eq(1)
|
931
|
+
expect(nc.first.data.hash_algorithms).to eq([RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512])
|
932
|
+
end
|
933
|
+
|
934
|
+
it 'adds Encryption Negotiate Contexts with the expected encryption algorithms' do
|
935
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
936
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
937
|
+
end
|
938
|
+
expect(nc.length).to eq(1)
|
939
|
+
expect(nc.first.data.ciphers).to eq(
|
940
|
+
[
|
941
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM,
|
942
|
+
RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
943
|
+
]
|
944
|
+
)
|
945
|
+
end
|
946
|
+
|
947
|
+
it 'adds Compression Negotiate Contexts with the expected compression algorithms' do
|
948
|
+
nc = negotiate_request.negotiate_context_list.select do |n|
|
949
|
+
n.context_type == RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
950
|
+
end
|
951
|
+
expect(nc.length).to eq(1)
|
952
|
+
expect(nc.first.data.compression_algorithms).to eq(
|
953
|
+
[
|
954
|
+
RubySMB::SMB2::CompressionCapabilities::LZNT1,
|
955
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77,
|
956
|
+
RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman,
|
957
|
+
RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
958
|
+
]
|
959
|
+
)
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
context 'when the negotiate packet does not include the 0x0311 dialect' do
|
964
|
+
it 'does not add any Negotiate Context' do
|
965
|
+
client.add_smb3_to_negotiate_request(negotiate_request, ['0x0300', '0x0302'])
|
966
|
+
expect(negotiate_request.negotiate_context_list?). to be false
|
967
|
+
end
|
403
968
|
end
|
404
969
|
end
|
405
970
|
|
@@ -414,10 +979,15 @@ RSpec.describe RubySMB::Client do
|
|
414
979
|
client.negotiate_request
|
415
980
|
end
|
416
981
|
|
417
|
-
it 'calls #
|
418
|
-
expect(smb2_client).to receive(:
|
982
|
+
it 'calls #smb2_3_negotiate_request if SMB2 is enabled' do
|
983
|
+
expect(smb2_client).to receive(:smb2_3_negotiate_request)
|
419
984
|
smb2_client.negotiate_request
|
420
985
|
end
|
986
|
+
|
987
|
+
it 'calls #smb2_3_negotiate_request if SMB3 is enabled' do
|
988
|
+
expect(smb3_client).to receive(:smb2_3_negotiate_request)
|
989
|
+
smb3_client.negotiate_request
|
990
|
+
end
|
421
991
|
end
|
422
992
|
|
423
993
|
describe '#negotiate_response' do
|
@@ -464,12 +1034,28 @@ RSpec.describe RubySMB::Client do
|
|
464
1034
|
end
|
465
1035
|
end
|
466
1036
|
|
467
|
-
context 'with
|
1037
|
+
context 'with only SMB3' do
|
1038
|
+
it 'returns a properly formed packet' do
|
1039
|
+
expect(smb3_client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
it 'raises an exception if the Response is invalid' do
|
1043
|
+
expect { smb3_client.negotiate_response(random_junk) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
it 'considers the response invalid if it is not an actual Negotiate Response' do
|
1047
|
+
bogus_response = smb2_response
|
1048
|
+
bogus_response.smb2_header.command = RubySMB::SMB2::Commands::ECHO
|
1049
|
+
expect { smb3_client.negotiate_response(bogus_response.to_binary_s) }.to raise_error(RubySMB::Error::InvalidPacket)
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
context 'with SMB1, SMB2 and SMB3 enabled' do
|
468
1054
|
it 'returns an SMB1 NegotiateResponse if it looks like SMB1' do
|
469
1055
|
expect(client.negotiate_response(smb1_extended_response_raw)).to eq smb1_extended_response
|
470
1056
|
end
|
471
1057
|
|
472
|
-
it 'returns an SMB2 NegotiateResponse if it looks like SMB2' do
|
1058
|
+
it 'returns an SMB2 NegotiateResponse if it looks like SMB2 or SMB3' do
|
473
1059
|
expect(client.negotiate_response(smb2_response.to_binary_s)).to eq smb2_response
|
474
1060
|
end
|
475
1061
|
end
|
@@ -477,78 +1063,446 @@ RSpec.describe RubySMB::Client do
|
|
477
1063
|
|
478
1064
|
describe '#parse_negotiate_response' do
|
479
1065
|
context 'when SMB1 was Negotiated' do
|
480
|
-
it 'turns off SMB2 support' do
|
1066
|
+
it 'turns off SMB2 and SMB3 support' do
|
481
1067
|
client.parse_negotiate_response(smb1_extended_response)
|
482
1068
|
expect(client.smb2).to be false
|
1069
|
+
expect(client.smb3).to be false
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
it 'sets whether or not signing is required' do
|
1073
|
+
smb1_extended_response.parameter_block.security_mode.security_signatures_required = 1
|
1074
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1075
|
+
expect(client.signing_required).to be true
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
it 'sets #dialect to the negotiated dialect' do
|
1079
|
+
smb1_extended_response.dialects = [
|
1080
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'A'),
|
1081
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'B'),
|
1082
|
+
RubySMB::SMB1::Dialect.new(dialect_string: 'C'),
|
1083
|
+
]
|
1084
|
+
smb1_extended_response.parameter_block.dialect_index = 1
|
1085
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1086
|
+
expect(client.dialect).to eq 'B'
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
it 'returns the string \'SMB1\'' do
|
1090
|
+
expect(client.parse_negotiate_response(smb1_extended_response)).to eq ('SMB1')
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
it 'sets #negotiated_smb_version to 1' do
|
1094
|
+
client.parse_negotiate_response(smb1_extended_response)
|
1095
|
+
expect(client.negotiated_smb_version).to eq(1)
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
|
1099
|
+
context 'when SMB2 was negotiated' do
|
1100
|
+
it 'turns off SMB1 and SMB3 support' do
|
1101
|
+
client.parse_negotiate_response(smb2_response)
|
1102
|
+
expect(client.smb1).to be false
|
1103
|
+
expect(client.smb3).to be false
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
it 'sets whether or not signing is required' do
|
1107
|
+
smb2_response.security_mode.signing_required = 1
|
1108
|
+
client.parse_negotiate_response(smb2_response)
|
1109
|
+
expect(client.signing_required).to be true
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
it 'sets #dialect to the negotiated dialect' do
|
1113
|
+
smb2_response.dialect_revision = 2
|
1114
|
+
client.parse_negotiate_response(smb2_response)
|
1115
|
+
expect(client.dialect).to eq '0x0002'
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
it 'returns the string \'SMB2\'' do
|
1119
|
+
expect(client.parse_negotiate_response(smb2_response)).to eq ('SMB2')
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1123
|
+
smb2_response.capabilities.large_mtu = 1
|
1124
|
+
client.parse_negotiate_response(smb2_response)
|
1125
|
+
expect(client.server_supports_multi_credit).to be true
|
1126
|
+
end
|
1127
|
+
|
1128
|
+
it 'sets #server_supports_multi_credit to false when the dialect is 0x0202' do
|
1129
|
+
smb2_response.dialect_revision = 0x0202
|
1130
|
+
smb2_response.capabilities.large_mtu = 1 # just to make sure it won't affect the result
|
1131
|
+
client.parse_negotiate_response(smb2_response)
|
1132
|
+
expect(client.server_supports_multi_credit).to be false
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
context 'when SMB3 was negotiated' do
|
1137
|
+
it 'turns off SMB1 and SMB2 support' do
|
1138
|
+
client.parse_negotiate_response(smb3_response)
|
1139
|
+
expect(client.smb1).to be false
|
1140
|
+
expect(client.smb2).to be false
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
it 'sets whether or not signing is required' do
|
1144
|
+
smb3_response.security_mode.signing_required = 1
|
1145
|
+
client.parse_negotiate_response(smb3_response)
|
1146
|
+
expect(client.signing_required).to be true
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
it 'sets #dialect to the negotiated dialect' do
|
1150
|
+
client.parse_negotiate_response(smb3_response)
|
1151
|
+
expect(client.dialect).to eq '0x0300'
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
it 'returns the string \'SMB2\'' do
|
1155
|
+
expect(client.parse_negotiate_response(smb3_response)).to eq ('SMB3')
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
it 'sets #server_supports_multi_credit to true when the response has #large_mtu capability set' do
|
1159
|
+
smb3_response.capabilities.large_mtu = 1
|
1160
|
+
client.parse_negotiate_response(smb3_response)
|
1161
|
+
expect(client.server_supports_multi_credit).to be true
|
1162
|
+
end
|
1163
|
+
|
1164
|
+
context 'when the server supports encryption' do
|
1165
|
+
before :example do
|
1166
|
+
smb3_response.capabilities.encryption = 1
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
it 'sets the expected encryption algorithm' do
|
1170
|
+
client.parse_negotiate_response(smb3_response)
|
1171
|
+
expect(client.encryption_algorithm).to eq(RubySMB::SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP[RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM])
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
it 'keeps session encryption enabled if it was already' do
|
1175
|
+
client.session_encrypt_data = true
|
1176
|
+
client.parse_negotiate_response(smb3_response)
|
1177
|
+
expect(client.session_encrypt_data).to be true
|
1178
|
+
end
|
1179
|
+
|
1180
|
+
it 'keeps session encryption disabled if it was already' do
|
1181
|
+
client.session_encrypt_data = false
|
1182
|
+
client.parse_negotiate_response(smb3_response)
|
1183
|
+
expect(client.session_encrypt_data).to be false
|
1184
|
+
end
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
context 'when the server does not support encryption' do
|
1188
|
+
before :example do
|
1189
|
+
smb3_response.capabilities.encryption = 0
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
it 'disables session encryption if it was already enabled' do
|
1193
|
+
client.session_encrypt_data = true
|
1194
|
+
client.parse_negotiate_response(smb3_response)
|
1195
|
+
expect(client.session_encrypt_data).to be false
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
it 'keeps session encryption disabled if it was already' do
|
1199
|
+
client.session_encrypt_data = false
|
1200
|
+
client.parse_negotiate_response(smb3_response)
|
1201
|
+
expect(client.session_encrypt_data).to be false
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
context 'when the response contains the SMB2 wildcard revision number dialect' do
|
1207
|
+
it 'only turns off SMB1 support' do
|
1208
|
+
smb2_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x02ff)
|
1209
|
+
client.parse_negotiate_response(smb2_response)
|
1210
|
+
expect(client.smb1).to be false
|
1211
|
+
expect(client.smb2).to be true
|
1212
|
+
expect(client.smb3).to be true
|
1213
|
+
end
|
1214
|
+
end
|
1215
|
+
|
1216
|
+
context 'when the negotiation failed' do
|
1217
|
+
context 'with a STATUS_NOT_SUPPORTED status code' do
|
1218
|
+
before :example do
|
1219
|
+
error_packet.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
it 'raises the expected exception with SMB2' do
|
1223
|
+
expect { smb2_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1224
|
+
RubySMB::Error::NegotiationFailure,
|
1225
|
+
'Unable to negotiate with remote host, SMB2 not supported'
|
1226
|
+
)
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
it 'raises the expected exception with SMB3' do
|
1230
|
+
expect { smb3_client.parse_negotiate_response(error_packet) }.to raise_error(
|
1231
|
+
RubySMB::Error::NegotiationFailure,
|
1232
|
+
'Unable to negotiate with remote host, SMB3 not supported'
|
1233
|
+
)
|
1234
|
+
end
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
context 'with an unknown status code' do
|
1238
|
+
it 'raises the expected exception' do
|
1239
|
+
expect { client.parse_negotiate_response(empty_packet) }.to raise_error(
|
1240
|
+
RubySMB::Error::NegotiationFailure,
|
1241
|
+
'Unable to negotiate with remote host'
|
1242
|
+
)
|
1243
|
+
end
|
1244
|
+
end
|
1245
|
+
end
|
1246
|
+
end
|
1247
|
+
|
1248
|
+
describe '#negotiate' do
|
1249
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1250
|
+
before :example do
|
1251
|
+
allow(client).to receive(:negotiate_request)
|
1252
|
+
allow(client).to receive(:send_recv)
|
1253
|
+
allow(client).to receive(:negotiate_response)
|
1254
|
+
allow(client).to receive(:parse_negotiate_response)
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
it 'calls the backing methods' do
|
1258
|
+
expect(client).to receive(:negotiate_request)
|
1259
|
+
expect(client).to receive(:send_recv)
|
1260
|
+
expect(client).to receive(:negotiate_response)
|
1261
|
+
expect(client).to receive(:parse_negotiate_response)
|
1262
|
+
client.negotiate
|
1263
|
+
end
|
1264
|
+
|
1265
|
+
context 'with SMB1' do
|
1266
|
+
it 'sets the response-packet #dialects array with the dialects sent in the request' do
|
1267
|
+
request_packet = client.smb1_negotiate_request
|
1268
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1269
|
+
allow(client).to receive(:negotiate_response).and_return(smb1_extended_response)
|
1270
|
+
expect(smb1_extended_response).to receive(:dialects=).with(request_packet.dialects)
|
1271
|
+
client.negotiate
|
1272
|
+
end
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
context "with 0x0311 dialect" do
|
1276
|
+
it 'calls #parse_negotiate_response and updates the preauth hash' do
|
1277
|
+
client.dialect = '0x0311'
|
1278
|
+
request_packet = client.smb2_3_negotiate_request
|
1279
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1280
|
+
allow(client).to receive(:negotiate_response).and_return(smb3_response)
|
1281
|
+
expect(client).to receive(:parse_negotiate_response).with(smb3_response)
|
1282
|
+
expect(client).to receive(:update_preauth_hash).with(request_packet)
|
1283
|
+
expect(client).to receive(:update_preauth_hash).with(smb3_response)
|
1284
|
+
client.negotiate
|
1285
|
+
end
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
context 'with a wildcard revision number response' do
|
1289
|
+
before :example do
|
1290
|
+
client.dialect = '0x02ff'
|
1291
|
+
allow(client).to receive(:smb2_message_id=) do
|
1292
|
+
client.dialect = '0x0202'
|
1293
|
+
end
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
it 'increments the message ID' do
|
1297
|
+
expect(client).to receive(:smb2_message_id=).with(1)
|
1298
|
+
client.negotiate
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
it 're-negotiates' do
|
1302
|
+
expect(client).to receive(:negotiate_request).twice
|
1303
|
+
expect(client).to receive(:send_recv).twice
|
1304
|
+
expect(client).to receive(:negotiate_response).twice
|
1305
|
+
expect(client).to receive(:parse_negotiate_response).twice
|
1306
|
+
client.negotiate
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
context 'when an error occurs' do
|
1311
|
+
before :example do
|
1312
|
+
allow(client).to receive(:negotiate_request).and_return(request_packet)
|
1313
|
+
allow(client).to receive(:send_recv).and_raise(RubySMB::Error::InvalidPacket)
|
1314
|
+
client.smb1 = false
|
1315
|
+
client.smb2 = false
|
1316
|
+
client.smb3 = false
|
483
1317
|
end
|
484
1318
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
1319
|
+
context 'with SMB1' do
|
1320
|
+
let(:request_packet) { client.smb1_negotiate_request }
|
1321
|
+
|
1322
|
+
it 'raise the expected exception' do
|
1323
|
+
client.smb1 = true
|
1324
|
+
expect { client.negotiate }.to raise_error(
|
1325
|
+
RubySMB::Error::NegotiationFailure,
|
1326
|
+
"Unable to negotiate SMB1 with the remote host: RubySMB::Error::InvalidPacket"
|
1327
|
+
)
|
1328
|
+
end
|
489
1329
|
end
|
490
1330
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
1331
|
+
context 'with SMB2' do
|
1332
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1333
|
+
|
1334
|
+
it 'raise the expected exception' do
|
1335
|
+
client.smb2 = true
|
1336
|
+
expect { client.negotiate }.to raise_error(
|
1337
|
+
RubySMB::Error::NegotiationFailure,
|
1338
|
+
"Unable to negotiate SMB2 with the remote host: RubySMB::Error::InvalidPacket"
|
1339
|
+
)
|
1340
|
+
end
|
500
1341
|
end
|
501
1342
|
|
502
|
-
|
503
|
-
|
1343
|
+
context 'with SMB3' do
|
1344
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1345
|
+
|
1346
|
+
it 'raise the expected exception' do
|
1347
|
+
client.smb3 = true
|
1348
|
+
expect { client.negotiate }.to raise_error(
|
1349
|
+
RubySMB::Error::NegotiationFailure,
|
1350
|
+
"Unable to negotiate SMB3 with the remote host: RubySMB::Error::InvalidPacket"
|
1351
|
+
)
|
1352
|
+
end
|
504
1353
|
end
|
505
1354
|
end
|
506
1355
|
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
1356
|
+
describe '#parse_smb3_capabilities' do
|
1357
|
+
let(:request_packet) { client.smb2_3_negotiate_request }
|
1358
|
+
let(:smb3_response) { RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311) }
|
1359
|
+
let(:nc_encryption) do
|
1360
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1361
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1362
|
+
)
|
1363
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_CCM
|
1364
|
+
nc
|
511
1365
|
end
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
1366
|
+
let(:nc_integrity) do
|
1367
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1368
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1369
|
+
)
|
1370
|
+
nc.data.hash_algorithms << RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
1371
|
+
nc
|
517
1372
|
end
|
518
1373
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
1374
|
+
before :example do
|
1375
|
+
allow(smb3_client).to receive(:update_preauth_hash)
|
1376
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1377
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
523
1378
|
end
|
524
1379
|
|
525
|
-
|
526
|
-
|
1380
|
+
context 'when selecting the integrity hash algorithm' do
|
1381
|
+
context 'with one algorithm' do
|
1382
|
+
it 'selects the expected algorithm' do
|
1383
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1384
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1385
|
+
end
|
1386
|
+
end
|
1387
|
+
|
1388
|
+
context 'with multiple algorithms' do
|
1389
|
+
it 'selects the first algorithm' do
|
1390
|
+
nc = smb3_response.find_negotiate_context(
|
1391
|
+
RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1392
|
+
)
|
1393
|
+
nc.data.hash_algorithms << 3
|
1394
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1395
|
+
expect(smb3_client.preauth_integrity_hash_algorithm).to eq('SHA512')
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
|
1399
|
+
context 'without integrity negotiate context' do
|
1400
|
+
it 'raises the expected exception' do
|
1401
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1402
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1403
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1404
|
+
RubySMB::Error::EncryptionError,
|
1405
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1406
|
+
)
|
1407
|
+
end
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
context 'with an unknown integrity hash algorithm' do
|
1411
|
+
it 'raises the expected exception' do
|
1412
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1413
|
+
smb3_response.add_negotiate_context(nc_encryption)
|
1414
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1415
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
1416
|
+
)
|
1417
|
+
nc.data.hash_algorithms << 5
|
1418
|
+
smb3_response.add_negotiate_context(nc)
|
1419
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1420
|
+
RubySMB::Error::EncryptionError,
|
1421
|
+
'Unable to retrieve the Preauth Integrity Hash Algorithm from the Negotiate response'
|
1422
|
+
)
|
1423
|
+
end
|
1424
|
+
end
|
527
1425
|
end
|
528
|
-
end
|
529
|
-
end
|
530
1426
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
end
|
1427
|
+
context 'when selecting the encryption algorithm' do
|
1428
|
+
context 'with one algorithm' do
|
1429
|
+
it 'selects the expected algorithm' do
|
1430
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1431
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1432
|
+
end
|
1433
|
+
end
|
539
1434
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
1435
|
+
context 'with multiple algorithms' do
|
1436
|
+
it 'selects the AES-128-GCM algorithm if included' do
|
1437
|
+
nc = smb3_response.find_negotiate_context(
|
1438
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1439
|
+
)
|
1440
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1441
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1442
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-GCM')
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
it 'selects the first algorithm if AES-128-GCM is not included' do
|
1446
|
+
nc = smb3_response.find_negotiate_context(
|
1447
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1448
|
+
)
|
1449
|
+
nc.data.ciphers << 3
|
1450
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1451
|
+
expect(smb3_client.encryption_algorithm).to eq('AES-128-CCM')
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
it 'keep tracks of the server supported algorithms' do
|
1455
|
+
nc = smb3_response.find_negotiate_context(
|
1456
|
+
RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1457
|
+
)
|
1458
|
+
nc.data.ciphers << RubySMB::SMB2::EncryptionCapabilities::AES_128_GCM
|
1459
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1460
|
+
expect(smb3_client.server_encryption_algorithms).to eq([1, 2])
|
1461
|
+
end
|
1462
|
+
end
|
1463
|
+
|
1464
|
+
context 'without encryption context' do
|
1465
|
+
it 'raises the expected exception' do
|
1466
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1467
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1468
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1469
|
+
RubySMB::Error::EncryptionError,
|
1470
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1471
|
+
)
|
1472
|
+
end
|
1473
|
+
end
|
548
1474
|
|
549
|
-
|
550
|
-
|
551
|
-
|
1475
|
+
context 'with an unknown encryption algorithm' do
|
1476
|
+
it 'raises the expected exception' do
|
1477
|
+
smb3_response = RubySMB::SMB2::Packet::NegotiateResponse.new(dialect_revision: 0x311)
|
1478
|
+
smb3_response.add_negotiate_context(nc_integrity)
|
1479
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1480
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES
|
1481
|
+
)
|
1482
|
+
nc.data.ciphers << 14
|
1483
|
+
smb3_response.add_negotiate_context(nc)
|
1484
|
+
expect { smb3_client.parse_smb3_capabilities(smb3_response) }.to raise_error(
|
1485
|
+
RubySMB::Error::EncryptionError,
|
1486
|
+
'Unable to retrieve the encryption cipher list supported by the server from the Negotiate response'
|
1487
|
+
)
|
1488
|
+
end
|
1489
|
+
end
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
context 'when selecting the compression algorithm' do
|
1493
|
+
it 'keep tracks of the server supported algorithms' do
|
1494
|
+
nc = RubySMB::SMB2::NegotiateContext.new(
|
1495
|
+
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
1496
|
+
)
|
1497
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
1498
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
1499
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
1500
|
+
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::Pattern_V1
|
1501
|
+
smb3_response.add_negotiate_context(nc)
|
1502
|
+
smb3_client.parse_smb3_capabilities(smb3_response)
|
1503
|
+
expect(smb3_client.server_compression_algorithms).to eq([1, 2, 3, 4])
|
1504
|
+
end
|
1505
|
+
end
|
552
1506
|
end
|
553
1507
|
end
|
554
1508
|
end
|
@@ -875,6 +1829,39 @@ RSpec.describe RubySMB::Client do
|
|
875
1829
|
smb2_client.smb2_authenticate
|
876
1830
|
expect(smb2_client.os_version).to eq '6.1.7601'
|
877
1831
|
end
|
1832
|
+
|
1833
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1834
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1835
|
+
smb2_client.dialect = dialect
|
1836
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1837
|
+
smb2_client.smb2_authenticate
|
1838
|
+
end
|
1839
|
+
end
|
1840
|
+
|
1841
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1842
|
+
smb2_client.dialect = '0x0311'
|
1843
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(response_packet)
|
1844
|
+
smb2_client.smb2_authenticate
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
context 'when setting the session_encrypt_data parameter' do
|
1848
|
+
before :example do
|
1849
|
+
smb2_client.smb3 = true
|
1850
|
+
smb2_client.session_encrypt_data = false
|
1851
|
+
end
|
1852
|
+
|
1853
|
+
it 'sets the session_encrypt_data parameter to true if the server requires encryption' do
|
1854
|
+
final_response_packet.session_flags.encrypt_data = 1
|
1855
|
+
smb2_client.smb2_authenticate
|
1856
|
+
expect(smb2_client.session_encrypt_data).to be true
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
it 'does not set the session_encrypt_data parameter if the server does not require encryption' do
|
1860
|
+
final_response_packet.session_flags.encrypt_data = 0
|
1861
|
+
smb2_client.smb2_authenticate
|
1862
|
+
expect(smb2_client.session_encrypt_data).to be false
|
1863
|
+
end
|
1864
|
+
end
|
878
1865
|
end
|
879
1866
|
|
880
1867
|
describe '#smb2_ntlmssp_negotiate_packet' do
|
@@ -890,20 +1877,34 @@ RSpec.describe RubySMB::Client do
|
|
890
1877
|
smb2_client.smb2_ntlmssp_negotiate_packet
|
891
1878
|
end
|
892
1879
|
|
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)
|
1880
|
+
it 'enables signing' do
|
1881
|
+
expect(smb2_client.smb2_ntlmssp_negotiate_packet.security_mode.signing_enabled).to eq 1
|
899
1882
|
end
|
900
1883
|
end
|
901
1884
|
|
902
1885
|
describe '#smb2_ntlmssp_negotiate' do
|
1886
|
+
before :example do
|
1887
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet).and_return(negotiate_packet)
|
1888
|
+
allow(smb2_client).to receive(:send_recv)
|
1889
|
+
end
|
1890
|
+
|
903
1891
|
it 'sends the request packet and receives a response' do
|
904
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
905
|
-
expect(
|
906
|
-
|
1892
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_negotiate_packet)
|
1893
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1894
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1895
|
+
end
|
1896
|
+
|
1897
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1898
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1899
|
+
smb2_client.dialect = dialect
|
1900
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1901
|
+
smb2_client.smb2_ntlmssp_negotiate
|
1902
|
+
end
|
1903
|
+
end
|
1904
|
+
|
1905
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1906
|
+
smb2_client.dialect = '0x0311'
|
1907
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
907
1908
|
smb2_client.smb2_ntlmssp_negotiate
|
908
1909
|
end
|
909
1910
|
end
|
@@ -961,13 +1962,35 @@ RSpec.describe RubySMB::Client do
|
|
961
1962
|
it 'sets the session ID on the request packet' do
|
962
1963
|
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).smb2_header.session_id).to eq session_id
|
963
1964
|
end
|
1965
|
+
|
1966
|
+
it 'enables signing' do
|
1967
|
+
expect(smb2_client.smb2_ntlmssp_auth_packet(type3_message, session_id).security_mode.signing_enabled).to eq 1
|
1968
|
+
end
|
964
1969
|
end
|
965
1970
|
|
966
1971
|
describe '#smb2_ntlmssp_authenticate' do
|
1972
|
+
before :example do
|
1973
|
+
allow(smb2_client).to receive(:smb2_ntlmssp_auth_packet).and_return(negotiate_packet)
|
1974
|
+
allow(smb2_client).to receive(:send_recv)
|
1975
|
+
end
|
1976
|
+
|
967
1977
|
it 'sends the request packet and receives a response' do
|
968
|
-
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
969
|
-
expect(
|
970
|
-
|
1978
|
+
expect(smb2_client).to receive(:smb2_ntlmssp_auth_packet)
|
1979
|
+
expect(smb2_client).to receive(:send_recv).with(negotiate_packet)
|
1980
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1981
|
+
end
|
1982
|
+
|
1983
|
+
['0x0202', '0x0210', '0x0300', '0x0302'].each do |dialect|
|
1984
|
+
it "does not update the preauth hash with dialect #{dialect}" do
|
1985
|
+
smb2_client.dialect = dialect
|
1986
|
+
expect(smb2_client).to_not receive(:update_preauth_hash)
|
1987
|
+
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
1988
|
+
end
|
1989
|
+
end
|
1990
|
+
|
1991
|
+
it "updates the preauth hash with dialect 0x0311" do
|
1992
|
+
smb2_client.dialect = '0x0311'
|
1993
|
+
expect(smb2_client).to receive(:update_preauth_hash).with(negotiate_packet)
|
971
1994
|
smb2_client.smb2_ntlmssp_authenticate(type3_message, session_id)
|
972
1995
|
end
|
973
1996
|
end
|
@@ -1103,6 +2126,108 @@ RSpec.describe RubySMB::Client do
|
|
1103
2126
|
end
|
1104
2127
|
end
|
1105
2128
|
end
|
2129
|
+
|
2130
|
+
describe '#smb3_sign' do
|
2131
|
+
context 'if signing is required and we have a session key' do
|
2132
|
+
let(:request) {
|
2133
|
+
packet = RubySMB::SMB2::Packet::SessionSetupRequest.new
|
2134
|
+
packet.smb2_header.flags.signed = 1
|
2135
|
+
packet.smb2_header.signature = "\x00" * 16
|
2136
|
+
packet
|
2137
|
+
}
|
2138
|
+
let(:session_key) { 'Session Key' }
|
2139
|
+
before :example do
|
2140
|
+
smb3_client.session_key = session_key
|
2141
|
+
smb3_client.signing_required = true
|
2142
|
+
end
|
2143
|
+
|
2144
|
+
['0x0300', '0x0302'].each do |dialect|
|
2145
|
+
context "with #{dialect} dialect" do
|
2146
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2147
|
+
smb3_client.dialect = dialect
|
2148
|
+
fake_hash = "\x34\xc0\x40\xfe\x87\xcf\x49\x3d\x37\x87\x52\xd0\xd5\xf5\xfb\x86".b
|
2149
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2150
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2151
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2152
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2153
|
+
end
|
2154
|
+
end
|
2155
|
+
end
|
2156
|
+
|
2157
|
+
context "with 0x0311 dialect" do
|
2158
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2159
|
+
smb3_client.dialect = '0x0311'
|
2160
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2161
|
+
fake_hash = "\x0e\x49\x6f\x8e\x74\x7c\xf2\xa0\x88\x5e\x9d\x54\xff\x0d\x0d\xfa".b
|
2162
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2163
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2164
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2165
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2166
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2167
|
+
end
|
2168
|
+
end
|
2169
|
+
|
2170
|
+
context 'with an incompatible dialect' do
|
2171
|
+
it 'raises the expected exception' do
|
2172
|
+
smb3_client.dialect = '0x0202'
|
2173
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2174
|
+
RubySMB::Error::SigningError,
|
2175
|
+
'Dialect is incompatible with SMBv3 signing'
|
2176
|
+
)
|
2177
|
+
end
|
2178
|
+
end
|
2179
|
+
end
|
2180
|
+
|
2181
|
+
context 'if signing is not required but it is a TreeConnectRequest and we have a session key' do
|
2182
|
+
let(:request) {
|
2183
|
+
packet = RubySMB::SMB2::Packet::TreeConnectRequest.new
|
2184
|
+
packet.smb2_header.flags.signed = 1
|
2185
|
+
packet.smb2_header.signature = "\x00" * 16
|
2186
|
+
packet
|
2187
|
+
}
|
2188
|
+
let(:session_key) { 'Session Key' }
|
2189
|
+
before :example do
|
2190
|
+
smb3_client.session_key = session_key
|
2191
|
+
smb3_client.signing_required = false
|
2192
|
+
end
|
2193
|
+
|
2194
|
+
['0x0300', '0x0302'].each do |dialect|
|
2195
|
+
context "with #{dialect} dialect" do
|
2196
|
+
it 'generates the signing key based on the session key and specific strings, and sign the packet with CMAC' do
|
2197
|
+
smb3_client.dialect = dialect
|
2198
|
+
fake_hash = "\x34\x9e\x28\xb9\x50\x08\x34\x31\xc0\x83\x9d\xba\x56\xa5\x70\xa4".b
|
2199
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMB2AESCMAC\x00", "SmbSign\x00")
|
2200
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMB2AESCMAC\x00", "SmbSign\x00").and_call_original
|
2201
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2202
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2203
|
+
end
|
2204
|
+
end
|
2205
|
+
end
|
2206
|
+
|
2207
|
+
context "with 0x0311 dialect" do
|
2208
|
+
it 'generates the signing key based on the session key, the preauth integrity hash and specific strings, and sign the packet with CMAC' do
|
2209
|
+
smb3_client.dialect = '0x0311'
|
2210
|
+
preauth_integrity_hash_value = 'Preauth Integrity Hash'
|
2211
|
+
fake_hash = "\x83\xd9\x31\x39\x60\x46\xbe\x1e\x29\x34\xc8\xcf\x8c\x8e\xb4\x73".b
|
2212
|
+
smb3_client.preauth_integrity_hash_value = preauth_integrity_hash_value
|
2213
|
+
signing_key = RubySMB::Crypto::KDF.counter_mode(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value)
|
2214
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(session_key, "SMBSigningKey\x00", preauth_integrity_hash_value).and_call_original
|
2215
|
+
expect(OpenSSL::CMAC).to receive(:digest).with('AES', signing_key, request.to_binary_s).and_call_original
|
2216
|
+
expect(smb3_client.smb3_sign(request).smb2_header.signature).to eq fake_hash
|
2217
|
+
end
|
2218
|
+
end
|
2219
|
+
|
2220
|
+
context 'with an incompatible dialect' do
|
2221
|
+
it 'raises the expected exception' do
|
2222
|
+
smb3_client.dialect = '0x0202'
|
2223
|
+
expect { smb3_client.smb3_sign(request) }.to raise_error(
|
2224
|
+
RubySMB::Error::SigningError,
|
2225
|
+
'Dialect is incompatible with SMBv3 signing'
|
2226
|
+
)
|
2227
|
+
end
|
2228
|
+
end
|
2229
|
+
end
|
2230
|
+
end
|
1106
2231
|
end
|
1107
2232
|
|
1108
2233
|
context '#increment_smb_message_id' do
|
@@ -1156,7 +2281,10 @@ RSpec.describe RubySMB::Client do
|
|
1156
2281
|
|
1157
2282
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1158
2283
|
response.smb_header.nt_status = 0xc0000015
|
1159
|
-
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2284
|
+
expect { smb1_client.smb1_tree_from_response(path, response) }.to raise_error(
|
2285
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2286
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2287
|
+
)
|
1160
2288
|
end
|
1161
2289
|
|
1162
2290
|
it 'creates a new Tree from itself, the share path, and the response packet' do
|
@@ -1177,11 +2305,14 @@ RSpec.describe RubySMB::Client do
|
|
1177
2305
|
}
|
1178
2306
|
|
1179
2307
|
describe '#smb2_tree_connect' do
|
1180
|
-
it 'builds and sends
|
2308
|
+
it 'builds and sends the expected TreeconnectRequest for the supplied share' do
|
1181
2309
|
allow(RubySMB::SMB2::Packet::TreeConnectRequest).to receive(:new).and_return(request)
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
2310
|
+
expect(smb2_client).to receive(:send_recv) do |req|
|
2311
|
+
expect(req).to eq(request)
|
2312
|
+
expect(req.smb2_header.tree_id).to eq(65_535)
|
2313
|
+
expect(req.path).to eq(path.encode('UTF-16LE'))
|
2314
|
+
response.to_binary_s
|
2315
|
+
end
|
1185
2316
|
smb2_client.smb2_tree_connect(path)
|
1186
2317
|
end
|
1187
2318
|
|
@@ -1200,11 +2331,20 @@ RSpec.describe RubySMB::Client do
|
|
1200
2331
|
|
1201
2332
|
it 'raises an UnexpectedStatusCode exception if we do not get STATUS_SUCCESS' do
|
1202
2333
|
response.smb2_header.nt_status = 0xc0000015
|
1203
|
-
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2334
|
+
expect { smb2_client.smb2_tree_from_response(path, response) }.to raise_error(
|
2335
|
+
RubySMB::Error::UnexpectedStatusCode,
|
2336
|
+
'The server responded with an unexpected status code: STATUS_NONEXISTENT_SECTOR'
|
2337
|
+
)
|
1204
2338
|
end
|
1205
2339
|
|
1206
2340
|
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)
|
2341
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: false)
|
2342
|
+
smb2_client.smb2_tree_from_response(path, response)
|
2343
|
+
end
|
2344
|
+
|
2345
|
+
it 'creates a new with encryption set if the response requires it' do
|
2346
|
+
response.share_flags.encrypt = 1
|
2347
|
+
expect(RubySMB::SMB2::Tree).to receive(:new).with(client: smb2_client, share: path, response: response, encrypt: true)
|
1208
2348
|
smb2_client.smb2_tree_from_response(path, response)
|
1209
2349
|
end
|
1210
2350
|
end
|
@@ -1301,7 +2441,7 @@ RSpec.describe RubySMB::Client do
|
|
1301
2441
|
end
|
1302
2442
|
|
1303
2443
|
it 'raise an InvalidPacket exception when the response is not valid' do
|
1304
|
-
echo_response.smb_header.command = RubySMB::SMB1::Commands::
|
2444
|
+
echo_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
|
1305
2445
|
allow(smb1_client).to receive(:send_recv).and_return(echo_response.to_binary_s)
|
1306
2446
|
expect { smb1_client.echo }.to raise_error(RubySMB::Error::InvalidPacket)
|
1307
2447
|
end
|
@@ -1325,5 +2465,346 @@ RSpec.describe RubySMB::Client do
|
|
1325
2465
|
end
|
1326
2466
|
end
|
1327
2467
|
|
2468
|
+
context 'Winreg' do
|
2469
|
+
describe '#connect_to_winreg' do
|
2470
|
+
let(:host) { '1.2.3.4' }
|
2471
|
+
let(:share) { "\\\\#{host}\\IPC$" }
|
2472
|
+
let(:ipc_tree) { double('IPC$ tree') }
|
2473
|
+
let(:named_pipe) { double('Named pipe') }
|
2474
|
+
before :example do
|
2475
|
+
allow(ipc_tree).to receive_messages(
|
2476
|
+
:share => share,
|
2477
|
+
:open_file => named_pipe
|
2478
|
+
)
|
2479
|
+
allow(client).to receive(:tree_connect).and_return(ipc_tree)
|
2480
|
+
end
|
2481
|
+
|
2482
|
+
context 'when the client is already connected to the IPC$ share' do
|
2483
|
+
before :example do
|
2484
|
+
client.tree_connects << ipc_tree
|
2485
|
+
allow(ipc_tree).to receive(:share).and_return(share)
|
2486
|
+
end
|
2487
|
+
|
2488
|
+
it 'does not connect to the already connected tree' do
|
2489
|
+
client.connect_to_winreg(host)
|
2490
|
+
expect(client).to_not have_received(:tree_connect)
|
2491
|
+
end
|
2492
|
+
end
|
2493
|
+
|
2494
|
+
it 'calls #tree_connect' do
|
2495
|
+
client.connect_to_winreg(host)
|
2496
|
+
expect(client).to have_received(:tree_connect).with(share)
|
2497
|
+
end
|
2498
|
+
|
2499
|
+
it 'open \'winreg\' file on the IPC$ Tree' do
|
2500
|
+
client.connect_to_winreg(host)
|
2501
|
+
expect(ipc_tree).to have_received(:open_file).with(filename: "winreg", write: true, read: true)
|
2502
|
+
end
|
2503
|
+
|
2504
|
+
it 'returns the expected opened named pipe' do
|
2505
|
+
expect(client.connect_to_winreg(host)).to eq(named_pipe)
|
2506
|
+
end
|
2507
|
+
|
2508
|
+
context 'when a block is given' do
|
2509
|
+
before :example do
|
2510
|
+
allow(named_pipe).to receive(:close)
|
2511
|
+
end
|
2512
|
+
|
2513
|
+
it 'yields the expected named_pipe' do
|
2514
|
+
client.connect_to_winreg(host) do |np|
|
2515
|
+
expect(np).to eq(named_pipe)
|
2516
|
+
end
|
2517
|
+
end
|
2518
|
+
|
2519
|
+
it 'closes the named pipe' do
|
2520
|
+
client.connect_to_winreg(host) { |np| }
|
2521
|
+
expect(named_pipe).to have_received(:close)
|
2522
|
+
end
|
2523
|
+
|
2524
|
+
it 'returns the block return value' do
|
2525
|
+
result = double('Result')
|
2526
|
+
expect(client.connect_to_winreg(host) { |np| result }).to eq(result)
|
2527
|
+
end
|
2528
|
+
end
|
2529
|
+
end
|
2530
|
+
|
2531
|
+
describe '#has_registry_key?' do
|
2532
|
+
let(:host) { '1.2.3.4' }
|
2533
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2534
|
+
let(:named_pipe) { double('Named pipe') }
|
2535
|
+
let(:result) { double('Result') }
|
2536
|
+
before :example do
|
2537
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2538
|
+
allow(named_pipe).to receive(:has_registry_key?).and_return(result)
|
2539
|
+
end
|
2540
|
+
|
2541
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2542
|
+
client.has_registry_key?(host, key)
|
2543
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2544
|
+
end
|
2545
|
+
|
2546
|
+
it 'calls Pipe #has_registry_key?' do
|
2547
|
+
client.has_registry_key?(host, key)
|
2548
|
+
expect(named_pipe).to have_received(:has_registry_key?).with(key)
|
2549
|
+
end
|
2550
|
+
end
|
2551
|
+
|
2552
|
+
describe '#read_registry_key_value' do
|
2553
|
+
let(:host) { '1.2.3.4' }
|
2554
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2555
|
+
let(:value_name) { 'Value' }
|
2556
|
+
let(:named_pipe) { double('Named pipe') }
|
2557
|
+
let(:result) { double('Result') }
|
2558
|
+
before :example do
|
2559
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2560
|
+
allow(named_pipe).to receive(:read_registry_key_value).and_return(result)
|
2561
|
+
end
|
2562
|
+
|
2563
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2564
|
+
client.read_registry_key_value(host, key, value_name)
|
2565
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2566
|
+
end
|
2567
|
+
|
2568
|
+
it 'calls Pipe #read_registry_key_value' do
|
2569
|
+
client.read_registry_key_value(host, key, value_name)
|
2570
|
+
expect(named_pipe).to have_received(:read_registry_key_value).with(key, value_name)
|
2571
|
+
end
|
2572
|
+
end
|
2573
|
+
|
2574
|
+
describe '#enum_registry_key' do
|
2575
|
+
let(:host) { '1.2.3.4' }
|
2576
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2577
|
+
let(:named_pipe) { double('Named pipe') }
|
2578
|
+
let(:result) { double('Result') }
|
2579
|
+
before :example do
|
2580
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2581
|
+
allow(named_pipe).to receive(:enum_registry_key).and_return(result)
|
2582
|
+
end
|
2583
|
+
|
2584
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2585
|
+
client.enum_registry_key(host, key)
|
2586
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2587
|
+
end
|
2588
|
+
|
2589
|
+
it 'calls Pipe #enum_registry_key' do
|
2590
|
+
client.enum_registry_key(host, key)
|
2591
|
+
expect(named_pipe).to have_received(:enum_registry_key).with(key)
|
2592
|
+
end
|
2593
|
+
end
|
2594
|
+
|
2595
|
+
describe '#enum_registry_values' do
|
2596
|
+
let(:host) { '1.2.3.4' }
|
2597
|
+
let(:key) { 'HKLM\\Registry\\Key' }
|
2598
|
+
let(:named_pipe) { double('Named pipe') }
|
2599
|
+
let(:result) { double('Result') }
|
2600
|
+
before :example do
|
2601
|
+
allow(client).to receive(:connect_to_winreg).and_yield(named_pipe)
|
2602
|
+
allow(named_pipe).to receive(:enum_registry_values).and_return(result)
|
2603
|
+
end
|
2604
|
+
|
2605
|
+
it 'calls #connect_to_winreg to wrap the main logic around' do
|
2606
|
+
client.enum_registry_values(host, key)
|
2607
|
+
expect(client).to have_received(:connect_to_winreg).with(host)
|
2608
|
+
end
|
2609
|
+
|
2610
|
+
it 'calls Pipe #enum_registry_values' do
|
2611
|
+
client.enum_registry_values(host, key)
|
2612
|
+
expect(named_pipe).to have_received(:enum_registry_values).with(key)
|
2613
|
+
end
|
2614
|
+
end
|
2615
|
+
end
|
2616
|
+
|
2617
|
+
describe '#update_preauth_hash' do
|
2618
|
+
it 'raises an EncryptionError exception if the preauth integrity hash algorithm is not known' do
|
2619
|
+
expect { client.update_preauth_hash('Test') }.to raise_error(
|
2620
|
+
RubySMB::Error::EncryptionError,
|
2621
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
2622
|
+
)
|
2623
|
+
end
|
2624
|
+
|
2625
|
+
it 'computes the hash value' do
|
2626
|
+
packet = RubySMB::SMB2::Packet::EchoRequest.new
|
2627
|
+
data = 'Previous hash'
|
2628
|
+
algo = RubySMB::SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[
|
2629
|
+
RubySMB::SMB2::PreauthIntegrityCapabilities::SHA_512
|
2630
|
+
]
|
2631
|
+
client.preauth_integrity_hash_algorithm = algo
|
2632
|
+
client.preauth_integrity_hash_value = data
|
2633
|
+
hash = OpenSSL::Digest.digest(algo, data + packet.to_binary_s)
|
2634
|
+
client.update_preauth_hash(packet)
|
2635
|
+
expect(client.preauth_integrity_hash_value).to eq(hash)
|
2636
|
+
end
|
2637
|
+
end
|
2638
|
+
|
2639
|
+
context 'Encryption' do
|
2640
|
+
describe '#smb3_encrypt' do
|
2641
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2642
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2643
|
+
let(:data) { RubySMB::SMB2::Packet::TreeConnectRequest.new.to_binary_s }
|
2644
|
+
|
2645
|
+
before :example do
|
2646
|
+
allow(RubySMB::SMB2::Packet::TransformHeader).to receive(:new).and_return(transform_packet)
|
2647
|
+
allow(transform_packet).to receive(:encrypt)
|
2648
|
+
client.session_key = session_key
|
2649
|
+
end
|
2650
|
+
|
2651
|
+
it 'does not generate a new client encryption key if it already exists' do
|
2652
|
+
client.client_encryption_key = 'key'
|
2653
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2654
|
+
expect(client.client_encryption_key).to eq('key')
|
2655
|
+
client.smb3_encrypt(data)
|
2656
|
+
end
|
2657
|
+
|
2658
|
+
['0x0300', '0x0302'].each do |dialect|
|
2659
|
+
context "with #{dialect} dialect" do
|
2660
|
+
before :example do
|
2661
|
+
client.dialect = dialect
|
2662
|
+
end
|
2663
|
+
|
2664
|
+
it 'generates the client encryption key with the expected parameters' do
|
2665
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2666
|
+
session_key,
|
2667
|
+
"SMB2AESCCM\x00",
|
2668
|
+
"ServerIn \x00"
|
2669
|
+
).and_call_original
|
2670
|
+
client.smb3_encrypt(data)
|
2671
|
+
end
|
2672
|
+
end
|
2673
|
+
end
|
2674
|
+
|
2675
|
+
context 'with 0x0311 dialect' do
|
2676
|
+
it 'generates the client encryption key with the expected parameters' do
|
2677
|
+
client.preauth_integrity_hash_value = ''
|
2678
|
+
client.dialect = '0x0311'
|
2679
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2680
|
+
session_key,
|
2681
|
+
"SMBC2SCipherKey\x00",
|
2682
|
+
''
|
2683
|
+
).and_call_original
|
2684
|
+
client.smb3_encrypt(data)
|
2685
|
+
end
|
2686
|
+
end
|
2687
|
+
|
2688
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2689
|
+
client.dialect = '0x0202'
|
2690
|
+
expect { client.smb3_encrypt(data) }.to raise_error(RubySMB::Error::EncryptionError)
|
2691
|
+
end
|
2692
|
+
|
2693
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2694
|
+
client.dialect = '0x0300'
|
2695
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2696
|
+
client.session_id = 123
|
2697
|
+
client.smb3_encrypt(data)
|
2698
|
+
expect(RubySMB::SMB2::Packet::TransformHeader).to have_received(:new).with(flags: 1, session_id: 123)
|
2699
|
+
expect(transform_packet).to have_received(:encrypt).with(data, client.client_encryption_key, algorithm: 'AES-128-CCM')
|
2700
|
+
end
|
2701
|
+
|
2702
|
+
it 'generates the expected client encryption key with 0x0302 dialect' do
|
2703
|
+
client.dialect = '0x0302'
|
2704
|
+
expected_enc_key =
|
2705
|
+
"\xa4\xfa\x23\xc1\xb0\x65\x84\xce\x47\x08\x5b\xe0\x64\x98\xd7\x87".b
|
2706
|
+
client.smb3_encrypt(data)
|
2707
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2708
|
+
end
|
2709
|
+
|
2710
|
+
it 'generates the expected client encryption key with 0x0311 dialect' do
|
2711
|
+
client.dialect = '0x0311'
|
2712
|
+
client.session_key =
|
2713
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2714
|
+
client.preauth_integrity_hash_value =
|
2715
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2716
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2717
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2718
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2719
|
+
expected_enc_key =
|
2720
|
+
"\xc7\x4e\xfe\x4d\x15\x48\x5b\x0b\x71\x45\x49\x26\x8a\xd9\x6c\xaa".b
|
2721
|
+
client.smb3_encrypt(data)
|
2722
|
+
expect(client.client_encryption_key).to eq expected_enc_key
|
2723
|
+
end
|
2724
|
+
end
|
2725
|
+
|
2726
|
+
describe '#smb3_decrypt' do
|
2727
|
+
let(:transform_packet) { double('TransformHeader packet') }
|
2728
|
+
let(:session_key) { "\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b }
|
2729
|
+
|
2730
|
+
before :example do
|
2731
|
+
allow(transform_packet).to receive(:decrypt)
|
2732
|
+
client.session_key = session_key
|
2733
|
+
end
|
2734
|
+
|
2735
|
+
it 'does not generate a new server encryption key if it already exists' do
|
2736
|
+
client.server_encryption_key = 'key'
|
2737
|
+
expect(RubySMB::Crypto::KDF).to_not receive(:counter_mode)
|
2738
|
+
expect(client.server_encryption_key).to eq('key')
|
2739
|
+
client.smb3_decrypt(transform_packet)
|
2740
|
+
end
|
2741
|
+
|
2742
|
+
['0x0300', '0x0302'].each do |dialect|
|
2743
|
+
context "with #{dialect} dialect" do
|
2744
|
+
before :example do
|
2745
|
+
client.dialect = dialect
|
2746
|
+
end
|
2747
|
+
|
2748
|
+
it 'generates the client encryption key with the expected parameters' do
|
2749
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2750
|
+
session_key,
|
2751
|
+
"SMB2AESCCM\x00",
|
2752
|
+
"ServerOut\x00"
|
2753
|
+
).and_call_original
|
2754
|
+
client.smb3_decrypt(transform_packet)
|
2755
|
+
end
|
2756
|
+
end
|
2757
|
+
end
|
2758
|
+
|
2759
|
+
context 'with 0x0311 dialect' do
|
2760
|
+
it 'generates the client encryption key with the expected parameters' do
|
2761
|
+
client.preauth_integrity_hash_value = ''
|
2762
|
+
client.dialect = '0x0311'
|
2763
|
+
expect(RubySMB::Crypto::KDF).to receive(:counter_mode).with(
|
2764
|
+
session_key,
|
2765
|
+
"SMBS2CCipherKey\x00",
|
2766
|
+
''
|
2767
|
+
).and_call_original
|
2768
|
+
client.smb3_decrypt(transform_packet)
|
2769
|
+
end
|
2770
|
+
end
|
2771
|
+
|
2772
|
+
it 'raises the expected exception if the dialect is incompatible' do
|
2773
|
+
client.dialect = '0x0202'
|
2774
|
+
expect { client.smb3_decrypt(transform_packet) }.to raise_error(RubySMB::Error::EncryptionError)
|
2775
|
+
end
|
2776
|
+
|
2777
|
+
it 'creates a TransformHeader packet and encrypt the data' do
|
2778
|
+
client.dialect = '0x0300'
|
2779
|
+
client.encryption_algorithm = 'AES-128-CCM'
|
2780
|
+
client.session_id = 123
|
2781
|
+
client.smb3_decrypt(transform_packet)
|
2782
|
+
expect(transform_packet).to have_received(:decrypt).with(client.server_encryption_key, algorithm: 'AES-128-CCM')
|
2783
|
+
end
|
2784
|
+
|
2785
|
+
it 'generates the expected server encryption key with 0x0302 dialect' do
|
2786
|
+
client.dialect = '0x0302'
|
2787
|
+
expected_enc_key =
|
2788
|
+
"\x65\x21\xd3\x6d\xe9\xe3\x5a\x66\x09\x61\xae\x3e\xc6\x49\x6b\xdf".b
|
2789
|
+
client.smb3_decrypt(transform_packet)
|
2790
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2791
|
+
end
|
2792
|
+
|
2793
|
+
it 'generates the expected server encryption key with 0x0311 dialect' do
|
2794
|
+
client.dialect = '0x0311'
|
2795
|
+
client.session_key =
|
2796
|
+
"\x5c\x00\x4a\x3b\xf0\xa2\x4f\x75\x4c\xb2\x74\x0a\xcf\xc4\x8e\x1a".b
|
2797
|
+
client.preauth_integrity_hash_value =
|
2798
|
+
"\x57\x77\x7d\x47\xc2\xa9\xc8\x23\x6e\x8a\xfa\x39\xe8\x77\x2f\xb0\xb6"\
|
2799
|
+
"\x01\xba\x85\x58\x77\xf5\x01\xa0\xf0\x31\x69\x6a\x64\x49\x1c\x61\xdb"\
|
2800
|
+
"\x57\x34\x19\x1b\x80\x33\x9a\xfa\x1d\x6c\x3f\xca\x44\x68\x78\x5b\xb9"\
|
2801
|
+
"\xda\x41\xfa\x83\xe5\xa9\x6f\xcf\x44\xbc\xe5\x26\x6e".b
|
2802
|
+
expected_enc_key =
|
2803
|
+
"\x8c\x2c\x31\x15\x66\xba\xa9\xab\xcf\xb2\x47\x8d\x72\xd5\xd7\x4a".b
|
2804
|
+
client.smb3_decrypt(transform_packet)
|
2805
|
+
expect(client.server_encryption_key).to eq expected_enc_key
|
2806
|
+
end
|
2807
|
+
end
|
2808
|
+
end
|
1328
2809
|
end
|
1329
2810
|
|