Merge branch 'dmaloney-r7-WinRM_piecemeal'

This commit is contained in:
sinn3r 2012-10-27 18:55:24 -05:00
commit 7a1c3e7cf6
3 changed files with 99 additions and 67 deletions

View File

@ -8,10 +8,8 @@ require 'rex/proto/ntlm/exceptions'
module Msf module Msf
module Exploit::Remote::WinRM module Exploit::Remote::WinRM
include Exploit::Remote::NTLM::Client include Exploit::Remote::NTLM::Client
include Exploit::Remote::HttpClient include Exploit::Remote::HttpClient
# #
# Constants # Constants
# #
@ -19,20 +17,15 @@ module Exploit::Remote::WinRM
NTLM_CONST ||= Rex::Proto::NTLM::Constants NTLM_CONST ||= Rex::Proto::NTLM::Constants
NTLM_UTILS ||= Rex::Proto::NTLM::Utils NTLM_UTILS ||= Rex::Proto::NTLM::Utils
NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions
def initialize(info = {}) def initialize(info = {})
super super
register_options( register_options(
[ [
Opt::RHOST,
Opt::RPORT(5985), Opt::RPORT(5985),
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]),
OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]),
OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentification', 'WORKSTATION']), OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentification', 'WORKSTATION']),
OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ]), OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ]),
OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]), OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]) OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
], self.class ], self.class
) )
@ -45,18 +38,15 @@ module Exploit::Remote::WinRM
'uri' => datastore['URI'], 'uri' => datastore['URI'],
'data' => Rex::Text.rand_text_alpha(8) 'data' => Rex::Text.rand_text_alpha(8)
} }
c = connect(opts)
c = connect(opts) to = opts[:timeout] || timeout
to = opts[:timeout] || timeout
ctype = "application/soap+xml;charset=UTF-8" ctype = "application/soap+xml;charset=UTF-8"
resp, c = send_request_cgi(opts.merge({ resp, c = send_request_cgi(opts.merge({
'uri' => opts['uri'], 'uri' => opts['uri'],
'method' => 'POST', 'method' => 'POST',
'ctype' => ctype, 'ctype' => ctype,
'data' => opts['data'] 'data' => opts['data']
}), to) }), to)
return resp return resp
end end
@ -71,18 +61,15 @@ module Exploit::Remote::WinRM
def winrm_run_cmd(cmd, timeout=20) def winrm_run_cmd(cmd, timeout=20)
resp,c = send_request_ntlm(winrm_open_shell_msg,timeout) resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
if resp.code == 401 if resp.code == 401
print_error "Login failure! Recheck supplied credentials." print_error "Login failure! Recheck supplied credentials."
return resp .code return resp .code
end end
unless resp.code == 200 unless resp.code == 200
print_error "Got unexpected response: \n #{resp.to_s}" print_error "Got unexpected response: \n #{resp.to_s}"
retval == resp.code || 0 retval == resp.code || 0
return retval return retval
end end
shell_id = winrm_get_shell_id(resp) shell_id = winrm_get_shell_id(resp)
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout) resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
cmd_id = winrm_get_cmd_id(resp) cmd_id = winrm_get_cmd_id(resp)
@ -90,7 +77,6 @@ module Exploit::Remote::WinRM
streams = winrm_get_cmd_streams(resp) streams = winrm_get_cmd_streams(resp)
resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout) resp,c = send_request_ntlm(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id)) resp,c = send_request_ntlm(winrm_delete_shell_msg(shell_id))
return streams return streams
end end
@ -98,7 +84,6 @@ module Exploit::Remote::WinRM
action = winrm_uri_action("wql") action = winrm_uri_action("wql")
contents = winrm_header(action) + winrm_wql_body(wql) contents = winrm_header(action) + winrm_wql_body(wql)
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -108,7 +93,6 @@ module Exploit::Remote::WinRM
header_data = action + options header_data = action + options
contents = winrm_header(header_data) + winrm_open_shell_body contents = winrm_header(header_data) + winrm_open_shell_body
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -119,7 +103,6 @@ module Exploit::Remote::WinRM
header_data = action + options + selectors header_data = action + options + selectors
contents = winrm_header(header_data) + winrm_cmd_body(cmd) contents = winrm_header(header_data) + winrm_cmd_body(cmd)
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -129,7 +112,6 @@ module Exploit::Remote::WinRM
header_data = action + selectors header_data = action + selectors
contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id) contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id)
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -139,7 +121,6 @@ module Exploit::Remote::WinRM
header_data = action + selectors header_data = action + selectors
contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id) contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id)
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -149,7 +130,6 @@ module Exploit::Remote::WinRM
header_data = action + selectors header_data = action + selectors
contents = winrm_header(header_data) + winrm_empty_body contents = winrm_header(header_data) + winrm_empty_body
msg = winrm_envelope(contents) msg = winrm_envelope(contents)
return msg return msg
end end
@ -159,28 +139,23 @@ module Exploit::Remote::WinRM
rows =[] rows =[]
rxml = REXML::Document.new(xml).root rxml = REXML::Document.new(xml).root
items = rxml.elements["///w:Items"] items = rxml.elements["///w:Items"]
items.elements.to_a("///w:XmlFragment").each do |node| items.elements.to_a("///w:XmlFragment").each do |node|
row_data = [] row_data = []
node.elements.to_a.each do |sub_node| node.elements.to_a.each do |sub_node|
columns << sub_node.name columns << sub_node.name
row_data << sub_node.text row_data << sub_node.text
end end
rows << row_data rows << row_data
end end
columns.uniq!
response_data = Rex::Ui::Text::Table.new( response_data = Rex::Ui::Text::Table.new(
'Header' => "#{datastore['WQL']} (#{rhost})", 'Header' => "#{datastore['WQL']} (#{rhost})",
'Indent' => 1, 'Indent' => 1,
'Columns' => columns.uniq! 'Columns' => columns
) )
rows.each do |row| rows.each do |row|
response_data << row response_data << row
end end
return response_data return response_data
end end
@ -197,17 +172,14 @@ module Exploit::Remote::WinRM
def winrm_get_cmd_streams(response) def winrm_get_cmd_streams(response)
streams = { streams = {
'stdout' => '', 'stdout' => '',
'stderr' => '', 'stderr' => '',
} }
xml = response.body xml = response.body
rxml = REXML::Document.new(xml).root rxml = REXML::Document.new(xml).root
rxml.elements.to_a("//rsp:Stream").each do |node| rxml.elements.to_a("//rsp:Stream").each do |node|
next if node.text.nil? next if node.text.nil?
streams[node.attributes['Name']] << Rex::Text.base64_decode(node.text) streams[node.attributes['Name']] << Rex::Text.base64_decode(node.text)
end end
return streams return streams
end end
@ -222,25 +194,20 @@ module Exploit::Remote::WinRM
'username' => datastore['USERNAME'], 'username' => datastore['USERNAME'],
'password' => datastore['PASSWORD'] 'password' => datastore['PASSWORD']
} }
ntlm_options = {
ntlm_options = :signing => false,
{ :usentlm2_session => datastore['NTLM::UseNTLM2_session'],
:signing => false, :use_ntlmv2 => datastore['NTLM::UseNTLMv2'],
:usentlm2_session => datastore['NTLM::UseNTLM2_session'], :send_lm => datastore['NTLM::SendLM'],
:use_ntlmv2 => datastore['NTLM::UseNTLMv2'], :send_ntlm => datastore['NTLM::SendNTLM']
:send_lm => datastore['NTLM::SendLM'], }
:send_ntlm => datastore['NTLM::SendNTLM']
}
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
domain_name = datastore['DOMAIN'] domain_name = datastore['DOMAIN']
ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name, ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name,
workstation_name, workstation_name,
ntlmssp_flags)) ntlmssp_flags))
to = opts[:timeout] || timeout to = opts[:timeout] || timeout
begin begin
c = connect(opts) c = connect(opts)
ctype = "application/soap+xml;charset=UTF-8" ctype = "application/soap+xml;charset=UTF-8"
@ -251,14 +218,11 @@ module Exploit::Remote::WinRM
'ctype' => ctype, 'ctype' => ctype,
'headers' => { 'Authorization' => ntlm_message_1}, 'headers' => { 'Authorization' => ntlm_message_1},
'data' => opts['data'] 'data' => opts['data']
})) }))
resp = c.send_recv(r, to) resp = c.send_recv(r, to)
unless resp.kind_of? Rex::Proto::Http::Response unless resp.kind_of? Rex::Proto::Http::Response
return [nil,nil] return [nil,nil]
end end
return [nil,nil] if resp.code == 404 return [nil,nil] if resp.code == 404
return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate'] return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate']
# Get the challenge and craft the response # Get the challenge and craft the response
@ -293,7 +257,6 @@ module Exploit::Remote::WinRM
ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'], ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'],
resp_lm, resp_ntlm, '', ntlmssp_flags) resp_lm, resp_ntlm, '', ntlmssp_flags)
ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3) ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3)
# Send the response # Send the response
r = c.request_cgi(opts.merge({ r = c.request_cgi(opts.merge({
'uri' => opts['uri'], 'uri' => opts['uri'],
@ -302,13 +265,10 @@ module Exploit::Remote::WinRM
'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"}, 'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"},
'data' => opts['data'] 'data' => opts['data']
})) }))
resp = c.send_recv(r, to, true) resp = c.send_recv(r, to, true)
unless resp.kind_of? Rex::Proto::Http::Response unless resp.kind_of? Rex::Proto::Http::Response
return [nil,nil] return [nil,nil]
end end
return [nil,nil] if resp.code == 404 return [nil,nil] if resp.code == 404
return [resp,c] return [resp,c]
rescue ::Errno::EPIPE, ::Timeout::Error rescue ::Errno::EPIPE, ::Timeout::Error
@ -324,7 +284,6 @@ module Exploit::Remote::WinRM
if rport == 5986 or datastore['SSL'] if rport == 5986 or datastore['SSL']
proto = "https" proto = "https"
end end
if datastore['VHOST'] if datastore['VHOST']
return "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}" return "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}"
else else
@ -332,17 +291,13 @@ module Exploit::Remote::WinRM
end end
end end
private private
def winrm_option_set(options) def winrm_option_set(options)
xml = "<w:OptionSet>" xml = "<w:OptionSet>"
options.each do |option_pair| options.each do |option_pair|
xml << winrm_option(*option_pair) xml << winrm_option(*option_pair)
end end
xml << "</w:OptionSet>" xml << "</w:OptionSet>"
return xml return xml
end end
@ -353,11 +308,9 @@ module Exploit::Remote::WinRM
def winrm_selector_set(selectors) def winrm_selector_set(selectors)
xml = "<w:SelectorSet>" xml = "<w:SelectorSet>"
selectors.each do |selector_pair| selectors.each do |selector_pair|
xml << winrm_selector(*selector_pair) xml << winrm_selector(*selector_pair)
end end
xml << "</w:SelectorSet>" xml << "</w:SelectorSet>"
return xml return xml
end end

