Skip to content

Commit cc93dbb

Browse files
committed
Merge pull request rapid7#102 from rapid7/feature/MSP-9707/smb-bruteforce-refactor
Feature/msp 9707/smb bruteforce refactor MSP-9707 #land
2 parents 147c6d8 + 0daa395 commit cc93dbb

File tree

5 files changed

+202
-255
lines changed

5 files changed

+202
-255
lines changed

Gemfile.local.example

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ end
2727

2828
# Create a custom group
2929
group :local do
30-
# Use pry to help view and interact with objects in the framework
31-
gem 'pry', '~> 0.9'
3230
# Use pry-debugger to step through code during development
3331
gem 'pry-debugger', '~> 0.2'
3432
# Add the lab gem so that the 'lab' plugin will work again

lib/metasploit/framework/login_scanner/smb.rb

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ class SMB
1717
include Metasploit::Framework::LoginScanner::RexSocket
1818
include Metasploit::Framework::LoginScanner::NTLM
1919

20+
# Constants to be used in {Result#access_level}
21+
module AccessLevels
22+
# Administrative access. For SMB, this is defined as being
23+
# able to successfully Tree Connect to the `ADMIN$` share.
24+
# This definition is not without its problems, but suffices to
25+
# conclude that such a user will most likely be able to use
26+
# psexec.
27+
ADMINISTRATOR = "Administrator"
28+
# Guest access means our creds were accepted but the logon
29+
# session is not associated with a real user account.
30+
GUEST = "Guest"
31+
end
32+
2033
CAN_GET_SESSION = true
2134
DEFAULT_REALM = 'WORKSTATION'
2235
LIKELY_PORTS = [ 139, 445 ]
@@ -100,6 +113,27 @@ module StatusCodes
100113
allow_nil: true
101114

102115

116+
# If login is successul and {Result#access_level} is not set
117+
# then arbitrary credentials are accepted. If it is set to
118+
# Guest, then arbitrary credentials are accepted, but given
119+
# Guest permissions.
120+
#
121+
# @param domain [String] Domain to authenticate against. Use an
122+
# empty string for local accounts.
123+
# @return [Result]
124+
def attempt_bogus_login(domain)
125+
if defined?(@result_for_bogus)
126+
return @result_for_bogus
127+
end
128+
cred = Credential.new(
129+
public: Rex::Text.rand_text_alpha(8),
130+
private: Rex::Text.rand_text_alpha(8),
131+
realm: domain
132+
)
133+
@result_for_bogus = attempt_login(cred)
134+
end
135+
136+
103137
# (see Base#attempt_login)
104138
def attempt_login(credential)
105139

@@ -121,7 +155,7 @@ def attempt_login(credential)
121155

122156
begin
123157
# TODO: OMG
124-
ok = simple.login(
158+
simple.login(
125159
smb_name,
126160
credential.public,
127161
credential.private,
@@ -140,16 +174,29 @@ def attempt_login(credential)
140174
}
141175
)
142176

143-
simple.connect("\\\\#{smb_name}\\IPC$")
144-
status = ok ? :success : :failed
145-
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
177+
# Windows SMB will return an error code during Session
178+
# Setup, but nix Samba requires a Tree Connect. Try admin$
179+
# first, since that will tell us if this user has local
180+
# admin access. Fall back to IPC$ which should be accessible
181+
# to any user with valid creds.
182+
begin
183+
simple.connect("\\\\#{host}\\admin$")
184+
access_level = AccessLevels::ADMINISTRATOR
185+
simple.disconnect("\\\\#{host}\\admin$")
186+
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
187+
simple.connect("\\\\#{host}\\IPC$")
188+
end
189+
190+
# If we made it this far without raising, we have a valid
191+
# login
192+
status = :success
193+
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
146194
status = case e.get_error(e.error_code)
147195
when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES
148196
:correct
149197
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
150198
:failed
151199
else
152-
puts e.backtrace.join
153200
:failed
154201
end
155202

@@ -161,7 +208,11 @@ def attempt_login(credential)
161208
status = :connection_error
162209
end
163210

164-
Result.new(credential: credential, status: status, proof: proof)
211+
if status == :success && simple.client.auth_user.nil?
212+
access_level ||= AccessLevels::GUEST
213+
end
214+
215+
Result.new(credential: credential, status: status, proof: proof, access_level: access_level)
165216
end
166217

167218
def connect
@@ -206,6 +257,7 @@ def set_sane_defaults
206257
self.smb_name = self.host if self.smb_name.nil?
207258

208259
end
260+
209261
end
210262
end
211263
end

lib/rex/proto/smb/exceptions.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,10 @@ def initialize(*args)
736736
super(*args)
737737
end
738738

739+
def error_name
740+
get_error(error_code)
741+
end
742+
739743
# returns an error string if it exists, otherwise just the error code
740744
def get_error(error)
741745
string = ''
@@ -807,7 +811,7 @@ def to_s
807811
class ErrorCode < InvalidPacket
808812
def to_s
809813
'The server responded with error: ' +
810-
self.get_error(self.error_code) +
814+
self.error_name +
811815
" (Command=#{self.command} WordCount=#{self.word_count})"
812816
end
813817
end

0 commit comments

Comments
 (0)