ruby_smb 3.1.2 → 3.1.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/examples/file_server.rb +6 -68
- data/examples/virtual_file_server.rb +10 -62
- data/lib/ruby_smb/client.rb +2 -2
- data/lib/ruby_smb/ntlm/client.rb +74 -0
- data/lib/ruby_smb/ntlm.rb +1 -0
- data/lib/ruby_smb/server/cli.rb +121 -0
- data/lib/ruby_smb/server.rb +1 -0
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/ntlm/client/session_spec.rb +114 -0
- data/spec/lib/ruby_smb/ntlm/client_spec.rb +36 -0
- data.tar.gz.sig +0 -0
- metadata +8 -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: 0b10829db92d5746b069916440f0e91ca006c370053fdc02621ed75723efbf40
|
4
|
+
data.tar.gz: aeeeb396277c47b16ff977e33344ab08da275a0632d6a9a220aac7e733e91311
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7269f0664dd840a83d39d5d1b3a953f87d050bcffbea0b9993685ef1ec91c9047a9117d88ee305d2c467555b475a7c15e82f0f8bfa1b98d077ea00fd401de200
|
7
|
+
data.tar.gz: a5997a65e48cd66585896c55cd8a01fbbee4a8b04280a15ecacdd79501fdd42caf772ac712a32228ea6c1991a6db27c9657d8829ca412691df0834e392c45284
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/examples/file_server.rb
CHANGED
@@ -3,81 +3,19 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'optparse'
|
5
5
|
require 'ruby_smb'
|
6
|
-
require 'ruby_smb/gss/provider/ntlm'
|
7
6
|
|
8
7
|
# we just need *a* default encoding to handle the strings from the NTLM messages
|
9
8
|
Encoding.default_internal = 'UTF-8' if Encoding.default_internal.nil?
|
10
9
|
|
11
|
-
options = {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
username: 'RubySMB',
|
16
|
-
password: 'password',
|
17
|
-
share_name: 'home',
|
18
|
-
share_path: '.',
|
19
|
-
smbv1: true,
|
20
|
-
smbv2: true,
|
21
|
-
smbv3: true
|
22
|
-
}
|
23
|
-
OptionParser.new do |opts|
|
24
|
-
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
25
|
-
opts.on("--path PATH", "The path to share (default: #{options[:share_path]})") do |path|
|
10
|
+
options = RubySMB::Server::Cli.parse(defaults: { share_path: '.' }) do |options, parser|
|
11
|
+
parser.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
12
|
+
|
13
|
+
parser.on("--share-path SHARE_PATH", "The path to share (default: #{options[:share_path]})") do |path|
|
26
14
|
options[:share_path] = path
|
27
15
|
end
|
28
|
-
opts.on("--share SHARE", "The share name (default: #{options[:share_name]})") do |share|
|
29
|
-
options[:share_name] = share
|
30
|
-
end
|
31
|
-
opts.on("--[no-]anonymous", "Allow anonymous access (default: #{options[:allow_anonymous]})") do |allow_anonymous|
|
32
|
-
options[:allow_anonymous] = allow_anonymous
|
33
|
-
end
|
34
|
-
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
35
|
-
options[:smbv1] = smbv1
|
36
|
-
end
|
37
|
-
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
38
|
-
options[:smbv2] = smbv2
|
39
|
-
end
|
40
|
-
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
41
|
-
options[:smbv3] = smbv3
|
42
|
-
end
|
43
|
-
opts.on("--[no-]guests", "Allow guest accounts (default: #{options[:allow_guests]})") do |allow_guests|
|
44
|
-
options[:allow_guests] = allow_guests
|
45
|
-
end
|
46
|
-
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
47
|
-
if username.include?('\\')
|
48
|
-
options[:domain], options[:username] = username.split('\\', 2)
|
49
|
-
else
|
50
|
-
options[:username] = username
|
51
|
-
end
|
52
|
-
end
|
53
|
-
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
54
|
-
options[:password] = password
|
55
|
-
end
|
56
|
-
end.parse!
|
57
|
-
|
58
|
-
ntlm_provider = RubySMB::Gss::Provider::NTLM.new(
|
59
|
-
allow_anonymous: options[:allow_anonymous],
|
60
|
-
allow_guests: options[:allow_guests]
|
61
|
-
)
|
62
|
-
ntlm_provider.put_account(options[:username], options[:password], domain: options[:domain]) # password can also be an NTLM hash
|
63
|
-
|
64
|
-
server = RubySMB::Server.new(
|
65
|
-
gss_provider: ntlm_provider,
|
66
|
-
logger: :stdout
|
67
|
-
)
|
68
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB1 } unless options[:smbv1]
|
69
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB2 } unless options[:smbv2]
|
70
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB3 } unless options[:smbv3]
|
71
|
-
|
72
|
-
if server.dialects.empty?
|
73
|
-
puts "at least one version must be enabled"
|
74
|
-
exit false
|
75
16
|
end
|
76
17
|
|
18
|
+
server = RubySMB::Server::Cli.build(options)
|
77
19
|
server.add_share(RubySMB::Server::Share::Provider::Disk.new(options[:share_name], options[:share_path]))
|
78
|
-
puts "server is running"
|
79
|
-
server.run do
|
80
|
-
puts "received connection"
|
81
|
-
true
|
82
|
-
end
|
83
20
|
|
21
|
+
RubySMB::Server::Cli.run(server)
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'optparse'
|
5
5
|
require 'ruby_smb'
|
6
|
-
require 'ruby_smb/gss/provider/ntlm'
|
7
6
|
|
8
7
|
# we just need *a* default encoding to handle the strings from the NTLM messages
|
9
8
|
Encoding.default_internal = 'UTF-8' if Encoding.default_internal.nil?
|
@@ -32,70 +31,23 @@ MAGIC_8_BALL_ANSWERS = [
|
|
32
31
|
'Very doubtful'
|
33
32
|
]
|
34
33
|
|
35
|
-
options =
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
password: 'password',
|
40
|
-
share_name: 'home',
|
41
|
-
smbv1: true,
|
42
|
-
smbv2: true,
|
43
|
-
smbv3: true
|
44
|
-
}
|
45
|
-
OptionParser.new do |opts|
|
46
|
-
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
47
|
-
opts.on("--share SHARE", "The share name (default: #{options[:share_name]})") do |share|
|
48
|
-
options[:share_name] = share
|
49
|
-
end
|
50
|
-
opts.on("--[no-]anonymous", "Allow anonymous access (default: #{options[:allow_anonymous]})") do |allow_anonymous|
|
51
|
-
options[:allow_anonymous] = allow_anonymous
|
52
|
-
end
|
53
|
-
opts.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{options[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
54
|
-
options[:smbv1] = smbv1
|
55
|
-
end
|
56
|
-
opts.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{options[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
57
|
-
options[:smbv2] = smbv2
|
58
|
-
end
|
59
|
-
opts.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{options[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
60
|
-
options[:smbv3] = smbv3
|
61
|
-
end
|
62
|
-
opts.on("--username USERNAME", "The account's username (default: #{options[:username]})") do |username|
|
63
|
-
if username.include?('\\')
|
64
|
-
options[:domain], options[:username] = username.split('\\', 2)
|
65
|
-
else
|
66
|
-
options[:username] = username
|
67
|
-
end
|
68
|
-
end
|
69
|
-
opts.on("--password PASSWORD", "The account's password (default: #{options[:password]})") do |password|
|
70
|
-
options[:password] = password
|
71
|
-
end
|
72
|
-
opts.on("--virtual-content CONTENT", "The virtual share contents") do |virtual_content|
|
34
|
+
options = RubySMB::Server::Cli.parse do |options, parser|
|
35
|
+
parser.banner = "Usage: #{File.basename(__FILE__)} [options]"
|
36
|
+
|
37
|
+
parser.on("--virtual-content CONTENT", "The virtual share contents") do |virtual_content|
|
73
38
|
options[:virtual_content] = virtual_content
|
74
39
|
end
|
75
|
-
|
40
|
+
|
41
|
+
parser.on("--virtual-name NAME", "The virtual share file name") do |virtual_name|
|
76
42
|
options[:virtual_name] = virtual_name
|
77
43
|
end
|
78
|
-
|
44
|
+
|
45
|
+
parser.on("--virtual-type TYPE", "The virtual share type") do |virtual_type|
|
79
46
|
options[:virtual_type] = virtual_type
|
80
47
|
end
|
81
|
-
end.parse!
|
82
|
-
|
83
|
-
ntlm_provider = RubySMB::Gss::Provider::NTLM.new(allow_anonymous: options[:allow_anonymous])
|
84
|
-
ntlm_provider.put_account(options[:username], options[:password], domain: options[:domain]) # password can also be an NTLM hash
|
85
|
-
|
86
|
-
server = RubySMB::Server.new(
|
87
|
-
gss_provider: ntlm_provider,
|
88
|
-
logger: :stdout
|
89
|
-
)
|
90
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB1 } unless options[:smbv1]
|
91
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB2 } unless options[:smbv2]
|
92
|
-
server.dialects.select! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB3 } unless options[:smbv3]
|
93
|
-
|
94
|
-
if server.dialects.empty?
|
95
|
-
puts "at least one version must be enabled"
|
96
|
-
exit false
|
97
48
|
end
|
98
49
|
|
50
|
+
server = RubySMB::Server::Cli.build(options)
|
99
51
|
virtual_disk = RubySMB::Server::Share::Provider::VirtualDisk.new(options[:share_name])
|
100
52
|
|
101
53
|
# greeting is a static text file
|
@@ -135,9 +87,5 @@ elsif options[:virtual_content] || options[:virtual_name] || options[:virtual_ty
|
|
135
87
|
end
|
136
88
|
|
137
89
|
server.add_share(virtual_disk)
|
138
|
-
puts "server is running"
|
139
|
-
server.run do |server_client|
|
140
|
-
puts "received connection"
|
141
|
-
true
|
142
|
-
end
|
143
90
|
|
91
|
+
RubySMB::Server::Cli.run(server)
|
data/lib/ruby_smb/client.rb
CHANGED
@@ -332,7 +332,7 @@ module RubySMB
|
|
332
332
|
# session setup response is received
|
333
333
|
@session_encrypt_data = always_encrypt
|
334
334
|
|
335
|
-
@ntlm_client =
|
335
|
+
@ntlm_client = RubySMB::NTLM::Client.new(
|
336
336
|
@username,
|
337
337
|
@password,
|
338
338
|
workstation: @local_workstation,
|
@@ -404,7 +404,7 @@ module RubySMB
|
|
404
404
|
@password = pass.encode('utf-8') || ''.encode('utf-8')
|
405
405
|
@username = user.encode('utf-8') || ''.encode('utf-8')
|
406
406
|
|
407
|
-
@ntlm_client =
|
407
|
+
@ntlm_client = RubySMB::NTLM::Client.new(
|
408
408
|
@username,
|
409
409
|
@password,
|
410
410
|
workstation: @local_workstation,
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RubySMB::NTLM
|
2
|
+
module Message
|
3
|
+
def deflag
|
4
|
+
security_buffers.inject(head_size) do |cur, a|
|
5
|
+
a[1].offset = cur
|
6
|
+
cur += a[1].data_size
|
7
|
+
has_flag?(:UNICODE) ? cur + cur % 2 : cur
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize
|
12
|
+
deflag
|
13
|
+
@alist.map { |n, f| f.serialize }.join + security_buffers.map { |n, f| f.value + (has_flag?(:UNICODE) ? "\x00".b * (f.value.length % 2) : '') }.join
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Client < Net::NTLM::Client
|
18
|
+
class Session < Net::NTLM::Client::Session
|
19
|
+
def authenticate!
|
20
|
+
calculate_user_session_key!
|
21
|
+
type3_opts = {
|
22
|
+
:lm_response => is_anonymous? ? "\x00".b : lmv2_resp,
|
23
|
+
:ntlm_response => is_anonymous? ? '' : ntlmv2_resp,
|
24
|
+
:domain => domain,
|
25
|
+
:user => username,
|
26
|
+
:workstation => workstation,
|
27
|
+
:flag => (challenge_message.flag & client.flags)
|
28
|
+
}
|
29
|
+
t3 = Net::NTLM::Message::Type3.create type3_opts
|
30
|
+
t3.extend(Message)
|
31
|
+
if negotiate_key_exchange?
|
32
|
+
t3.enable(:session_key)
|
33
|
+
rc4 = OpenSSL::Cipher.new("rc4")
|
34
|
+
rc4.encrypt
|
35
|
+
rc4.key = user_session_key
|
36
|
+
sk = rc4.update exported_session_key
|
37
|
+
sk << rc4.final
|
38
|
+
t3.session_key = sk
|
39
|
+
end
|
40
|
+
t3
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_anonymous?
|
44
|
+
username == '' && password == ''
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def use_oem_strings?
|
50
|
+
# @see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/99d90ff4-957f-4c8a-80e4-5bfe5a9a9832
|
51
|
+
!challenge_message.has_flag?(:UNICODE) && challenge_message.has_flag?(:OEM)
|
52
|
+
end
|
53
|
+
|
54
|
+
def calculate_user_session_key!
|
55
|
+
if is_anonymous?
|
56
|
+
# see MS-NLMP section 3.4
|
57
|
+
@user_session_key = "\x00".b * 16
|
58
|
+
else
|
59
|
+
@user_session_key = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmv2_hash, nt_proof_str)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def init_context(resp = nil, channel_binding = nil)
|
65
|
+
if resp.nil?
|
66
|
+
@session = nil
|
67
|
+
type1_message
|
68
|
+
else
|
69
|
+
@session = Client::Session.new(self, Net::NTLM::Message.decode64(resp), channel_binding)
|
70
|
+
@session.authenticate!
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/ruby_smb/ntlm.rb
CHANGED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module RubySMB
|
4
|
+
class Server
|
5
|
+
module Cli
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
allow_anonymous: true,
|
8
|
+
allow_guests: false,
|
9
|
+
domain: nil,
|
10
|
+
username: 'RubySMB',
|
11
|
+
password: 'password',
|
12
|
+
share_name: 'home',
|
13
|
+
smbv1: true,
|
14
|
+
smbv2: true,
|
15
|
+
smbv3: true
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
# Parse options from the command line. The resulting option hash is suitable for passing to {build}.
|
19
|
+
#
|
20
|
+
# @yield [options, parser] A block that can be used to update the built option parser.
|
21
|
+
# @yieldparam [Hash<Symbol => >] options The options hash that should be assigned to.
|
22
|
+
# @yieldparam [OptionParser] parser The built option parser.
|
23
|
+
# @param [Hash] defaults Default option values to use.
|
24
|
+
# @return [Hash<Symbol => >] The options hash.
|
25
|
+
# * :allow_anonymous [Boolean] Whether or not to allow anonymous authentication.
|
26
|
+
# * :allow_guest [Boolean] Whether or not to allow guest authentication.
|
27
|
+
# * :username [String] The username of the account to add for authentication.
|
28
|
+
# * :password [String] The password of the account to add for authentication.
|
29
|
+
# * :domain [String] The domain of the account to add for authentication.
|
30
|
+
# * :smbv1 [Boolean] Whether or not to enable SMBv1 dialects.
|
31
|
+
# * :smbv2 [Boolean] Whether or not to enable SMBv2 dialects.
|
32
|
+
# * :smbv3 [Boolean] Whether or not to enable SMBv3 dialects.
|
33
|
+
# * :share_name [String] The name of the share to add.
|
34
|
+
def self.parse(defaults: {}, &block)
|
35
|
+
defaults = DEFAULT_OPTIONS.merge(defaults)
|
36
|
+
options = defaults.clone
|
37
|
+
OptionParser.new do |parser|
|
38
|
+
parser.on("--share-name SHARE_NAME", "The share name (default: #{defaults[:share_name]})") do |share|
|
39
|
+
options[:share_name] = share
|
40
|
+
end
|
41
|
+
|
42
|
+
parser.on("--[no-]anonymous", "Allow anonymous access (default: #{defaults[:allow_anonymous]})") do |allow_anonymous|
|
43
|
+
options[:allow_anonymous] = allow_anonymous
|
44
|
+
end
|
45
|
+
|
46
|
+
parser.on("--[no-]guests", "Allow guest accounts (default: #{defaults[:allow_guests]})") do |allow_guests|
|
47
|
+
options[:allow_guests] = allow_guests
|
48
|
+
end
|
49
|
+
|
50
|
+
parser.on("--[no-]smbv1", "Enable or disable SMBv1 (default: #{defaults[:smbv1] ? 'Enabled' : 'Disabled'})") do |smbv1|
|
51
|
+
options[:smbv1] = smbv1
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.on("--[no-]smbv2", "Enable or disable SMBv2 (default: #{defaults[:smbv2] ? 'Enabled' : 'Disabled'})") do |smbv2|
|
55
|
+
options[:smbv2] = smbv2
|
56
|
+
end
|
57
|
+
|
58
|
+
parser.on("--[no-]smbv3", "Enable or disable SMBv3 (default: #{defaults[:smbv3] ? 'Enabled' : 'Disabled'})") do |smbv3|
|
59
|
+
options[:smbv3] = smbv3
|
60
|
+
end
|
61
|
+
|
62
|
+
parser.on("--username USERNAME", "The account's username (default: #{defaults[:username]})") do |username|
|
63
|
+
if username.include?('\\')
|
64
|
+
options[:domain], options[:username] = username.split('\\', 2)
|
65
|
+
else
|
66
|
+
options[:username] = username
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
parser.on("--password PASSWORD", "The account's password (default: #{defaults[:password]})") do |password|
|
71
|
+
options[:password] = password
|
72
|
+
end
|
73
|
+
|
74
|
+
block.call(options, parser) if block_given?
|
75
|
+
end.parse!
|
76
|
+
|
77
|
+
options
|
78
|
+
end
|
79
|
+
|
80
|
+
# Build a server instance from the specified options. The NTLM provider will be used for authentication.
|
81
|
+
#
|
82
|
+
# @param [Hash] options the options to use while building the server. See the return value of {parse} for a
|
83
|
+
# comprehensive list of keys.
|
84
|
+
def self.build(options)
|
85
|
+
ntlm_provider = RubySMB::Gss::Provider::NTLM.new(
|
86
|
+
allow_anonymous: options[:allow_anonymous],
|
87
|
+
allow_guests: options[:allow_guests]
|
88
|
+
)
|
89
|
+
ntlm_provider.put_account(options[:username], options[:password], domain: options[:domain]) # password can also be an NTLM hash
|
90
|
+
|
91
|
+
server = RubySMB::Server.new(
|
92
|
+
gss_provider: ntlm_provider,
|
93
|
+
logger: :stdout
|
94
|
+
)
|
95
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB1 } unless options[:smbv1]
|
96
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB2 } unless options[:smbv2]
|
97
|
+
server.dialects.filter! { |dialect| RubySMB::Dialect[dialect].family != RubySMB::Dialect::FAMILY_SMB3 } unless options[:smbv3]
|
98
|
+
|
99
|
+
server
|
100
|
+
end
|
101
|
+
|
102
|
+
# Run the server forever. At least 1 SMB dialect must be enabled.
|
103
|
+
#
|
104
|
+
# @param [RubySMB::Server] server The server instance to run.
|
105
|
+
# @param [#puts] out The stream to write standard output messages to.
|
106
|
+
# @param [#puts] err The stream to write error messages to.
|
107
|
+
def self.run(server, out: $stdout, err: $stderr)
|
108
|
+
if server.dialects.empty?
|
109
|
+
err.puts 'at least one version must be enabled'
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
113
|
+
out.puts 'server is running'
|
114
|
+
server.run do |server_client|
|
115
|
+
out.puts 'received connection'
|
116
|
+
true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/ruby_smb/server.rb
CHANGED
@@ -6,6 +6,7 @@ module RubySMB
|
|
6
6
|
# Currently, the server only supports negotiating and authenticating requests. No other server functionality is
|
7
7
|
# available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
|
8
8
|
class Server
|
9
|
+
require 'ruby_smb/server/cli'
|
9
10
|
require 'ruby_smb/server/server_client'
|
10
11
|
require 'ruby_smb/server/session'
|
11
12
|
require 'ruby_smb/server/share'
|
data/lib/ruby_smb/version.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RubySMB::NTLM::Client::Session do
|
4
|
+
let(:message) { Net::NTLM::Message.decode64(%Q{
|
5
|
+
TlRMTVNTUAACAAAADAAMADgAAAA1goni+fNfw+cInOgAAAAAAAAAAJoAmgBE
|
6
|
+
AAAACgBjRQAAAA9NAFMARgBMAEEAQgACAAwATQBTAEYATABBAEIAAQAeAFcA
|
7
|
+
SQBOAC0AMwBNAFMAUAA4AEsAMgBMAEMARwBDAAQAGABtAHMAZgBsAGEAYgAu
|
8
|
+
AGwAbwBjAGEAbAADADgAVwBJAE4ALQAzAE0AUwBQADgASwAyAEwAQwBHAEMA
|
9
|
+
LgBtAHMAZgBsAGEAYgAuAGwAbwBjAGEAbAAHAAgAS6UAWjxl2AEAAAAA
|
10
|
+
}) }
|
11
|
+
subject(:client) { RubySMB::NTLM::Client.new('rubysmb', 'rubysmb', flags: RubySMB::NTLM::DEFAULT_CLIENT_FLAGS) }
|
12
|
+
subject(:session) { described_class.new(client, message) }
|
13
|
+
|
14
|
+
describe '#authenticate!' do
|
15
|
+
it 'calculates the user session key' do
|
16
|
+
expect(session).to receive(:calculate_user_session_key!).and_call_original
|
17
|
+
session.authenticate!
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'checks if it is anonymous' do
|
21
|
+
expect(session).to receive(:is_anonymous?).at_least(1).times.and_call_original
|
22
|
+
session.authenticate!
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns a Type3 message' do
|
26
|
+
expect(session.authenticate!).to be_a Net::NTLM::Message::Type3
|
27
|
+
expect(session.authenticate!).to be_a RubySMB::NTLM::Message
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when it is anonymous' do
|
31
|
+
before(:each) { allow(session).to receive(:is_anonymous?).and_return(true) }
|
32
|
+
after(:each) { session.authenticate! }
|
33
|
+
|
34
|
+
it 'uses the correct lm response' do
|
35
|
+
expect(session).to_not receive(:lmv2_resp)
|
36
|
+
expect(Net::NTLM::Message::Type3).to receive(:create).and_wrap_original do |method, params|
|
37
|
+
expect(params).to include :lm_response
|
38
|
+
expect(params[:lm_response]).to eq "\x00".b
|
39
|
+
method.call(params)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'uses the correct ntlm response' do
|
44
|
+
expect(session).to_not receive(:ntlmv2_resp)
|
45
|
+
expect(Net::NTLM::Message::Type3).to receive(:create).and_wrap_original do |method, params|
|
46
|
+
expect(params).to include :ntlm_response
|
47
|
+
expect(params[:ntlm_response]).to eq ''
|
48
|
+
method.call(params)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when it is not anonymous' do
|
54
|
+
before(:each) { allow(session).to receive(:is_anonymous?).and_return(false) }
|
55
|
+
after(:each) { session.authenticate! }
|
56
|
+
|
57
|
+
it 'uses the correct lm response' do
|
58
|
+
expect(session).to receive(:lmv2_resp).and_call_original
|
59
|
+
expect(Net::NTLM::Message::Type3).to receive(:create).and_wrap_original do |method, params|
|
60
|
+
expect(params).to include :lm_response
|
61
|
+
expect(params[:lm_response].length).to be > 16
|
62
|
+
method.call(params)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'uses the correct ntlm response' do
|
67
|
+
expect(session).to receive(:ntlmv2_resp).and_call_original
|
68
|
+
expect(Net::NTLM::Message::Type3).to receive(:create).and_wrap_original do |method, params|
|
69
|
+
expect(params).to include :ntlm_response
|
70
|
+
expect(params[:ntlm_response].length).to be > 16
|
71
|
+
method.call(params)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#calculate_user_session_key!' do
|
78
|
+
it 'returns an all zero key when it is anonymous' do
|
79
|
+
expect(session).to receive(:is_anonymous?).and_return(true)
|
80
|
+
expect(session.send(:calculate_user_session_key!)).to eq "\x00".b * 16
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'returns a session key' do
|
84
|
+
expect(session).to receive(:is_anonymous?).and_return(false)
|
85
|
+
expect(session.send(:calculate_user_session_key!)).to_not eq "\x00".b * 16
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#is_anonymous?' do
|
90
|
+
it 'returns false when the username is not blank' do
|
91
|
+
allow(session).to receive(:username).and_return('username')
|
92
|
+
allow(session).to receive(:password).and_return('')
|
93
|
+
expect(session.is_anonymous?).to be false
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns false when the password is not blank' do
|
97
|
+
allow(session).to receive(:username).and_return('')
|
98
|
+
allow(session).to receive(:password).and_return('password')
|
99
|
+
expect(session.is_anonymous?).to be false
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns false when the username is not blank and the password is not blank' do
|
103
|
+
allow(session).to receive(:username).and_return('username')
|
104
|
+
allow(session).to receive(:password).and_return('password')
|
105
|
+
expect(session.is_anonymous?).to be false
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns true when the username is blank and the password is blank' do
|
109
|
+
allow(session).to receive(:username).and_return('')
|
110
|
+
allow(session).to receive(:password).and_return('')
|
111
|
+
expect(session.is_anonymous?).to be true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe RubySMB::NTLM::Client do
|
4
|
+
subject(:client) { described_class.new('rubysmb', 'rubysmb', flags: RubySMB::NTLM::DEFAULT_CLIENT_FLAGS) }
|
5
|
+
|
6
|
+
describe '#init_context' do
|
7
|
+
context 'when a response is provided' do
|
8
|
+
let(:resp) { %Q{
|
9
|
+
TlRMTVNTUAACAAAADAAMADgAAAA1goni+fNfw+cInOgAAAAAAAAAAJoAmgBE
|
10
|
+
AAAACgBjRQAAAA9NAFMARgBMAEEAQgACAAwATQBTAEYATABBAEIAAQAeAFcA
|
11
|
+
SQBOAC0AMwBNAFMAUAA4AEsAMgBMAEMARwBDAAQAGABtAHMAZgBsAGEAYgAu
|
12
|
+
AGwAbwBjAGEAbAADADgAVwBJAE4ALQAzAE0AUwBQADgASwAyAEwAQwBHAEMA
|
13
|
+
LgBtAHMAZgBsAGEAYgAuAGwAbwBjAGEAbAAHAAgAS6UAWjxl2AEAAAAA
|
14
|
+
} }
|
15
|
+
it 'returns a Type3 message' do
|
16
|
+
expect(client.init_context(resp)).to be_a Net::NTLM::Message::Type3
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates a new session object' do
|
20
|
+
expect(RubySMB::NTLM::Client::Session).to receive(:new).and_call_original
|
21
|
+
client.init_context(resp)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when a response is not provided' do
|
26
|
+
it 'returns a Type1 message' do
|
27
|
+
expect(client.init_context).to be_a Net::NTLM::Message::Type1
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not create a new session object' do
|
31
|
+
expect(RubySMB::NTLM::Client::Session).to_not receive(:new)
|
32
|
+
client.init_context
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
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.1.
|
4
|
+
version: 3.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Metasploit Hackers
|
@@ -97,7 +97,7 @@ cert_chain:
|
|
97
97
|
EknWpNgVhohbot1lfVAMmIhdtOVaRVcQQixWPwprDj/ydB8ryDMDosIMcw+fkoXU
|
98
98
|
9GJsSaSRRYQ9UUkVL27b64okU8D48m8=
|
99
99
|
-----END CERTIFICATE-----
|
100
|
-
date: 2022-05-
|
100
|
+
date: 2022-05-24 00:00:00.000000000 Z
|
101
101
|
dependencies:
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: redcarpet
|
@@ -473,7 +473,9 @@ files:
|
|
473
473
|
- lib/ruby_smb/nbss/session_header.rb
|
474
474
|
- lib/ruby_smb/nbss/session_request.rb
|
475
475
|
- lib/ruby_smb/ntlm.rb
|
476
|
+
- lib/ruby_smb/ntlm/client.rb
|
476
477
|
- lib/ruby_smb/server.rb
|
478
|
+
- lib/ruby_smb/server/cli.rb
|
477
479
|
- lib/ruby_smb/server/server_client.rb
|
478
480
|
- lib/ruby_smb/server/server_client/encryption.rb
|
479
481
|
- lib/ruby_smb/server/server_client/negotiation.rb
|
@@ -802,6 +804,8 @@ files:
|
|
802
804
|
- spec/lib/ruby_smb/nbss/netbios_name_spec.rb
|
803
805
|
- spec/lib/ruby_smb/nbss/session_header_spec.rb
|
804
806
|
- spec/lib/ruby_smb/nbss/session_request_spec.rb
|
807
|
+
- spec/lib/ruby_smb/ntlm/client/session_spec.rb
|
808
|
+
- spec/lib/ruby_smb/ntlm/client_spec.rb
|
805
809
|
- spec/lib/ruby_smb/server/server_client_spec.rb
|
806
810
|
- spec/lib/ruby_smb/server/session_spec.rb
|
807
811
|
- spec/lib/ruby_smb/server/share/provider/disk_spec.rb
|
@@ -1112,6 +1116,8 @@ test_files:
|
|
1112
1116
|
- spec/lib/ruby_smb/nbss/netbios_name_spec.rb
|
1113
1117
|
- spec/lib/ruby_smb/nbss/session_header_spec.rb
|
1114
1118
|
- spec/lib/ruby_smb/nbss/session_request_spec.rb
|
1119
|
+
- spec/lib/ruby_smb/ntlm/client/session_spec.rb
|
1120
|
+
- spec/lib/ruby_smb/ntlm/client_spec.rb
|
1115
1121
|
- spec/lib/ruby_smb/server/server_client_spec.rb
|
1116
1122
|
- spec/lib/ruby_smb/server/session_spec.rb
|
1117
1123
|
- spec/lib/ruby_smb/server/share/provider/disk_spec.rb
|
metadata.gz.sig
CHANGED
Binary file
|