View File

@ -52,9 +52,9 @@ class Metasploit3 < Msf::Auxiliary
:name => 'winrm', :name => 'winrm',
:info => desc :info => desc
) )
print_good "Negotiate protocol supported" if methods.include? "Negotiate" print_good "#{ip}:#{rport}: Negotiate protocol supported" if methods.include? "Negotiate"
print_good "Kerberos protocol supported" if methods.include? "Kerberos" print_good "#{ip}:#{rport}: Kerberos protocol supported" if methods.include? "Kerberos"
print_good "Basic protocol supported" if methods.include? "Basic" print_good "#{ip}:#{rport}: Basic protocol supported" if methods.include? "Basic"
else else
print_error "#{ip}:#{rport} Does not appear to be a WinRM server" print_error "#{ip}:#{rport} Does not appear to be a WinRM server"
end end

View File

@ -0,0 +1,79 @@
##
# $Id$
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
require 'rex/proto/ntlm/message'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::WinRM
include Msf::Auxiliary::Report
include Msf::Auxiliary::AuthBrute
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'WinRM Login Utility',
'Version' => '$Revision$',
'Description' => %q{
This module attempts to authenticate to a WinRM service. It currently
works only if the remote end allows Negotiate(NTLM) authentication.
Kerberos is not currently supported.
},
'Author' => [ 'thelightcosine' ],
'References' =>
[
[ 'CVE', '1999-0502'] # Weak password
],
'License' => MSF_LICENSE
)
end
def run_host(ip)
unless accepts_ntlm_auth
print_error "The Remote WinRM server (#{ip} does not appear to allow Negotiate(NTLM) auth"
return
end
each_user_pass do |user, pass|
resp,c = send_request_ntlm(test_request)
if resp.nil?
print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out"
return
elsif resp.code == 200
cred_hash = {
:host => ip,
:port => rport,
:sname => 'winrm',
:pass => pass,
:user => user,
:source_type => "user_supplied",
:active => true
}
report_auth_info(cred_hash)
print_good "#{ip}:#{rport}: Valid credential found: #{user}:#{pass}"
elsif resp.code == 401
print_error "#{ip}:#{rport}: Login failed: #{user}:#{pass}"
else
print_error "Recieved unexpected Response Code: #{resp.code}"
end
end
end
def test_request
data = winrm_wql_msg("Select Name,Status from Win32_Service")
end
end