ruby_smb 2.0.7 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/verify.yml +57 -0
- data/README.md +0 -1
- data/examples/auth_capture.rb +71 -0
- data/lib/ruby_smb/client/negotiation.rb +11 -13
- data/lib/ruby_smb/client.rb +32 -27
- data/lib/ruby_smb/compression/lznt1.rb +164 -0
- data/lib/ruby_smb/compression.rb +7 -0
- data/lib/ruby_smb/dialect.rb +45 -0
- data/lib/ruby_smb/dispatcher/base.rb +1 -1
- data/lib/ruby_smb/dispatcher/socket.rb +1 -1
- data/lib/ruby_smb/gss/provider/authenticator.rb +42 -0
- data/lib/ruby_smb/gss/provider/ntlm.rb +303 -0
- data/lib/ruby_smb/gss/provider.rb +35 -0
- data/lib/ruby_smb/gss.rb +56 -63
- data/lib/ruby_smb/ntlm.rb +45 -0
- data/lib/ruby_smb/server/server_client/negotiation.rb +155 -0
- data/lib/ruby_smb/server/server_client/session_setup.rb +82 -0
- data/lib/ruby_smb/server/server_client.rb +163 -0
- data/lib/ruby_smb/server.rb +54 -0
- data/lib/ruby_smb/signing.rb +59 -0
- data/lib/ruby_smb/smb1/packet/negotiate_response.rb +11 -11
- data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +1 -1
- data/lib/ruby_smb/smb1/packet/session_setup_request.rb +1 -1
- data/lib/ruby_smb/smb1/tree.rb +1 -1
- data/lib/ruby_smb/smb2/negotiate_context.rb +18 -2
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
- data/lib/ruby_smb/smb2/packet/negotiate_request.rb +9 -0
- data/lib/ruby_smb/smb2/packet/negotiate_response.rb +0 -1
- data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -2
- data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +1 -1
- data/lib/ruby_smb/smb2.rb +3 -1
- data/lib/ruby_smb/version.rb +1 -1
- data/lib/ruby_smb.rb +5 -3
- data/spec/lib/ruby_smb/client_spec.rb +24 -16
- data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm/account_spec.rb +32 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm/authenticator_spec.rb +101 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm/os_version_spec.rb +32 -0
- data/spec/lib/ruby_smb/gss/provider/ntlm_spec.rb +113 -0
- data/spec/lib/ruby_smb/server/server_client_spec.rb +156 -0
- data/spec/lib/ruby_smb/server_spec.rb +32 -0
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +4 -4
- data/spec/lib/ruby_smb/smb2/negotiate_context_spec.rb +2 -2
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -5
- data/spec/spec_helper.rb +1 -1
- data.tar.gz.sig +3 -1
- metadata +30 -4
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -6
- data/lib/ruby_smb/client/signing.rb +0 -64
data/lib/ruby_smb/gss.rb
CHANGED
@@ -2,6 +2,27 @@ module RubySMB
|
|
2
2
|
# module containing methods required for using the [GSS-API](http://www.rfc-editor.org/rfc/rfc2743.txt)
|
3
3
|
# for Secure Protected Negotiation(SPNEGO) in SMB Authentication.
|
4
4
|
module Gss
|
5
|
+
require 'ruby_smb/gss/provider'
|
6
|
+
|
7
|
+
OID_SPNEGO = OpenSSL::ASN1::ObjectId.new('1.3.6.1.5.5.2')
|
8
|
+
OID_NEGOEX = OpenSSL::ASN1::ObjectId.new('1.3.6.1.4.1.311.2.2.30')
|
9
|
+
OID_NTLMSSP = OpenSSL::ASN1::ObjectId.new('1.3.6.1.4.1.311.2.2.10')
|
10
|
+
|
11
|
+
# Allow safe navigation of a decoded ASN.1 data structure. Similar to Ruby's
|
12
|
+
# builtin Hash#dig method but using the #value attribute of each ASN object.
|
13
|
+
#
|
14
|
+
# @param asn The ASN object to apply the traversal path on.
|
15
|
+
# @param [Array] path The path to traverse, each element is passed to the
|
16
|
+
# ASN object's #value's #[] operator.
|
17
|
+
def self.asn1dig(asn, *path)
|
18
|
+
path.each do |part|
|
19
|
+
return nil unless asn&.value
|
20
|
+
asn = asn.value[part]
|
21
|
+
end
|
22
|
+
|
23
|
+
asn
|
24
|
+
end
|
25
|
+
|
5
26
|
# Cargo culted from Rex. Hacked Together ASN1 encoding that works for our GSS purposes
|
6
27
|
# @todo Document these magic numbers
|
7
28
|
def self.asn1encode(str = '')
|
@@ -25,78 +46,50 @@ module RubySMB
|
|
25
46
|
end
|
26
47
|
|
27
48
|
# Create a GSS Security Blob of an NTLM Type 1 Message.
|
28
|
-
# This code has been cargo culted and needs to be researched
|
29
|
-
# and refactored into something better later.
|
30
|
-
# @todo Refactor this into non-magical code
|
31
49
|
def self.gss_type1(type1)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
type1
|
48
|
-
)
|
49
|
-
)
|
50
|
-
)
|
51
|
-
)
|
52
|
-
)
|
50
|
+
OpenSSL::ASN1::ASN1Data.new([
|
51
|
+
OID_SPNEGO,
|
52
|
+
OpenSSL::ASN1::ASN1Data.new([
|
53
|
+
OpenSSL::ASN1::Sequence.new([
|
54
|
+
OpenSSL::ASN1::ASN1Data.new([
|
55
|
+
OpenSSL::ASN1::Sequence.new([
|
56
|
+
OID_NTLMSSP
|
57
|
+
])
|
58
|
+
], 0, :CONTEXT_SPECIFIC),
|
59
|
+
OpenSSL::ASN1::ASN1Data.new([
|
60
|
+
OpenSSL::ASN1::OctetString.new(type1)
|
61
|
+
], 2, :CONTEXT_SPECIFIC)
|
62
|
+
])
|
63
|
+
], 0, :CONTEXT_SPECIFIC)
|
64
|
+
], 0, :APPLICATION).to_der
|
53
65
|
end
|
54
66
|
|
55
67
|
# Create a GSS Security Blob of an NTLM Type 2 Message.
|
56
|
-
# This code has been cargo culted and needs to be researched
|
57
|
-
# and refactored into something better later.
|
58
68
|
def self.gss_type2(type2)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
"\xa2" + asn1encode(
|
73
|
-
"\x04" + asn1encode(
|
74
|
-
type2
|
75
|
-
)
|
76
|
-
)
|
77
|
-
)
|
78
|
-
)
|
79
|
-
|
80
|
-
blob
|
69
|
+
OpenSSL::ASN1::ASN1Data.new([
|
70
|
+
OpenSSL::ASN1::Sequence.new([
|
71
|
+
OpenSSL::ASN1::ASN1Data.new([
|
72
|
+
OpenSSL::ASN1::Enumerated.new(OpenSSL::BN.new(1))
|
73
|
+
], 0, :CONTEXT_SPECIFIC),
|
74
|
+
OpenSSL::ASN1::ASN1Data.new([
|
75
|
+
OID_NTLMSSP
|
76
|
+
], 1, :CONTEXT_SPECIFIC),
|
77
|
+
OpenSSL::ASN1::ASN1Data.new([
|
78
|
+
OpenSSL::ASN1::OctetString.new(type2)
|
79
|
+
], 2, :CONTEXT_SPECIFIC)
|
80
|
+
])
|
81
|
+
], 1, :CONTEXT_SPECIFIC).to_der
|
81
82
|
end
|
82
83
|
|
83
84
|
# Create a GSS Security Blob of an NTLM Type 3 Message.
|
84
|
-
# This code has been cargo culted and needs to be researched
|
85
|
-
# and refactored into something better later.
|
86
|
-
# @todo Refactor this into non-magical code
|
87
85
|
def self.gss_type3(type3)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
)
|
96
|
-
)
|
97
|
-
)
|
98
|
-
|
99
|
-
gss
|
86
|
+
OpenSSL::ASN1::ASN1Data.new([
|
87
|
+
OpenSSL::ASN1::Sequence.new([
|
88
|
+
OpenSSL::ASN1::ASN1Data.new([
|
89
|
+
OpenSSL::ASN1::OctetString.new(type3)
|
90
|
+
], 2, :CONTEXT_SPECIFIC)
|
91
|
+
])
|
92
|
+
], 1, :CONTEXT_SPECIFIC).to_der
|
100
93
|
end
|
101
94
|
end
|
102
95
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module NTLM
|
3
|
+
# [[MS-NLMP] 2.2.2.5](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832)
|
4
|
+
NEGOTIATE_FLAGS = {
|
5
|
+
:UNICODE => 1 << 0,
|
6
|
+
:OEM => 1 << 1,
|
7
|
+
:REQUEST_TARGET => 1 << 2,
|
8
|
+
:SIGN => 1 << 4,
|
9
|
+
:SEAL => 1 << 5,
|
10
|
+
:DATAGRAM => 1 << 6,
|
11
|
+
:LAN_MANAGER_KEY => 1 << 7,
|
12
|
+
:NTLM => 1 << 9,
|
13
|
+
:NT_ONLY => 1 << 10,
|
14
|
+
:ANONYMOUS => 1 << 11,
|
15
|
+
:OEM_DOMAIN_SUPPLIED => 1 << 12,
|
16
|
+
:OEM_WORKSTATION_SUPPLIED => 1 << 13,
|
17
|
+
:ALWAYS_SIGN => 1 << 15,
|
18
|
+
:TARGET_TYPE_DOMAIN => 1 << 16,
|
19
|
+
:TARGET_TYPE_SERVER => 1 << 17,
|
20
|
+
:TARGET_TYPE_SHARE => 1 << 18,
|
21
|
+
:EXTENDED_SECURITY => 1 << 19,
|
22
|
+
:IDENTIFY => 1 << 20,
|
23
|
+
:NON_NT_SESSION => 1 << 22,
|
24
|
+
:TARGET_INFO => 1 << 23,
|
25
|
+
:VERSION_INFO => 1 << 25,
|
26
|
+
:KEY128 => 1 << 29,
|
27
|
+
:KEY_EXCHANGE => 1 << 30,
|
28
|
+
:KEY56 => 1 << 31
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
class OSVersion < BinData::Record
|
32
|
+
endian :big
|
33
|
+
|
34
|
+
uint8 :major
|
35
|
+
uint8 :minor
|
36
|
+
uint16 :build
|
37
|
+
uint32 :ntlm_revision, initial_value: 15
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"Version #{major}.#{minor} (Build #{build}); NTLM Current Revision #{ntlm_revision}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
class Server
|
5
|
+
class ServerClient
|
6
|
+
module Negotiation
|
7
|
+
#
|
8
|
+
# Handle an SMB negotiation request. Once negotiation is complete, the state will be updated to :session_setup.
|
9
|
+
# At this point the @dialect will have been set along with other dialect-specific values.
|
10
|
+
#
|
11
|
+
# @param [String] raw_request the negotiation request to process
|
12
|
+
def handle_negotiate(raw_request)
|
13
|
+
response = nil
|
14
|
+
case raw_request[0...4].unpack1('L>')
|
15
|
+
when RubySMB::SMB1::SMB_PROTOCOL_ID
|
16
|
+
request = SMB1::Packet::NegotiateRequest.read(raw_request)
|
17
|
+
response = do_negotiate_smb1(request) if request.is_a?(SMB1::Packet::NegotiateRequest)
|
18
|
+
when RubySMB::SMB2::SMB2_PROTOCOL_ID
|
19
|
+
request = SMB2::Packet::NegotiateRequest.read(raw_request)
|
20
|
+
response = do_negotiate_smb2(request) if request.is_a?(SMB2::Packet::NegotiateRequest)
|
21
|
+
end
|
22
|
+
|
23
|
+
if response.nil?
|
24
|
+
disconnect!
|
25
|
+
else
|
26
|
+
send_packet(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_negotiate_smb1(request)
|
33
|
+
client_dialects = request.dialects.map(&:dialect_string).map(&:value)
|
34
|
+
|
35
|
+
if client_dialects.include?(Client::SMB1_DIALECT_SMB2_WILDCARD) && \
|
36
|
+
@server.dialects.any? { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB2 }
|
37
|
+
response = SMB2::Packet::NegotiateResponse.new
|
38
|
+
response.smb2_header.credits = 1
|
39
|
+
response.security_mode.signing_enabled = 1
|
40
|
+
response.dialect_revision = SMB2::SMB2_WILDCARD_REVISION
|
41
|
+
response.server_guid = @server.guid
|
42
|
+
|
43
|
+
response.max_transact_size = 0x800000
|
44
|
+
response.max_read_size = 0x800000
|
45
|
+
response.max_write_size = 0x800000
|
46
|
+
response.system_time.set(Time.now)
|
47
|
+
response.security_buffer_offset = response.security_buffer.abs_offset
|
48
|
+
response.security_buffer = process_gss.buffer
|
49
|
+
return response
|
50
|
+
end
|
51
|
+
|
52
|
+
server_dialects = @server.dialects.select { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB1 }
|
53
|
+
dialect = (server_dialects & client_dialects).first
|
54
|
+
if dialect.nil?
|
55
|
+
# 'NT LM 0.12' is currently the only supported dialect
|
56
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/80850595-e301-4464-9745-58e4945eb99b
|
57
|
+
response = SMB1::Packet::NegotiateResponse.new
|
58
|
+
response.parameter_block.word_count = 1
|
59
|
+
response.parameter_block.dialect_index = 0xffff
|
60
|
+
response.data_block.byte_count = 0
|
61
|
+
return response
|
62
|
+
end
|
63
|
+
|
64
|
+
response = SMB1::Packet::NegotiateResponseExtended.new
|
65
|
+
response.parameter_block.dialect_index = client_dialects.index(dialect)
|
66
|
+
response.parameter_block.max_mpx_count = 50
|
67
|
+
response.parameter_block.max_number_vcs = 1
|
68
|
+
response.parameter_block.max_buffer_size = 16644
|
69
|
+
response.parameter_block.max_raw_size = 65536
|
70
|
+
server_time = Time.now
|
71
|
+
response.parameter_block.system_time.set(server_time)
|
72
|
+
response.parameter_block.server_time_zone = server_time.utc_offset
|
73
|
+
response.data_block.server_guid = @server.guid
|
74
|
+
response.data_block.security_blob = process_gss.buffer
|
75
|
+
|
76
|
+
@state = :session_setup
|
77
|
+
@dialect = dialect
|
78
|
+
response
|
79
|
+
end
|
80
|
+
|
81
|
+
def do_negotiate_smb2(request)
|
82
|
+
client_dialects = request.dialects.map { |d| "0x%04x" % d }
|
83
|
+
server_dialects = @server.dialects.select { |dialect| Dialect[dialect].order == Dialect::ORDER_SMB2 }
|
84
|
+
dialect = (server_dialects & client_dialects).first
|
85
|
+
|
86
|
+
response = SMB2::Packet::NegotiateResponse.new
|
87
|
+
response.smb2_header.credits = 1
|
88
|
+
response.security_mode.signing_enabled = 1
|
89
|
+
response.server_guid = @server.guid
|
90
|
+
response.max_transact_size = 0x800000
|
91
|
+
response.max_read_size = 0x800000
|
92
|
+
response.max_write_size = 0x800000
|
93
|
+
response.system_time.set(Time.now)
|
94
|
+
if dialect.nil?
|
95
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
|
96
|
+
# > If a common dialect is not found, the server MUST fail the request with STATUS_NOT_SUPPORTED.
|
97
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_NOT_SUPPORTED.value
|
98
|
+
return response
|
99
|
+
end
|
100
|
+
|
101
|
+
contexts = []
|
102
|
+
hash_algorithm = hash_value = nil
|
103
|
+
if dialect == '0x0311'
|
104
|
+
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/b39f253e-4963-40df-8dff-2f9040ebbeb1
|
105
|
+
nc = request.find_negotiate_context(SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES)
|
106
|
+
hash_algorithm = SMB2::PreauthIntegrityCapabilities::HASH_ALGORITM_MAP[nc&.data&.hash_algorithms&.first]
|
107
|
+
hash_value = "\x00" * 64
|
108
|
+
unless hash_algorithm
|
109
|
+
response.smb2_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_PARAMETER.value
|
110
|
+
return response
|
111
|
+
end
|
112
|
+
|
113
|
+
contexts << SMB2::NegotiateContext.new(
|
114
|
+
context_type: SMB2::NegotiateContext::SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
|
115
|
+
data: {
|
116
|
+
hash_algorithms: [ SMB2::PreauthIntegrityCapabilities::SHA_512 ],
|
117
|
+
salt: SecureRandom.random_bytes(32)
|
118
|
+
}
|
119
|
+
)
|
120
|
+
|
121
|
+
nc = request.find_negotiate_context(SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES)
|
122
|
+
cipher = nc&.data&.ciphers&.first
|
123
|
+
cipher = 0 unless SMB2::EncryptionCapabilities::ENCRYPTION_ALGORITHM_MAP.include? cipher
|
124
|
+
contexts << SMB2::NegotiateContext.new(
|
125
|
+
context_type: SMB2::NegotiateContext::SMB2_ENCRYPTION_CAPABILITIES,
|
126
|
+
data: {
|
127
|
+
ciphers: [ cipher ]
|
128
|
+
}
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
# the order in which the response is built is important to ensure it is valid
|
133
|
+
response.dialect_revision = dialect.to_i(16)
|
134
|
+
response.security_buffer_offset = response.security_buffer.abs_offset
|
135
|
+
response.security_buffer = process_gss.buffer
|
136
|
+
if dialect == '0x0311'
|
137
|
+
response.negotiate_context_offset = response.negotiate_context_list.abs_offset
|
138
|
+
contexts.each { |nc| response.add_negotiate_context(nc) }
|
139
|
+
end
|
140
|
+
@preauth_integrity_hash_algorithm = hash_algorithm
|
141
|
+
@preauth_integrity_hash_value = hash_value
|
142
|
+
|
143
|
+
if dialect == '0x0311'
|
144
|
+
update_preauth_hash(request)
|
145
|
+
update_preauth_hash(response)
|
146
|
+
end
|
147
|
+
|
148
|
+
@state = :session_setup
|
149
|
+
@dialect = dialect
|
150
|
+
response
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
class ServerClient
|
4
|
+
module SessionSetup
|
5
|
+
#
|
6
|
+
# Setup a new session based on the negotiated dialect. Once session setup is complete, the state will be updated
|
7
|
+
# to :authenticated.
|
8
|
+
#
|
9
|
+
# @param [String] raw_request the session setup request to process
|
10
|
+
def handle_session_setup(raw_request)
|
11
|
+
response = nil
|
12
|
+
|
13
|
+
case metadialect.order
|
14
|
+
when Dialect::ORDER_SMB1
|
15
|
+
request = SMB1::Packet::SessionSetupRequest.read(raw_request)
|
16
|
+
response = do_session_setup_smb1(request)
|
17
|
+
when Dialect::ORDER_SMB2
|
18
|
+
request = SMB2::Packet::SessionSetupRequest.read(raw_request)
|
19
|
+
response = do_session_setup_smb2(request)
|
20
|
+
end
|
21
|
+
|
22
|
+
if response.nil?
|
23
|
+
disconnect!
|
24
|
+
else
|
25
|
+
send_packet(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_session_setup_smb1(request)
|
32
|
+
gss_result = process_gss(request.data_block.security_blob)
|
33
|
+
return if gss_result.nil?
|
34
|
+
|
35
|
+
response = SMB1::Packet::SessionSetupResponse.new
|
36
|
+
response.smb_header.pid_low = request.smb_header.pid_low
|
37
|
+
response.smb_header.uid = rand(0x10000)
|
38
|
+
response.smb_header.mid = request.smb_header.mid
|
39
|
+
response.smb_header.nt_status = gss_result.nt_status.value
|
40
|
+
response.smb_header.flags.reply = true
|
41
|
+
response.smb_header.flags2.unicode = true
|
42
|
+
response.smb_header.flags2.extended_security = true
|
43
|
+
unless gss_result.buffer.nil?
|
44
|
+
response.parameter_block.security_blob_length = gss_result.buffer.length
|
45
|
+
response.data_block.security_blob = gss_result.buffer
|
46
|
+
end
|
47
|
+
|
48
|
+
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
49
|
+
@state = :authenticated
|
50
|
+
@identity = gss_result.identity
|
51
|
+
end
|
52
|
+
|
53
|
+
response
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_session_setup_smb2(request)
|
57
|
+
gss_result = process_gss(request.buffer)
|
58
|
+
return if gss_result.nil?
|
59
|
+
|
60
|
+
response = SMB2::Packet::SessionSetupResponse.new
|
61
|
+
response.smb2_header.nt_status = gss_result.nt_status.value
|
62
|
+
response.smb2_header.credits = 1
|
63
|
+
response.smb2_header.message_id = @message_id += 1
|
64
|
+
response.smb2_header.session_id = @session_id = @session_id || SecureRandom.random_bytes(4).unpack1('V')
|
65
|
+
response.buffer = gss_result.buffer
|
66
|
+
|
67
|
+
update_preauth_hash(request) if @dialect == '0x0311'
|
68
|
+
if gss_result.nt_status == WindowsError::NTStatus::STATUS_SUCCESS
|
69
|
+
@state = :authenticated
|
70
|
+
@identity = gss_result.identity
|
71
|
+
@session_key = @gss_authenticator.session_key
|
72
|
+
elsif gss_result.nt_status == WindowsError::NTStatus::STATUS_MORE_PROCESSING_REQUIRED && @dialect == '0x0311'
|
73
|
+
update_preauth_hash(response)
|
74
|
+
end
|
75
|
+
|
76
|
+
response
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module RubySMB
|
2
|
+
class Server
|
3
|
+
# This class represents a single connected client to the server. It stores and processes connection specific related
|
4
|
+
# information.
|
5
|
+
class ServerClient
|
6
|
+
|
7
|
+
require 'ruby_smb/dialect'
|
8
|
+
require 'ruby_smb/signing'
|
9
|
+
require 'ruby_smb/server/server_client/negotiation'
|
10
|
+
require 'ruby_smb/server/server_client/session_setup'
|
11
|
+
|
12
|
+
include RubySMB::Signing
|
13
|
+
include RubySMB::Server::ServerClient::Negotiation
|
14
|
+
include RubySMB::Server::ServerClient::SessionSetup
|
15
|
+
|
16
|
+
attr_reader :dialect, :identity, :state, :session_key
|
17
|
+
|
18
|
+
# @param [Server] server the server that accepted this connection
|
19
|
+
# @param [Dispatcher::Socket] dispatcher the connection's socket dispatcher
|
20
|
+
def initialize(server, dispatcher)
|
21
|
+
@server = server
|
22
|
+
@dispatcher = dispatcher
|
23
|
+
@state = :negotiate
|
24
|
+
@dialect = nil
|
25
|
+
@message_id = 0
|
26
|
+
@session_id = nil
|
27
|
+
@session_key = nil
|
28
|
+
@gss_authenticator = server.gss_provider.new_authenticator(self)
|
29
|
+
@identity = nil
|
30
|
+
@tree_connections = {}
|
31
|
+
@preauth_integrity_hash_algorithm = nil
|
32
|
+
@preauth_integrity_hash_value = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# The dialects metadata definition.
|
37
|
+
#
|
38
|
+
# @return [Dialect::Definition]
|
39
|
+
def metadialect
|
40
|
+
Dialect::ALL[@dialect]
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# The peername of the connected socket. This is a combination of the IPv4 or IPv6 address and port number.
|
45
|
+
#
|
46
|
+
# @example Parse the value into an IP address
|
47
|
+
# ::Socket::unpack_sockaddr_in(server_client.getpeername)
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def getpeername
|
51
|
+
@dispatcher.tcp_socket.getpeername
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Handle an authenticated request. This is the main handler for all requests after the connection has been
|
56
|
+
# authenticated.
|
57
|
+
#
|
58
|
+
# @param [String] raw_request the request that should be handled
|
59
|
+
def handle_authenticated(raw_request)
|
60
|
+
response = nil
|
61
|
+
|
62
|
+
case raw_request[0...4].unpack1('L>')
|
63
|
+
when RubySMB::SMB1::SMB_PROTOCOL_ID
|
64
|
+
raise NotImplementedError
|
65
|
+
when RubySMB::SMB2::SMB2_PROTOCOL_ID
|
66
|
+
raise NotImplementedError
|
67
|
+
end
|
68
|
+
|
69
|
+
if response.nil?
|
70
|
+
disconnect!
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
send_packet(response)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Process a GSS authentication buffer. If no buffer is specified, the request is assumed to be the first in the
|
79
|
+
# negotiation sequence.
|
80
|
+
#
|
81
|
+
# @param [String, nil] buffer the request GSS request buffer that should be processed
|
82
|
+
# @return [Gss::Provider::Result] the result of the processed GSS request
|
83
|
+
def process_gss(buffer=nil)
|
84
|
+
@gss_authenticator.process(buffer)
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Run the processing loop to receive and handle requests. This loop runs until an exception occurs or the
|
89
|
+
# dispatcher socket is closed.
|
90
|
+
#
|
91
|
+
def run
|
92
|
+
loop do
|
93
|
+
begin
|
94
|
+
raw_request = recv_packet
|
95
|
+
rescue RubySMB::Error::CommunicationError
|
96
|
+
break
|
97
|
+
end
|
98
|
+
|
99
|
+
case @state
|
100
|
+
when :negotiate
|
101
|
+
handle_negotiate(raw_request)
|
102
|
+
when :session_setup
|
103
|
+
handle_session_setup(raw_request)
|
104
|
+
when :authenticated
|
105
|
+
handle_authenticated(raw_request)
|
106
|
+
end
|
107
|
+
|
108
|
+
break if @dispatcher.tcp_socket.closed?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Disconnect the remote client.
|
114
|
+
#
|
115
|
+
def disconnect!
|
116
|
+
@state = nil
|
117
|
+
@dispatcher.tcp_socket.close
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Receive a single SMB packet from the dispatcher.
|
122
|
+
#
|
123
|
+
# @return [String] the raw packet
|
124
|
+
def recv_packet
|
125
|
+
@dispatcher.recv_packet
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Send a single SMB packet using the dispatcher. If necessary, the packet will be signed.
|
130
|
+
#
|
131
|
+
# @param [GenericPacket] packet the packet to send
|
132
|
+
def send_packet(packet)
|
133
|
+
if @state == :authenticated && @identity != Gss::Provider::IDENTITY_ANONYMOUS && !@session_key.nil?
|
134
|
+
case metadialect.family
|
135
|
+
when Dialect::FAMILY_SMB2
|
136
|
+
packet = smb2_sign(packet)
|
137
|
+
when Dialect::FAMILY_SMB3
|
138
|
+
packet = smb3_sign(packet)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
@dispatcher.send_packet(packet)
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Update the preauth integrity hash as used by dialect 3.1.1 for various cryptographic operations. The algorithm
|
147
|
+
# and hash values must have been initialized prior to calling this.
|
148
|
+
#
|
149
|
+
# @param [String] data the data with which to update the preauth integrity hash
|
150
|
+
def update_preauth_hash(data)
|
151
|
+
unless @preauth_integrity_hash_algorithm
|
152
|
+
raise RubySMB::Error::EncryptionError.new(
|
153
|
+
'Cannot compute the Preauth Integrity Hash value: Preauth Integrity Hash Algorithm is nil'
|
154
|
+
)
|
155
|
+
end
|
156
|
+
@preauth_integrity_hash_value = OpenSSL::Digest.digest(
|
157
|
+
@preauth_integrity_hash_algorithm,
|
158
|
+
@preauth_integrity_hash_value + data.to_binary_s
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|