Skip to content

Commit cca30b5

Browse files
author
Tod Beardsley
committed
Land rapid7#4094, fixes for OWA brute forcer
Fixes rapid7#4083 Thanks TONS to @jhart-r7 for doing most of the work on this!
2 parents 40ee03e + a468397 commit cca30b5

File tree

1 file changed

+31
-20
lines changed

1 file changed

+31
-20
lines changed

modules/auxiliary/scanner/http/owa_login.rb

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ def initialize
1818
super(
1919
'Name' => 'Outlook Web App (OWA) Brute Force Utility',
2020
'Description' => %q{
21-
This module tests credentials on OWA 2003, 2007, 2010, 2013 servers. The default
22-
action is set to OWA 2010.
21+
This module tests credentials on OWA 2003, 2007, 2010, and 2013 servers.
2322
},
2423
'Author' =>
2524
[
@@ -70,7 +69,7 @@ def initialize
7069
}
7170
]
7271
],
73-
'DefaultAction' => 'OWA_2010',
72+
'DefaultAction' => 'OWA_2013',
7473
'DefaultOptions' => {
7574
'SSL' => true
7675
}
@@ -93,20 +92,21 @@ def initialize
9392
deregister_options('BLANK_PASSWORDS', 'RHOSTS','PASSWORD','USERNAME')
9493
end
9594

96-
def run
97-
98-
vhost = datastore['VHOST'] || datastore['RHOST']
99-
100-
print_status("#{msg} Testing version #{action.name}")
101-
95+
def setup
10296
# Here's a weird hack to check if each_user_pass is empty or not
10397
# apparently you cannot do each_user_pass.empty? or even inspect() it
10498
isempty = true
10599
each_user_pass do |user|
106100
isempty = false
107101
break
108102
end
109-
print_error("No username/password specified") if isempty
103+
raise ArgumentError, "No username/password specified" if isempty
104+
end
105+
106+
def run
107+
vhost = datastore['VHOST'] || datastore['RHOST']
108+
109+
print_status("#{msg} Testing version #{action.name}")
110110

111111
auth_path = action.opts['AuthPath']
112112
inbox_path = action.opts['InboxPath']
@@ -204,20 +204,31 @@ def try_user_pass(opts)
204204
end
205205

206206
#No password change required moving on.
207-
reason = res.headers['location'].split('reason=')[1]
207+
unless location = res.headers['location']
208+
print_error("#{msg} No HTTP redirect. This is not OWA 2013, aborting.")
209+
return :abort
210+
end
211+
reason = location.split('reason=')[1]
208212
if reason == nil
209213
headers['Cookie'] = 'PBack=0;' << res.get_cookies
210214
else
211215
#Login didn't work. no point on going on.
212-
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'")
216+
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (HTTP redirect with reason #{reason})")
213217
return :Skip_pass
214218
end
215219
else
216-
# these two lines are the authentication info
220+
# The authentication info is in the cookies on this response
217221
cookies = res.get_cookies
218-
sessionid = 'sessionid=' << cookies.split('sessionid=')[1].split('; ')[0]
219-
cadata = 'cadata=' << cookies.split('cadata=')[1].split('; ')[0]
220-
headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata
222+
cookie_header = 'PBack=0'
223+
%w(sessionid cadata).each do |necessary_cookie|
224+
if cookies =~ /#{necessary_cookie}=([^;]*)/
225+
cookie_header << "; #{Regexp.last_match(1)}"
226+
else
227+
print_error("#{msg} Missing #{necessary_cookie} cookie. This is not OWA 2010, aborting")
228+
return :abort
229+
end
230+
end
231+
headers['Cookie'] = cookie_header
221232
end
222233

223234
begin
@@ -236,8 +247,8 @@ def try_user_pass(opts)
236247
return :abort
237248
end
238249

239-
if res.code == 302
240-
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'")
250+
if res.redirect?
251+
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (response was a #{res.code} redirect)")
241252
return :skip_pass
242253
end
243254

@@ -256,7 +267,7 @@ def try_user_pass(opts)
256267
report_auth_info(report_hash)
257268
return :next_user
258269
else
259-
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'")
270+
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (response body did not match)")
260271
return :skip_pass
261272
end
262273
end
@@ -291,7 +302,7 @@ def get_ad_domain
291302
next
292303
end
293304

294-
if res and res.code == 401 and res['WWW-Authenticate'].match(/^NTLM/i)
305+
if res && res.code == 401 && res.headers.has_key?('WWW-Authenticate') && res.headers['WWW-Authenticate'].match(/^NTLM/i)
295306
hash = res['WWW-Authenticate'].split('NTLM ')[1]
296307
domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'')
297308
print_good("Found target domain: " + domain)

0 commit comments

Comments
 (0)