ruby_smb 3.2.7 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/ruby_smb/server/server_client/share_io.rb +2 -2
- data/lib/ruby_smb/server/share/provider/disk.rb +2 -2
- data/lib/ruby_smb/server/share/provider/processor.rb +25 -0
- data/lib/ruby_smb/server/share/provider/virtual_disk.rb +3 -3
- data/lib/ruby_smb/server/share/provider.rb +48 -1
- data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +1 -1
- data/lib/ruby_smb/smb2/tree.rb +3 -1
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/dcerpc/response_spec.rb +4 -6
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +5 -0
- data.tar.gz.sig +0 -0
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f696c845250348795f67d3d5b163c65b432acdbcd01bc86d0a6cd81ddcbfc67
|
4
|
+
data.tar.gz: b43dcb4fa6ff4497e61329b8b5a17049a6b445fcdf5c3df61dc380960ea02b9c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8368cf1844e47161a57051d7b8dfa807b665c898153636b1baedb2211acd4685ef67c5ba167581ce84a9ec7c8c17e812d1f876bb70f34b647bb075bb94861349
|
7
|
+
data.tar.gz: 3b33a185fb3b981147b005e13793aa1f8a09e11c2e5e0dc9b75469ef980ec019bc2dd5e9bef187910f9b5be012d7ca710799b45aa0bdf79547ae3d48de8e22e7
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -11,7 +11,7 @@ module RubySMB
|
|
11
11
|
end
|
12
12
|
|
13
13
|
logger.debug("Received #{SMB1::Commands.name(request.smb_header.command)} request for share: #{share_processor.provider.name}")
|
14
|
-
share_processor.
|
14
|
+
share_processor.share_io(__callee__, request)
|
15
15
|
end
|
16
16
|
|
17
17
|
alias :do_close_smb1 :proxy_share_io_smb1
|
@@ -29,7 +29,7 @@ module RubySMB
|
|
29
29
|
end
|
30
30
|
|
31
31
|
logger.debug("Received #{SMB2::Commands.name(request.smb2_header.command)} request for share: #{share_processor.provider.name}")
|
32
|
-
share_processor.
|
32
|
+
share_processor.share_io(__callee__, request)
|
33
33
|
end
|
34
34
|
|
35
35
|
alias :do_close_smb2 :proxy_share_io_smb2
|
@@ -14,13 +14,13 @@ module RubySMB
|
|
14
14
|
# @param [String] name The name of this share.
|
15
15
|
# @param [String, Pathname] path The local file system path to share. This path must be an absolute path to an existing
|
16
16
|
# directory.
|
17
|
-
def initialize(name, path)
|
17
|
+
def initialize(name, path, hooks: nil)
|
18
18
|
path = Pathname.new(File.expand_path(path)) if path.is_a?(String)
|
19
19
|
raise ArgumentError.new('path must be a directory') unless path.directory? # it needs to exist
|
20
20
|
raise ArgumentError.new('path must be absolute') unless path.absolute? # it needs to be absolute so it is independent of the cwd
|
21
21
|
|
22
22
|
@path = path
|
23
|
-
super(name)
|
23
|
+
super(name, hooks: hooks)
|
24
24
|
end
|
25
25
|
|
26
26
|
attr_accessor :path
|
@@ -80,6 +80,31 @@ module RubySMB
|
|
80
80
|
@server_client.server
|
81
81
|
end
|
82
82
|
|
83
|
+
# Forward a share IO method for a particular request. This is a choke point to allow any hooks that were
|
84
|
+
# registered with the share provider to be executed before and after the specified method is invoked to
|
85
|
+
# process the request and generate the response. This is used for both SMB1 and SMB2 requests.
|
86
|
+
#
|
87
|
+
# @param [Symbol] method_name The method name to forward the request to
|
88
|
+
# @param [RubySMB::GenericPacket] request The request packet to be processed
|
89
|
+
# @return [RubySMB::GenericPacket]
|
90
|
+
def share_io(method_name, request)
|
91
|
+
@provider.hooks.each do |hook|
|
92
|
+
next unless hook.request_class == request.class && hook.location == :before
|
93
|
+
|
94
|
+
request = hook.callback.call(@session, request) || request
|
95
|
+
end
|
96
|
+
|
97
|
+
response = send(method_name, request)
|
98
|
+
|
99
|
+
@provider.hooks.each do |hook|
|
100
|
+
next unless hook.request_class == request.class && hook.location == :after
|
101
|
+
|
102
|
+
response = hook.callback.call(@session, request, response) || response
|
103
|
+
end
|
104
|
+
|
105
|
+
response
|
106
|
+
end
|
107
|
+
|
83
108
|
# The underlying share provider that this is a processor for.
|
84
109
|
# @!attribute [r] provider
|
85
110
|
# @return [RubySMB::Server::Share::Provider::Base]
|
@@ -14,9 +14,9 @@ module RubySMB
|
|
14
14
|
private_constant :HASH_METHODS
|
15
15
|
|
16
16
|
# @param [String] name The name of this share.
|
17
|
-
def initialize(name)
|
17
|
+
def initialize(name, hooks: nil)
|
18
18
|
@vfs = {}
|
19
|
-
super(name, add(VirtualPathname.new(self, File::SEPARATOR)))
|
19
|
+
super(name, add(VirtualPathname.new(self, File::SEPARATOR)), hooks: hooks)
|
20
20
|
end
|
21
21
|
|
22
22
|
# Add a dynamic file to the virtual file system. A dynamic file is one whose contents are generated by the
|
@@ -86,7 +86,7 @@ module RubySMB
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def new_processor(server_client, session)
|
89
|
-
scoped_virtual_disk = self.class.new(@name)
|
89
|
+
scoped_virtual_disk = self.class.new(@name, hooks: @hooks)
|
90
90
|
@vfs.each_value do |path|
|
91
91
|
path = path.dup
|
92
92
|
path.virtual_disk = scoped_virtual_disk if path.is_a?(VirtualPathname)
|
@@ -6,9 +6,51 @@ module RubySMB
|
|
6
6
|
# type and name. It is shared across all client connections and
|
7
7
|
# sessions.
|
8
8
|
class Base
|
9
|
+
Hook = Struct.new(:request_class, :location, :callback)
|
10
|
+
|
9
11
|
# @param [String] name The name of this share.
|
10
|
-
def initialize(name)
|
12
|
+
def initialize(name, hooks: nil)
|
11
13
|
@name = name
|
14
|
+
@hooks = hooks || []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add a hook to be called when the specified request class is processed. Any hook that was previously
|
18
|
+
# installed for the request class and location will be removed. A hook installed with a location of :before
|
19
|
+
# will be called with the session and request as the only two arguments. The return value, if provided, will
|
20
|
+
# replace the request that is to be processed. A hook installed with a location of :after will be called with
|
21
|
+
# the session, request and response as the only three arguments. The return value, if provided, will replace
|
22
|
+
# the response that is to be sent to the client.
|
23
|
+
#
|
24
|
+
# @param [RubySMB::GenericPacket] request_class The request class to register the hook for.
|
25
|
+
# @param [Proc] callback The routine to be executed when the request class is being processed.
|
26
|
+
# @param [Symbol] location When the callback should be invoked. Must be either :before or :after.
|
27
|
+
def add_hook(request_class, callback: nil, location: :before, &block)
|
28
|
+
unless %i[ before after ].include?(location)
|
29
|
+
raise ArgumentError, 'the location argument must be :before or :after'
|
30
|
+
end
|
31
|
+
|
32
|
+
unless callback.nil? ^ block.nil?
|
33
|
+
raise ArgumentError, 'either a callback or a block must be specified'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Remove any hooks that were previously installed, this enforces that only one hook can be present at a time
|
37
|
+
# for any particular request class and location combination.
|
38
|
+
remove_hook(request_class, location: location)
|
39
|
+
@hooks << Hook.new(request_class, location, callback || block)
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
# Remove a hook for the specified request class.
|
45
|
+
#
|
46
|
+
# @param [RubySMB::GenericPacket] request_class The request class to register the hook for.
|
47
|
+
# @param [Symbol] location When the callback should be invoked.
|
48
|
+
def remove_hook(request_class, location: :before)
|
49
|
+
@hooks.filter! do |hook|
|
50
|
+
hook.request_class == request_class && hook.location == location
|
51
|
+
end
|
52
|
+
|
53
|
+
nil
|
12
54
|
end
|
13
55
|
|
14
56
|
# Create a new, session-specific processor instance for this share.
|
@@ -28,6 +70,11 @@ module RubySMB
|
|
28
70
|
# @!attribute [r] name
|
29
71
|
# @return [String]
|
30
72
|
attr_accessor :name
|
73
|
+
|
74
|
+
# The hooks installed for this share.
|
75
|
+
# @!attribute [r] hooks
|
76
|
+
# @return [Array]
|
77
|
+
attr_accessor :hooks
|
31
78
|
end
|
32
79
|
end
|
33
80
|
end
|
@@ -35,7 +35,7 @@ module RubySMB
|
|
35
35
|
end
|
36
36
|
|
37
37
|
uint64 :allocation_size, label: 'Allocation Size'
|
38
|
-
smb_ext_file_attributes :ext_file_attributes, label: '
|
38
|
+
smb_ext_file_attributes :ext_file_attributes, label: 'Extended File Attributes'
|
39
39
|
share_access :share_access, label: 'Share Access'
|
40
40
|
# The following constants are defined in RubySMB::Dispositions
|
41
41
|
uint32 :create_disposition, label: 'Create Disposition'
|
data/lib/ruby_smb/smb2/tree.rb
CHANGED
@@ -85,6 +85,7 @@ module RubySMB
|
|
85
85
|
# @raise [RubySMB::Error::InvalidPacket] if the response is not a QueryDirectoryResponse packet
|
86
86
|
def list(directory: nil, pattern: '*', type: RubySMB::Fscc::FileInformation::FileIdFullDirectoryInformation)
|
87
87
|
create_response = open_directory(directory: directory)
|
88
|
+
opened_directory = RubySMB::SMB2::File.new(tree: self, response: create_response, name: directory)
|
88
89
|
file_id = create_response.file_id
|
89
90
|
|
90
91
|
directory_request = RubySMB::SMB2::Packet::QueryDirectoryRequest.new
|
@@ -127,8 +128,9 @@ module RubySMB
|
|
127
128
|
# Reset the message id so the client can update appropriately.
|
128
129
|
directory_request.smb2_header.message_id = 0
|
129
130
|
end
|
130
|
-
|
131
131
|
files
|
132
|
+
ensure
|
133
|
+
opened_directory.close if opened_directory
|
132
134
|
end
|
133
135
|
|
134
136
|
# 'Opens' a directory file on the remote end, using a CreateRequest. This
|
data/lib/ruby_smb/version.rb
CHANGED
@@ -131,7 +131,7 @@ RSpec.describe RubySMB::Dcerpc::Response do
|
|
131
131
|
end
|
132
132
|
|
133
133
|
describe '#stub_length' do
|
134
|
-
let(:stub_length) { rand(0xFF) }
|
134
|
+
let(:stub_length) { rand(1..0xFF) }
|
135
135
|
before :example do
|
136
136
|
packet.stub = 'A' * stub_length
|
137
137
|
end
|
@@ -142,7 +142,7 @@ RSpec.describe RubySMB::Dcerpc::Response do
|
|
142
142
|
|
143
143
|
context 'with auth verifier' do
|
144
144
|
it 'returns the correct stub length' do
|
145
|
-
auth_size = rand(0xFF)
|
145
|
+
auth_size = rand(1..0xFF)
|
146
146
|
packet.pdu_header.auth_length = auth_size
|
147
147
|
packet.auth_value = 'B' * auth_size
|
148
148
|
expect(packet.stub_length).to eq(stub_length + packet.auth_pad.num_bytes)
|
@@ -152,8 +152,8 @@ RSpec.describe RubySMB::Dcerpc::Response do
|
|
152
152
|
|
153
153
|
describe '#read' do
|
154
154
|
let(:response) { described_class.new }
|
155
|
-
let(:auth_size) { rand(0xFF) }
|
156
|
-
let(:stub_size) { rand(0xFF) }
|
155
|
+
let(:auth_size) { rand(1..0xFF) }
|
156
|
+
let(:stub_size) { rand(1..0xFF) }
|
157
157
|
before :example do
|
158
158
|
response.pdu_header.auth_length = auth_size
|
159
159
|
response.stub = 'A' * stub_size
|
@@ -176,5 +176,3 @@ RSpec.describe RubySMB::Dcerpc::Response do
|
|
176
176
|
expect(described_class.read(binary)).to eq(packet)
|
177
177
|
end
|
178
178
|
end
|
179
|
-
|
180
|
-
|
@@ -166,10 +166,14 @@ RSpec.describe RubySMB::SMB2::Tree do
|
|
166
166
|
let(:create_res) { double('create response') }
|
167
167
|
let(:query_dir_req) { RubySMB::SMB2::Packet::QueryDirectoryRequest.new }
|
168
168
|
let(:query_dir_res) { RubySMB::SMB2::Packet::QueryDirectoryResponse.new }
|
169
|
+
let(:open_dir) { instance_double(RubySMB::SMB2::File) }
|
169
170
|
|
170
171
|
before :example do
|
171
172
|
allow(tree).to receive(:open_directory).and_return(create_res)
|
172
173
|
allow(create_res).to receive(:file_id)
|
174
|
+
allow(RubySMB::SMB2::File).to receive(:new).and_return(open_dir)
|
175
|
+
allow(open_dir).to receive(:close)
|
176
|
+
allow(create_res).to receive(:file_attributes)
|
173
177
|
allow(RubySMB::SMB2::Packet::QueryDirectoryRequest).to receive(:new).and_return(query_dir_req)
|
174
178
|
allow(client).to receive(:send_recv)
|
175
179
|
allow(RubySMB::SMB2::Packet::QueryDirectoryResponse).to receive(:read).and_return(query_dir_res)
|
@@ -180,6 +184,7 @@ RSpec.describe RubySMB::SMB2::Tree do
|
|
180
184
|
dir = '/dir'
|
181
185
|
expect(tree).to receive(:open_directory).with(directory: dir).and_return(create_res)
|
182
186
|
tree.list(directory: dir)
|
187
|
+
expect(open_dir).to have_received(:close)
|
183
188
|
end
|
184
189
|
|
185
190
|
it 'uses the File ID from the create response' do
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_smb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Metasploit Hackers
|
@@ -38,7 +38,7 @@ cert_chain:
|
|
38
38
|
DgscAao7wB3xW2BWEp1KnaDWkf1x9ttgoBEYyuYwU7uatB67kBQG1PKvLt79wHvz
|
39
39
|
Dxs+KOjGbBRfMnPgVGYkORKVrZIwlaboHbDKxcVW5xv+oZc7KYXWGg==
|
40
40
|
-----END CERTIFICATE-----
|
41
|
-
date: 2023-11
|
41
|
+
date: 2023-12-11 00:00:00.000000000 Z
|
42
42
|
dependencies:
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: redcarpet
|
metadata.gz.sig
CHANGED
Binary file
|