msftidy
parent
8f77ba1147
commit
de45263fd9
|
@ -1,268 +1,269 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Exploit::Remote::SMB
|
||||
include Msf::Exploit::Remote::SMB::Authenticated
|
||||
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
|
||||
attr_reader :accepts_bogus_domains
|
||||
|
||||
def proto
|
||||
'smb'
|
||||
end
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'SMB Login Check Scanner',
|
||||
'Description' => %q{
|
||||
This module will test a SMB login on a range of machines and
|
||||
report successful logins. If you have loaded a database plugin
|
||||
and connected to a database this module will record successful
|
||||
logins and hosts so you can track your access.
|
||||
},
|
||||
'Author' => [
|
||||
'tebo <tebo [at] attackresearch [dot] com>', # Original
|
||||
'Ben Campbell <eat_meatballs [at] hotmail.co.uk>' # Refactoring
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '1999-0506'], # Weak password
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
deregister_options('RHOST','USERNAME','PASSWORD')
|
||||
|
||||
@accepts_guest_logins = {}
|
||||
@correct_credentials_status_codes = ["STATUS_INVALID_LOGON_HOURS",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_ACCOUNT_EXPIRED",
|
||||
"STATUS_ACCOUNT_DISABLED",
|
||||
"STATUS_PASSWORD_EXPIRED",
|
||||
"STATUS_PASSWORD_MUST_CHANGE"]
|
||||
|
||||
# These are normally advanced options, but for this module they have a
|
||||
# more active role, so make them regular options.
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SMBPass', [ false, "SMB Password" ]),
|
||||
OptString.new('SMBUser', [ false, "SMB Username" ]),
|
||||
OptString.new('SMBDomain', [ false, "SMB Domain", 'WORKGROUP']),
|
||||
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]),
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]),
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_brute(:level => :vstatus, :ip => ip, :msg => "Starting SMB login bruteforce")
|
||||
|
||||
if accepts_bogus_logins?
|
||||
print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
return
|
||||
end
|
||||
|
||||
unless datastore['RECORD_GUEST']
|
||||
if accepts_guest_logins?
|
||||
print_status("#{ip} - This system allows guest sessions with any credentials, these instances will not be reported.")
|
||||
end
|
||||
end
|
||||
|
||||
domain = datastore['SMBDomain'] || ""
|
||||
|
||||
begin
|
||||
each_user_pass do |user, pass|
|
||||
result = try_user_pass(domain, user, pass)
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def check_login_status(domain, user, pass)
|
||||
connect()
|
||||
status_code = ""
|
||||
begin
|
||||
status_code = simple.login( datastore['SMBName'],
|
||||
user,
|
||||
pass,
|
||||
domain,
|
||||
datastore['SMB::VerifySignature'],
|
||||
datastore['NTLM::UseNTLMv2'],
|
||||
datastore['NTLM::UseNTLM2_session'],
|
||||
datastore['NTLM::SendLM'],
|
||||
datastore['NTLM::UseLMKey'],
|
||||
datastore['NTLM::SendNTLM'],
|
||||
datastore['SMB::Native_OS'],
|
||||
datastore['SMB::Native_LM'],
|
||||
{:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
|
||||
)
|
||||
# This does not appear to be required to validate login details? simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
status_code = e.get_error(e.error_code)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
|
||||
status_code = e.error_reason
|
||||
ensure
|
||||
disconnect()
|
||||
end
|
||||
|
||||
return status_code
|
||||
end
|
||||
|
||||
# Unsure how this result is different than bogus logins
|
||||
def accepts_guest_logins?
|
||||
guest = false
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
|
||||
check_login_status(datastore['SMBDomain'], user, pass)
|
||||
|
||||
unless(simple.client.auth_user)
|
||||
guest = true
|
||||
@accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost) #'rhost' should be rhost?
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => 'accepts guest login from any account',
|
||||
:update => :unique_data
|
||||
)
|
||||
end
|
||||
|
||||
return guest
|
||||
end
|
||||
|
||||
def accepts_bogus_logins?
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
check_login_status(datastore['SMBDomain'], user, pass)
|
||||
return simple.client.auth_user ? true : false
|
||||
end
|
||||
|
||||
# This logic is not universal ie a local account will not care about workgroup
|
||||
# but remote domain authentication will so check each instance
|
||||
def accepts_bogus_domains?(user, pass, rhost)
|
||||
domain = Rex::Text.rand_text_alpha(8)
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
bogus_domain = valid_credentials?(status)
|
||||
if bogus_domain
|
||||
vprint_status "Domain is ignored"
|
||||
end
|
||||
|
||||
return valid_credentials?(status)
|
||||
end
|
||||
|
||||
def valid_credentials?(status)
|
||||
return (status == "STATUS_SUCCESS" || @correct_credentials_status_codes.include?(status))
|
||||
end
|
||||
|
||||
def try_user_pass(domain, user, pass)
|
||||
# Note that unless PRESERVE_DOMAINS is true, we're more
|
||||
# than happy to pass illegal usernames that contain
|
||||
# slashes.
|
||||
if datastore["PRESERVE_DOMAINS"]
|
||||
d,u = domain_username_split(user)
|
||||
user = u
|
||||
domain = d if d
|
||||
end
|
||||
|
||||
user = user.to_s.gsub(/<BLANK>/i,"")
|
||||
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
output_message = "#{smbhost} - %s (#{smb_peer_os}) #{user} : #{pass} [#{status}]"
|
||||
|
||||
case status
|
||||
when 'STATUS_SUCCESS'
|
||||
if(simple.client.auth_user)
|
||||
print_good(output_message % "SUCCESSFUL LOGIN")
|
||||
vprint_status("Auth-User: #{simple.client.auth_user}")
|
||||
else
|
||||
print_status(output_message % "GUEST LOGIN")
|
||||
@accepts_guest_logins[rhost] = [user, pass] unless datastore['RECORD_GUEST']
|
||||
end
|
||||
|
||||
report_creds(domain,user,pass,true) if @accepts_guest_logins.select{ |g_host, g_creds| g_host == rhost and g_creds == [user,pass] }.empty?
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
when *@correct_credentials_status_codes
|
||||
print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" )
|
||||
report_creds(domain,user,pass,false)
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
else
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def validuser_case_sensitive?(domain, user, pass)
|
||||
if user == user.downcase
|
||||
user = user.upcase
|
||||
else
|
||||
user = user.downcase
|
||||
end
|
||||
|
||||
status = check_login_status(domain, user, pass)
|
||||
case_insensitive = valid_credentials?(status)
|
||||
if case_insensitive
|
||||
vprint_status("Username is case insensitive")
|
||||
end
|
||||
|
||||
return case_insensitive
|
||||
end
|
||||
|
||||
def note_creds(domain,user,pass,reason)
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => {:user => user, :pass => pass, :status => reason},
|
||||
:update => :unique_data
|
||||
)
|
||||
end
|
||||
|
||||
def report_creds(domain,user,pass,active)
|
||||
login_name = ""
|
||||
|
||||
if accepts_bogus_domains?(user,pass,rhost)
|
||||
login_name = user
|
||||
else
|
||||
login_name = "#{domain}\\#{user}"
|
||||
end
|
||||
|
||||
report_hash = {
|
||||
:host => rhost,
|
||||
:port => datastore['RPORT'],
|
||||
:sname => 'smb',
|
||||
:user => login_name,
|
||||
:pass => pass,
|
||||
:source_type => "user_supplied",
|
||||
:active => active
|
||||
}
|
||||
|
||||
if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/
|
||||
report_hash.merge!({:type => 'smb_hash'})
|
||||
else
|
||||
report_hash.merge!({:type => 'password'})
|
||||
end
|
||||
report_auth_info(report_hash)
|
||||
end
|
||||
end
|
||||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::DCERPC
|
||||
include Msf::Exploit::Remote::SMB
|
||||
include Msf::Exploit::Remote::SMB::Authenticated
|
||||
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
|
||||
attr_reader :accepts_bogus_domains
|
||||
|
||||
def proto
|
||||
'smb'
|
||||
end
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'SMB Login Check Scanner',
|
||||
'Description' => %q{
|
||||
This module will test a SMB login on a range of machines and
|
||||
report successful logins. If you have loaded a database plugin
|
||||
and connected to a database this module will record successful
|
||||
logins and hosts so you can track your access.
|
||||
},
|
||||
'Author' => [
|
||||
'tebo <tebo [at] attackresearch [dot] com>', # Original
|
||||
'Ben Campbell <eat_meatballs [at] hotmail.co.uk>' # Refactoring
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '1999-0506'], # Weak password
|
||||
|
||||
],
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
deregister_options('RHOST','USERNAME','PASSWORD')
|
||||
|
||||
@accepts_guest_logins = {}
|
||||
@correct_credentials_status_codes = ["STATUS_INVALID_LOGON_HOURS",
|
||||
"STATUS_ACCOUNT_RESTRICTION",
|
||||
"STATUS_ACCOUNT_EXPIRED",
|
||||
"STATUS_ACCOUNT_DISABLED",
|
||||
"STATUS_PASSWORD_EXPIRED",
|
||||
"STATUS_PASSWORD_MUST_CHANGE"]
|
||||
|
||||
# These are normally advanced options, but for this module they have a
|
||||
# more active role, so make them regular options.
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SMBPass', [ false, "SMB Password" ]),
|
||||
OptString.new('SMBUser', [ false, "SMB Username" ]),
|
||||
OptString.new('SMBDomain', [ false, "SMB Domain", 'WORKGROUP']),
|
||||
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]),
|
||||
OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]),
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
print_brute(:level => :vstatus, :ip => ip, :msg => "Starting SMB login bruteforce")
|
||||
|
||||
if accepts_bogus_logins?
|
||||
print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.")
|
||||
return
|
||||
end
|
||||
|
||||
unless datastore['RECORD_GUEST']
|
||||
if accepts_guest_logins?
|
||||
print_status("#{ip} - This system allows guest sessions with any credentials, these instances will not be reported.")
|
||||
end
|
||||
end
|
||||
|
||||
domain = datastore['SMBDomain'] || ""
|
||||
|
||||
begin
|
||||
each_user_pass do |user, pass|
|
||||
result = try_user_pass(domain, user, pass)
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def check_login_status(domain, user, pass)
|
||||
connect()
|
||||
status_code = ""
|
||||
begin
|
||||
status_code = simple.login( datastore['SMBName'],
|
||||
user,
|
||||
pass,
|
||||
domain,
|
||||
datastore['SMB::VerifySignature'],
|
||||
datastore['NTLM::UseNTLMv2'],
|
||||
datastore['NTLM::UseNTLM2_session'],
|
||||
datastore['NTLM::SendLM'],
|
||||
datastore['NTLM::UseLMKey'],
|
||||
datastore['NTLM::SendNTLM'],
|
||||
datastore['SMB::Native_OS'],
|
||||
datastore['SMB::Native_LM'],
|
||||
{:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
|
||||
)
|
||||
# This does not appear to be required to validate login details? simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
||||
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||
status_code = e.get_error(e.error_code)
|
||||
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
|
||||
status_code = e.error_reason
|
||||
ensure
|
||||
disconnect()
|
||||
end
|
||||
|
||||
return status_code
|
||||
end
|
||||
|
||||
# Unsure how this result is different than bogus logins
|
||||
def accepts_guest_logins?
|
||||
guest = false
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
|
||||
check_login_status(datastore['SMBDomain'], user, pass)
|
||||
|
||||
unless(simple.client.auth_user)
|
||||
|
||||
guest = true
|
||||
@accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost) #'rhost' should be rhost?
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => 'accepts guest login from any account',
|
||||
:update => :unique_data
|
||||
)
|
||||
end
|
||||
|
||||
return guest
|
||||
end
|
||||
|
||||
def accepts_bogus_logins?
|
||||
user = Rex::Text.rand_text_alpha(8)
|
||||
pass = Rex::Text.rand_text_alpha(8)
|
||||
check_login_status(datastore['SMBDomain'], user, pass)
|
||||
return simple.client.auth_user ? true : false
|
||||
end
|
||||
|
||||
# This logic is not universal ie a local account will not care about workgroup
|
||||
# but remote domain authentication will so check each instance
|
||||
def accepts_bogus_domains?(user, pass, rhost)
|
||||
domain = Rex::Text.rand_text_alpha(8)
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
bogus_domain = valid_credentials?(status)
|
||||
if bogus_domain
|
||||
vprint_status "Domain is ignored"
|
||||
end
|
||||
|
||||
return valid_credentials?(status)
|
||||
end
|
||||
|
||||
def valid_credentials?(status)
|
||||
return (status == "STATUS_SUCCESS" || @correct_credentials_status_codes.include?(status))
|
||||
end
|
||||
|
||||
def try_user_pass(domain, user, pass)
|
||||
# Note that unless PRESERVE_DOMAINS is true, we're more
|
||||
# than happy to pass illegal usernames that contain
|
||||
# slashes.
|
||||
if datastore["PRESERVE_DOMAINS"]
|
||||
d,u = domain_username_split(user)
|
||||
user = u
|
||||
domain = d if d
|
||||
end
|
||||
|
||||
user = user.to_s.gsub(/<BLANK>/i,"")
|
||||
|
||||
status = check_login_status(domain, user, pass)
|
||||
|
||||
output_message = "#{smbhost} - %s (#{smb_peer_os}) #{user} : #{pass} [#{status}]"
|
||||
|
||||
case status
|
||||
when 'STATUS_SUCCESS'
|
||||
|
||||
if(simple.client.auth_user)
|
||||
print_good(output_message % "SUCCESSFUL LOGIN")
|
||||
vprint_status("Auth-User: #{simple.client.auth_user}")
|
||||
else
|
||||
print_status(output_message % "GUEST LOGIN")
|
||||
@accepts_guest_logins[rhost] = [user, pass] unless datastore['RECORD_GUEST']
|
||||
end
|
||||
|
||||
report_creds(domain,user,pass,true) if @accepts_guest_logins.select{ |g_host, g_creds| g_host == rhost and g_creds == [user,pass] }.empty?
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
when *@correct_credentials_status_codes
|
||||
print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" )
|
||||
report_creds(domain,user,pass,false)
|
||||
validuser_case_sensitive?(domain, user, pass)
|
||||
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
else
|
||||
vprint_error(output_message % "FAILED LOGIN")
|
||||
end
|
||||
end
|
||||
|
||||
def validuser_case_sensitive?(domain, user, pass)
|
||||
if user == user.downcase
|
||||
user = user.upcase
|
||||
else
|
||||
user = user.downcase
|
||||
end
|
||||
|
||||
status = check_login_status(domain, user, pass)
|
||||
case_insensitive = valid_credentials?(status)
|
||||
if case_insensitive
|
||||
vprint_status("Username is case insensitive")
|
||||
end
|
||||
|
||||
return case_insensitive
|
||||
end
|
||||
|
||||
def note_creds(domain,user,pass,reason)
|
||||
report_note(
|
||||
:host => rhost,
|
||||
:proto => 'tcp',
|
||||
:sname => 'smb',
|
||||
:port => datastore['RPORT'],
|
||||
:type => 'smb.account.info',
|
||||
:data => {:user => user, :pass => pass, :status => reason},
|
||||
:update => :unique_data
|
||||
)
|
||||
end
|
||||
|
||||
def report_creds(domain,user,pass,active)
|
||||
login_name = ""
|
||||
|
||||
if accepts_bogus_domains?(user,pass,rhost)
|
||||
login_name = user
|
||||
else
|
||||
login_name = "#{domain}\\#{user}"
|
||||
end
|
||||
|
||||
report_hash = {
|
||||
:host => rhost,
|
||||
:port => datastore['RPORT'],
|
||||
:sname => 'smb',
|
||||
:user => login_name,
|
||||
:pass => pass,
|
||||
:source_type => "user_supplied",
|
||||
:active => active
|
||||
}
|
||||
|
||||
if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/
|
||||
report_hash.merge!({:type => 'smb_hash'})
|
||||
else
|
||||
report_hash.merge!({:type => 'password'})
|
||||
end
|
||||
report_auth_info(report_hash)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue