Skip to content

Commit 7a1c3e7

Browse files
committed
Merge branch 'dmaloney-r7-WinRM_piecemeal'
2 parents 4e6b539 + b15c38f commit 7a1c3e7

File tree

3 files changed

+99
-67
lines changed

3 files changed

+99
-67
lines changed

lib/msf/core/exploit/winrm.rb

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,24 @@
88

99
module Msf
1010
module Exploit::Remote::WinRM
11-
1211
include Exploit::Remote::NTLM::Client
1312
include Exploit::Remote::HttpClient
14-
1513
#
1614
# Constants
1715
#
1816
NTLM_CRYPT ||= Rex::Proto::NTLM::Crypt
1917
NTLM_CONST ||= Rex::Proto::NTLM::Constants
2018
NTLM_UTILS ||= Rex::Proto::NTLM::Utils
2119
NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions
22-
2320
def initialize(info = {})
2421
super
2522
register_options(
2623
[
27-
Opt::RHOST,
2824
Opt::RPORT(5985),
29-
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
30-
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]),
31-
OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]),
3225
OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentification', 'WORKSTATION']),
3326
OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ]),
3427
OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
35-
OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ])
28+
OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
3629
], self.class
3730
)
3831

@@ -45,18 +38,15 @@ def winrm_poke(timeout = 20)
4538
'uri' => datastore['URI'],
4639
'data' => Rex::Text.rand_text_alpha(8)
4740
}
48-
49-
c = connect(opts)
50-
to = opts[:timeout] || timeout
41+
c = connect(opts)
42+
to = opts[:timeout] || timeout
5143
ctype = "application/soap+xml;charset=UTF-8"
52-
5344
resp, c = send_request_cgi(opts.merge({
54-
'uri' => opts['uri'],
45+
'uri' => opts['uri'],
5546
'method' => 'POST',
56-
'ctype' => ctype,
57-
'data' => opts['data']
47+
'ctype' => ctype,
48+
'data' => opts['data']
5849
}), to)
59-
6050
return resp
6151
end
6252

@@ -71,34 +61,29 @@ def parse_auth_methods(resp)
7161

7262
def winrm_run_cmd(cmd, timeout=20)
7363
resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
74-
7564
if resp.code == 401
7665
print_error "Login failure! Recheck supplied credentials."
7766
return resp .code
7867
end
79-
8068
unless resp.code == 200
8169
print_error "Got unexpected response: \n #{resp.to_s}"
8270
retval == resp.code || 0
8371
return retval
8472
end
85-
8673
shell_id = winrm_get_shell_id(resp)
8774
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
8875
cmd_id = winrm_get_cmd_id(resp)
8976
resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
9077
streams = winrm_get_cmd_streams(resp)
9178
resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
9279
resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id))
93-
9480
return streams
9581
end
9682

9783
def winrm_wql_msg(wql)
9884
action = winrm_uri_action("wql")
9985
contents = winrm_header(action) + winrm_wql_body(wql)
10086
msg = winrm_envelope(contents)
101-
10287
return msg
10388
end
10489

@@ -108,7 +93,6 @@ def winrm_open_shell_msg
10893
header_data = action + options
10994
contents = winrm_header(header_data) + winrm_open_shell_body
11095
msg = winrm_envelope(contents)
111-
11296
return msg
11397
end
11498

@@ -119,7 +103,6 @@ def winrm_cmd_msg(cmd,shell_id)
119103
header_data = action + options + selectors
120104
contents = winrm_header(header_data) + winrm_cmd_body(cmd)
121105
msg = winrm_envelope(contents)
122-
123106
return msg
124107
end
125108

@@ -129,7 +112,6 @@ def winrm_cmd_recv_msg(shell_id,cmd_id)
129112
header_data = action + selectors
130113
contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id)
131114
msg = winrm_envelope(contents)
132-
133115
return msg
134116
end
135117

@@ -139,7 +121,6 @@ def winrm_terminate_cmd_msg(shell_id,cmd_id)
139121
header_data = action + selectors
140122
contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id)
141123
msg = winrm_envelope(contents)
142-
143124
return msg
144125
end
145126

@@ -149,7 +130,6 @@ def winrm_delete_shell_msg(shell_id)
149130
header_data = action + selectors
150131
contents = winrm_header(header_data) + winrm_empty_body
151132
msg = winrm_envelope(contents)
152-
153133
return msg
154134
end
155135

@@ -159,28 +139,23 @@ def parse_wql_response(response)
159139
rows =[]
160140
rxml = REXML::Document.new(xml).root
161141
items = rxml.elements["///w:Items"]
162-
163142
items.elements.to_a("///w:XmlFragment").each do |node|
164143
row_data = []
165-
166144
node.elements.to_a.each do |sub_node|
167145
columns << sub_node.name
168146
row_data << sub_node.text
169147
end
170-
171148
rows << row_data
172149
end
173-
150+
columns.uniq!
174151
response_data = Rex::Ui::Text::Table.new(
175152
'Header' => "#{datastore['WQL']} (#{rhost})",
176153
'Indent' => 1,
177-
'Columns' => columns.uniq!
154+
'Columns' => columns
178155
)
179-
180156
rows.each do |row|
181157
response_data << row
182158
end
183-
184159
return response_data
185160
end
186161

@@ -197,17 +172,14 @@ def winrm_get_cmd_id(response)
197172
def winrm_get_cmd_streams(response)
198173
streams = {
199174
'stdout' => '',
200-
'stderr' => '',
175+
'stderr' => '',
201176
}
202-
203177
xml = response.body
204178
rxml = REXML::Document.new(xml).root
205-
206179
rxml.elements.to_a("//rsp:Stream").each do |node|
207180
next if node.text.nil?
208181
streams[node.attributes['Name']] << Rex::Text.base64_decode(node.text)
209182
end
210-
211183
return streams
212184
end
213185

@@ -222,25 +194,20 @@ def send_request_ntlm(data, timeout = 20)
222194
'username' => datastore['USERNAME'],
223195
'password' => datastore['PASSWORD']
224196
}
225-
226-
ntlm_options =
227-
{
228-
:signing => false,
229-
:usentlm2_session => datastore['NTLM::UseNTLM2_session'],
230-
:use_ntlmv2 => datastore['NTLM::UseNTLMv2'],
231-
:send_lm => datastore['NTLM::SendLM'],
232-
:send_ntlm => datastore['NTLM::SendNTLM']
233-
}
234-
197+
ntlm_options = {
198+
:signing => false,
199+
:usentlm2_session => datastore['NTLM::UseNTLM2_session'],
200+
:use_ntlmv2 => datastore['NTLM::UseNTLMv2'],
201+
:send_lm => datastore['NTLM::SendLM'],
202+
:send_ntlm => datastore['NTLM::SendNTLM']
203+
}
235204
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
236205
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
237206
domain_name = datastore['DOMAIN']
238207
ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name,
239208
workstation_name,
240209
ntlmssp_flags))
241-
242210
to = opts[:timeout] || timeout
243-
244211
begin
245212
c = connect(opts)
246213
ctype = "application/soap+xml;charset=UTF-8"
@@ -251,14 +218,11 @@ def send_request_ntlm(data, timeout = 20)
251218
'ctype' => ctype,
252219
'headers' => { 'Authorization' => ntlm_message_1},
253220
'data' => opts['data']
254-
}))
255-
221+
}))
256222
resp = c.send_recv(r, to)
257-
258223
unless resp.kind_of? Rex::Proto::Http::Response
259224
return [nil,nil]
260225
end
261-
262226
return [nil,nil] if resp.code == 404
263227
return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate']
264228
# Get the challenge and craft the response
@@ -293,7 +257,6 @@ def send_request_ntlm(data, timeout = 20)
293257
ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'],
294258
resp_lm, resp_ntlm, '', ntlmssp_flags)
295259
ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3)
296-
297260
# Send the response
298261
r = c.request_cgi(opts.merge({
299262
'uri' => opts['uri'],
@@ -302,13 +265,10 @@ def send_request_ntlm(data, timeout = 20)
302265
'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"},
303266
'data' => opts['data']
304267
}))
305-
306268
resp = c.send_recv(r, to, true)
307-
308269
unless resp.kind_of? Rex::Proto::Http::Response
309270
return [nil,nil]
310271
end
311-
312272
return [nil,nil] if resp.code == 404
313273
return [resp,c]
314274
rescue ::Errno::EPIPE, ::Timeout::Error
@@ -324,25 +284,20 @@ def target_url
324284
if rport == 5986 or datastore['SSL']
325285
proto = "https"
326286
end
327-
328287
if datastore['VHOST']
329288
return "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}"
330289
else
331290
return "#{proto}://#{rhost}:#{rport}#{@uri.to_s}"
332291
end
333292
end
334293

335-
336-
337294
private
338295

339296
def winrm_option_set(options)
340297
xml = "<w:OptionSet>"
341-
342298
options.each do |option_pair|
343299
xml << winrm_option(*option_pair)
344300
end
345-
346301
xml << "</w:OptionSet>"
347302
return xml
348303
end
@@ -353,11 +308,9 @@ def winrm_option(name,value)
353308

354309
def winrm_selector_set(selectors)
355310
xml = "<w:SelectorSet>"
356-
357311
selectors.each do |selector_pair|
358312
xml << winrm_selector(*selector_pair)
359313
end
360-
361314
xml << "</w:SelectorSet>"
362315
return xml
363316
end

modules/auxiliary/scanner/winrm/winrm_auth_methods.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ def run_host(ip)
5252
:name => 'winrm',
5353
:info => desc
5454
)
55-
print_good "Negotiate protocol supported" if methods.include? "Negotiate"
56-
print_good "Kerberos protocol supported" if methods.include? "Kerberos"
57-
print_good "Basic protocol supported" if methods.include? "Basic"
55+
print_good "#{ip}:#{rport}: Negotiate protocol supported" if methods.include? "Negotiate"
56+
print_good "#{ip}:#{rport}: Kerberos protocol supported" if methods.include? "Kerberos"
57+
print_good "#{ip}:#{rport}: Basic protocol supported" if methods.include? "Basic"
5858
else
5959
print_error "#{ip}:#{rport} Does not appear to be a WinRM server"
6060
end
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
##
2+
# $Id$
3+
##
4+
5+
##
6+
# This file is part of the Metasploit Framework and may be subject to
7+
# redistribution and commercial restrictions. Please see the Metasploit
8+
# web site for more information on licensing and terms of use.
9+
# http://metasploit.com/
10+
##
11+
12+
13+
require 'msf/core'
14+
require 'rex/proto/ntlm/message'
15+
16+
class Metasploit3 < Msf::Auxiliary
17+
18+
include Msf::Exploit::Remote::WinRM
19+
include Msf::Auxiliary::Report
20+
include Msf::Auxiliary::AuthBrute
21+
22+
include Msf::Auxiliary::Scanner
23+
24+
def initialize
25+
super(
26+
'Name' => 'WinRM Login Utility',
27+
'Version' => '$Revision$',
28+
'Description' => %q{
29+
This module attempts to authenticate to a WinRM service. It currently
30+
works only if the remote end allows Negotiate(NTLM) authentication.
31+
Kerberos is not currently supported.
32+
},
33+
'Author' => [ 'thelightcosine' ],
34+
'References' =>
35+
[
36+
[ 'CVE', '1999-0502'] # Weak password
37+
],
38+
'License' => MSF_LICENSE
39+
)
40+
41+
end
42+
43+
44+
def run_host(ip)
45+
unless accepts_ntlm_auth
46+
print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth"
47+
return
48+
end
49+
each_user_pass do |user, pass|
50+
resp,c = send_request_ntlm(test_request)
51+
if resp.nil?
52+
print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out"
53+
return
54+
elsif resp.code == 200
55+
cred_hash = {
56+
:host => ip,
57+
:port => rport,
58+
:sname => 'winrm',
59+
:pass => pass,
60+
:user => user,
61+
:source_type => "user_supplied",
62+
:active => true
63+
}
64+
report_auth_info(cred_hash)
65+
print_good "#{ip}:#{rport}: Valid credential found: #{user}:#{pass}"
66+
elsif resp.code == 401
67+
print_error "#{ip}:#{rport}: Login failed: #{user}:#{pass}"
68+
else
69+
print_error "Recieved unexpected Response Code: #{resp.code}"
70+
end
71+
end
72+
end
73+
74+
75+
def test_request
76+
data = winrm_wql_msg("Select Name,Status from Win32_Service")
77+
end
78+
79+
end

0 commit comments

Comments
 (0)