WinRM mixin and basic discovery module
parent
ed3f87b738
commit
3a8dd261ae
|
@ -0,0 +1,480 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
require 'uri'
|
||||||
|
require 'digest'
|
||||||
|
require 'rex/proto/ntlm/crypt'
|
||||||
|
require 'rex/proto/ntlm/constants'
|
||||||
|
require 'rex/proto/ntlm/utils'
|
||||||
|
require 'rex/proto/ntlm/exceptions'
|
||||||
|
module Msf
|
||||||
|
|
||||||
|
module Exploit::Remote::WinRM
|
||||||
|
include Exploit::Remote::NTLM::Client
|
||||||
|
include Exploit::Remote::HttpClient
|
||||||
|
|
||||||
|
#
|
||||||
|
# Constants
|
||||||
|
#
|
||||||
|
NTLM_CRYPT ||= Rex::Proto::NTLM::Crypt
|
||||||
|
NTLM_CONST ||= Rex::Proto::NTLM::Constants
|
||||||
|
NTLM_UTILS ||= Rex::Proto::NTLM::Utils
|
||||||
|
NTLM_XCEPT ||= Rex::Proto::NTLM::Exceptions
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Opt::RHOST,
|
||||||
|
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('URI', [ true, "The URI of the WinRM service", "/wsman" ]),
|
||||||
|
OptString.new('USERNAME', [ false, 'A specific username to authenticate as' ]),
|
||||||
|
OptString.new('PASSWORD', [ false, 'A specific password to authenticate with' ]),
|
||||||
|
], self.class
|
||||||
|
)
|
||||||
|
|
||||||
|
register_advanced_options(
|
||||||
|
[
|
||||||
|
OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests',
|
||||||
|
Rex::Proto::Http::Client::DefaultUserAgent
|
||||||
|
]),
|
||||||
|
], self.class
|
||||||
|
)
|
||||||
|
|
||||||
|
register_evasion_options(
|
||||||
|
[
|
||||||
|
OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-all', 'u-random']]),
|
||||||
|
OptBool.new('HTTP::uri_full_url', [false, 'Use the full URL for all HTTP requests', false]),
|
||||||
|
OptInt.new('HTTP::pad_method_uri_count', [false, 'How many whitespace characters to use between the method and uri', 1]),
|
||||||
|
OptInt.new('HTTP::pad_uri_version_count', [false, 'How many whitespace characters to use between the uri and version', 1]),
|
||||||
|
OptEnum.new('HTTP::pad_method_uri_type', [false, 'What type of whitespace to use between the method and uri', 'space', ['space', 'tab', 'apache']]),
|
||||||
|
OptEnum.new('HTTP::pad_uri_version_type', [false, 'What type of whitespace to use between the uri and version', 'space', ['space', 'tab', 'apache']]),
|
||||||
|
OptBool.new('HTTP::method_random_valid', [false, 'Use a random, but valid, HTTP method for request', false]),
|
||||||
|
OptBool.new('HTTP::method_random_invalid', [false, 'Use a random invalid, HTTP method for request', false]),
|
||||||
|
OptBool.new('HTTP::method_random_case', [false, 'Use random casing for the HTTP method', false]),
|
||||||
|
OptBool.new('HTTP::uri_dir_self_reference', [false, 'Insert self-referential directories into the uri', false]),
|
||||||
|
OptBool.new('HTTP::uri_dir_fake_relative', [false, 'Insert fake relative directories into the uri', false]),
|
||||||
|
OptBool.new('HTTP::uri_use_backslashes', [false, 'Use back slashes instead of forward slashes in the uri ', false]),
|
||||||
|
OptBool.new('HTTP::pad_fake_headers', [false, 'Insert random, fake headers into the HTTP request', false]),
|
||||||
|
OptInt.new('HTTP::pad_fake_headers_count', [false, 'How many fake headers to insert into the HTTP request', 0]),
|
||||||
|
OptBool.new('HTTP::pad_get_params', [false, 'Insert random, fake query string variables into the request', false]),
|
||||||
|
OptInt.new('HTTP::pad_get_params_count', [false, 'How many fake query string variables to insert into the request', 16]),
|
||||||
|
OptBool.new('HTTP::pad_post_params', [false, 'Insert random, fake post variables into the request', false]),
|
||||||
|
OptInt.new('HTTP::pad_post_params_count', [false, 'How many fake post variables to insert into the request', 16]),
|
||||||
|
OptBool.new('HTTP::uri_fake_end', [false, 'Add a fake end of URI (eg: /%20HTTP/1.0/../../)', false]),
|
||||||
|
OptBool.new('HTTP::uri_fake_params_start', [false, 'Add a fake start of params to the URI (eg: /%3fa=b/../)', false]),
|
||||||
|
OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', false])
|
||||||
|
], self.class)
|
||||||
|
|
||||||
|
register_autofilter_ports([ 80,443,5985,5986 ])
|
||||||
|
register_autofilter_services(%W{ winrm })
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_poke(timeout = 20)
|
||||||
|
opts = {
|
||||||
|
'uri' => datastore['URI'],
|
||||||
|
'data' => 'test'
|
||||||
|
}
|
||||||
|
c = connect(opts)
|
||||||
|
to = opts[:timeout] || timeout
|
||||||
|
ctype = "application/soap+xml;charset=UTF-8"
|
||||||
|
resp, c = send_request_cgi(opts.merge({
|
||||||
|
'uri' => opts['uri'],
|
||||||
|
'method' => 'POST',
|
||||||
|
'ctype' => ctype,
|
||||||
|
'data' => opts['data']
|
||||||
|
}))
|
||||||
|
return resp
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_auth_methods(resp)
|
||||||
|
return [] unless resp.code == 401
|
||||||
|
methods = []
|
||||||
|
methods << "Negotiate" if resp.headers['WWW-Authenticate'].include? "Negotiate"
|
||||||
|
methods << "Kerberos" if resp.headers['WWW-Authenticate'].include? "Kerberos"
|
||||||
|
methods << "Basic" if resp.headers['WWW-Authenticate'].include? "Basic"
|
||||||
|
return methods
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_run_cmd(cmd, timeout=20)
|
||||||
|
resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
|
||||||
|
if resp.code == 401
|
||||||
|
print_error "Login failure! Recheck supplied credentials."
|
||||||
|
return resp .code
|
||||||
|
end
|
||||||
|
|
||||||
|
unless resp.code == 200
|
||||||
|
print_error "Got unexpected response: \n #{resp.to_s}"
|
||||||
|
return resp.code
|
||||||
|
end
|
||||||
|
shell_id = winrm_get_shell_id(resp)
|
||||||
|
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
|
||||||
|
cmd_id = winrm_get_cmd_id(resp)
|
||||||
|
resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
|
||||||
|
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_delete_shell_msg(shell_id))
|
||||||
|
return streams
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def winrm_wql_msg(wql)
|
||||||
|
action = winrm_uri_action("wql")
|
||||||
|
contents = winrm_header(action) + winrm_wql_body(wql)
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_open_shell_msg
|
||||||
|
action = winrm_uri_action("create_shell")
|
||||||
|
options = winrm_option_set([['WINRS_NOPROFILE', 'FALSE'], ['WINRS_CODEPAGE', '437']])
|
||||||
|
header_data = action + options
|
||||||
|
contents = winrm_header(header_data) + winrm_open_shell_body
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_cmd_msg(cmd,shell_id)
|
||||||
|
action = winrm_uri_action("send_cmd")
|
||||||
|
options = winrm_option_set([['WINRS_CONSOLEMODE_STDIN', 'TRUE'], ['WINRS_SKIP_CMD_SHELL', 'FALSE']])
|
||||||
|
selectors = winrm_selector_set([['ShellId', shell_id]])
|
||||||
|
header_data = action + options + selectors
|
||||||
|
contents = winrm_header(header_data) + winrm_cmd_body(cmd)
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_cmd_recv_msg(shell_id,cmd_id)
|
||||||
|
action = winrm_uri_action("recv_cmd")
|
||||||
|
selectors = winrm_selector_set([['ShellId', shell_id]])
|
||||||
|
header_data = action + selectors
|
||||||
|
contents = winrm_header(header_data) + winrm_cmd_recv_body(cmd_id)
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_terminate_cmd_msg(shell_id,cmd_id)
|
||||||
|
action = winrm_uri_action("signal_shell")
|
||||||
|
selectors = winrm_selector_set([['ShellId', shell_id]])
|
||||||
|
header_data = action + selectors
|
||||||
|
contents = winrm_header(header_data) + winrm_terminate_cmd_body(cmd_id)
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_delete_shell_msg(shell_id)
|
||||||
|
action = winrm_uri_action("delete_shell")
|
||||||
|
selectors = winrm_selector_set([['ShellId', shell_id]])
|
||||||
|
header_data = action + selectors
|
||||||
|
contents = winrm_header(header_data) + winrm_empty_body
|
||||||
|
msg = winrm_envelope(contents)
|
||||||
|
return msg
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def parse_wql_response(response)
|
||||||
|
xml = response.body
|
||||||
|
columns = []
|
||||||
|
rows =[]
|
||||||
|
rxml = REXML::Document.new(xml).root
|
||||||
|
items = rxml.elements["///w:Items"]
|
||||||
|
items.elements.to_a("///w:XmlFragment").each do |node|
|
||||||
|
row_data = []
|
||||||
|
node.elements.to_a.each do |sub_node|
|
||||||
|
columns << sub_node.name
|
||||||
|
row_data << sub_node.text
|
||||||
|
end
|
||||||
|
rows << row_data
|
||||||
|
end
|
||||||
|
columns.uniq!
|
||||||
|
response_data = Rex::Ui::Text::Table.new(
|
||||||
|
'Header' => "#{datastore['WQL']} (#{rhost})",
|
||||||
|
'Indent' => 1,
|
||||||
|
'Columns' => columns
|
||||||
|
)
|
||||||
|
rows.each do |row|
|
||||||
|
response_data << row
|
||||||
|
end
|
||||||
|
return response_data
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_get_shell_id(response)
|
||||||
|
xml = response.body
|
||||||
|
shell_id = REXML::Document.new(xml).elements["//w:Selector"].text
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_get_cmd_id(response)
|
||||||
|
xml = response.body
|
||||||
|
cmd_id = REXML::Document.new(xml).elements["//rsp:CommandId"].text
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_get_cmd_streams(response)
|
||||||
|
streams = {
|
||||||
|
'stdout' => '',
|
||||||
|
'stderr' => '',
|
||||||
|
}
|
||||||
|
xml = response.body
|
||||||
|
rxml = REXML::Document.new(xml).root
|
||||||
|
rxml.elements.to_a("//rsp:Stream").each do |node|
|
||||||
|
next if node.text.nil?
|
||||||
|
streams[node.attributes['Name']] << Base64.decode64(node.text)
|
||||||
|
end
|
||||||
|
return streams
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_option_set(options)
|
||||||
|
xml = "<w:OptionSet>"
|
||||||
|
options.each do |option_pair|
|
||||||
|
xml << winrm_option(*option_pair)
|
||||||
|
end
|
||||||
|
xml << "</w:OptionSet>"
|
||||||
|
return xml
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_option(name,value)
|
||||||
|
%Q{<w:Option Name="#{name}">#{value}</w:Option>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_selector_set(selectors)
|
||||||
|
xml = "<w:SelectorSet>"
|
||||||
|
selectors.each do |selector_pair|
|
||||||
|
xml << winrm_selector(*selector_pair)
|
||||||
|
end
|
||||||
|
xml << "</w:SelectorSet>"
|
||||||
|
return xml
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_selector(name,value)
|
||||||
|
%Q{<w:Selector Name="#{name}">#{value}</w:Selector>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_wql_body(wql)
|
||||||
|
%Q{
|
||||||
|
<env:Body>
|
||||||
|
<n:Enumerate>
|
||||||
|
<w:OptimizeEnumeration xsi:nil="true"/>
|
||||||
|
<w:MaxElements>32000</w:MaxElements>
|
||||||
|
<w:Filter Dialect="http://schemas.microsoft.com/wbem/wsman/1/WQL">#{wql}</w:Filter>
|
||||||
|
</n:Enumerate>
|
||||||
|
</env:Body>
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_open_shell_body
|
||||||
|
%q{<env:Body>
|
||||||
|
<rsp:Shell>
|
||||||
|
<rsp:InputStreams>stdin</rsp:InputStreams>
|
||||||
|
<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>
|
||||||
|
</rsp:Shell>
|
||||||
|
</env:Body>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_cmd_body(cmd)
|
||||||
|
%Q{ <env:Body>
|
||||||
|
<rsp:CommandLine>
|
||||||
|
<rsp:Command>"#{cmd}"</rsp:Command>
|
||||||
|
</rsp:CommandLine>
|
||||||
|
</env:Body>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_cmd_recv_body(cmd_id)
|
||||||
|
%Q{<env:Body>
|
||||||
|
<rsp:Receive>
|
||||||
|
<rsp:DesiredStream CommandId="#{cmd_id}">stdout stderr</rsp:DesiredStream>
|
||||||
|
</rsp:Receive>
|
||||||
|
</env:Body>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_terminate_cmd_body(cmd_id)
|
||||||
|
%Q{ <env:Body>
|
||||||
|
<rsp:Signal CommandId="#{cmd_id}">
|
||||||
|
<rsp:Code>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate</rsp:Code>
|
||||||
|
</rsp:Signal>
|
||||||
|
</env:Body>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_empty_body
|
||||||
|
%q{<env:Body/>}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_envelope(data)
|
||||||
|
%Q{
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<env:Envelope xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:b="http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd"
|
||||||
|
xmlns:cfg="http://schemas.microsoft.com/wbem/wsman/1/config" xmlns:env="http://www.w3.org/2003/05/soap-envelope"
|
||||||
|
xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"
|
||||||
|
xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
|
||||||
|
xmlns:x="http://schemas.xmlsoap.org/ws/2004/09/transfer" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> #{data}</env:Envelope>
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_header(data)
|
||||||
|
%Q{
|
||||||
|
<env:Header>
|
||||||
|
<a:To>#{target_url}</a:To>
|
||||||
|
<a:ReplyTo>
|
||||||
|
<a:Address mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
|
||||||
|
</a:ReplyTo>
|
||||||
|
<w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
|
||||||
|
<a:MessageID>uuid:#{generate_uuid}</a:MessageID>
|
||||||
|
<w:Locale mustUnderstand="false" xml:lang="en-US"/>
|
||||||
|
<p:DataLocale mustUnderstand="false" xml:lang="en-US"/>
|
||||||
|
<w:OperationTimeout>PT60S</w:OperationTimeout>
|
||||||
|
#{data}
|
||||||
|
</env:Header>
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def winrm_uri_action(type)
|
||||||
|
case type
|
||||||
|
when "wql"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/*</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate</a:Action>}
|
||||||
|
when "create_shell"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Create</a:Action>}
|
||||||
|
when "send_cmd"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command</a:Action>}
|
||||||
|
when "recv_cmd"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>}
|
||||||
|
when "signal_shell"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal</a:Action>}
|
||||||
|
when "delete_shell"
|
||||||
|
return %q{<w:ResourceURI mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd</w:ResourceURI>
|
||||||
|
<a:Action mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete</a:Action>}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_uuid
|
||||||
|
bytes = ::SecureRandom.random_bytes(16)
|
||||||
|
::Rex::Proto::DCERPC::UUID.uuid_unpack(bytes)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def send_request_ntlm(data, timeout = 20)
|
||||||
|
opts = {
|
||||||
|
'uri' => datastore['URI'],
|
||||||
|
'data' => data,
|
||||||
|
'username' => datastore['USERNAME'],
|
||||||
|
'password' => datastore['PASSWORD']
|
||||||
|
}
|
||||||
|
ntlm_options = {
|
||||||
|
:signing => false,
|
||||||
|
:usentlm2_session => datastore['NTLM::UseNTLM2_session'],
|
||||||
|
:use_ntlmv2 => datastore['NTLM::UseNTLMv2'],
|
||||||
|
:send_lm => datastore['NTLM::SendLM'],
|
||||||
|
:send_ntlm => datastore['NTLM::SendNTLM']
|
||||||
|
}
|
||||||
|
|
||||||
|
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
|
||||||
|
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
|
||||||
|
domain_name = datastore['DOMAIN']
|
||||||
|
|
||||||
|
ntlm_message_1 = "NEGOTIATE " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name,
|
||||||
|
workstation_name,
|
||||||
|
ntlmssp_flags))
|
||||||
|
to = opts[:timeout] || timeout
|
||||||
|
begin
|
||||||
|
c = connect(opts)
|
||||||
|
|
||||||
|
ctype = "application/soap+xml;charset=UTF-8"
|
||||||
|
|
||||||
|
# First request to get the challenge
|
||||||
|
r = c.request_cgi(opts.merge({
|
||||||
|
'uri' => opts['uri'],
|
||||||
|
'method' => 'POST',
|
||||||
|
'ctype' => ctype,
|
||||||
|
'headers' => { 'Authorization' => ntlm_message_1},
|
||||||
|
'data' => opts['data']
|
||||||
|
}))
|
||||||
|
resp = c.send_recv(r, to)
|
||||||
|
unless resp.kind_of? Rex::Proto::Http::Response
|
||||||
|
return [nil,nil]
|
||||||
|
end
|
||||||
|
return [nil,nil] if resp.code == 404
|
||||||
|
return [nil,nil] unless resp.code == 401 && resp.headers['WWW-Authenticate']
|
||||||
|
|
||||||
|
# Get the challenge and craft the response
|
||||||
|
ntlm_challenge = resp.headers['WWW-Authenticate'].match(/NEGOTIATE ([A-Z0-9\x2b\x2f=]+)/i)[1]
|
||||||
|
return [nil,nil] unless ntlm_challenge
|
||||||
|
|
||||||
|
|
||||||
|
#old and simplier method but not compatible with windows 7/2008r2
|
||||||
|
#ntlm_message_2 = Rex::Proto::NTLM::Message.decode64(ntlm_challenge)
|
||||||
|
#ntlm_message_3 = ntlm_message_2.response( {:user => opts['username'],:password => opts['password']}, {:ntlmv2 => true})
|
||||||
|
|
||||||
|
ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge)
|
||||||
|
blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(ntlm_message_2)
|
||||||
|
|
||||||
|
challenge_key = blob_data[:challenge_key]
|
||||||
|
server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
|
||||||
|
#netbios name
|
||||||
|
default_name = blob_data[:default_name] || ''
|
||||||
|
#netbios domain
|
||||||
|
default_domain = blob_data[:default_domain] || ''
|
||||||
|
#dns name
|
||||||
|
dns_host_name = blob_data[:dns_host_name] || ''
|
||||||
|
#dns domain
|
||||||
|
dns_domain_name = blob_data[:dns_domain_name] || ''
|
||||||
|
#Client time
|
||||||
|
chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''
|
||||||
|
|
||||||
|
spnopt = {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
|
||||||
|
|
||||||
|
resp_lm,
|
||||||
|
resp_ntlm,
|
||||||
|
client_challenge,
|
||||||
|
ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(opts['username'], opts['password'], challenge_key,
|
||||||
|
domain_name, default_name, default_domain,
|
||||||
|
dns_host_name, dns_domain_name, chall_MsvAvTimestamp,
|
||||||
|
spnopt, ntlm_options)
|
||||||
|
|
||||||
|
ntlm_message_3 = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, opts['username'],
|
||||||
|
resp_lm, resp_ntlm, '', ntlmssp_flags)
|
||||||
|
ntlm_message_3 = Rex::Text::encode_base64(ntlm_message_3)
|
||||||
|
|
||||||
|
# Send the response
|
||||||
|
r = c.request_cgi(opts.merge({
|
||||||
|
'uri' => opts['uri'],
|
||||||
|
'method' => 'POST',
|
||||||
|
'ctype' => ctype,
|
||||||
|
'headers' => { 'Authorization' => "NEGOTIATE #{ntlm_message_3}"},
|
||||||
|
'data' => opts['data']
|
||||||
|
}))
|
||||||
|
resp = c.send_recv(r, to, true)
|
||||||
|
unless resp.kind_of? Rex::Proto::Http::Response
|
||||||
|
return [nil,nil]
|
||||||
|
end
|
||||||
|
return [nil,nil] if resp.code == 404
|
||||||
|
return [resp,c]
|
||||||
|
|
||||||
|
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def accepts_ntlm_auth
|
||||||
|
parse_auth_methods(winrm_poke).include? "Negotiate"
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_url
|
||||||
|
proto = "http"
|
||||||
|
if rport == 5986 or datastore['SSL']
|
||||||
|
proto = "https"
|
||||||
|
end
|
||||||
|
if datastore['VHOST']
|
||||||
|
return "#{proto}://#{datastore ['VHOST']}:#{rport}#{@uri.to_s}"
|
||||||
|
else
|
||||||
|
return "#{proto}://#{rhost}:#{rport}#{@uri.to_s}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,68 @@
|
||||||
|
##
|
||||||
|
# $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::Scanner
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super(
|
||||||
|
'Name' => 'WinRM Authentication Methos Detection',
|
||||||
|
'Version' => '$Revision$',
|
||||||
|
'Description' => %q{
|
||||||
|
This module sends a request to a an http/https service to see if it is a WinRM service.
|
||||||
|
If it is a WinRM service, it also gathers the Authentication Methods supported.
|
||||||
|
},
|
||||||
|
'Author' => [ 'thelightcosine' ],
|
||||||
|
'License' => MSF_LICENSE
|
||||||
|
)
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptString.new('URI', [ true, "The URI of the WinRM service", "/wsman" ])
|
||||||
|
], self.class)
|
||||||
|
|
||||||
|
deregister_options('USERNAME', 'PASSWORD')
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def run_host(ip)
|
||||||
|
resp = winrm_poke
|
||||||
|
if resp.code == 401 and resp.headers['Server'].include? "Microsoft-HTTPAPI"
|
||||||
|
methods = parse_auth_methods(resp)
|
||||||
|
desc = resp.headers['Server'] + " Authentication Methods: " + methods.to_s
|
||||||
|
report_service(
|
||||||
|
:host => ip,
|
||||||
|
:port => rport,
|
||||||
|
:proto => 'tcp',
|
||||||
|
:name => 'winrm',
|
||||||
|
:info => desc
|
||||||
|
)
|
||||||
|
print_good "Negotiate protocol supported" if methods.include? "Negotiate"
|
||||||
|
print_good "Kerberos protocol supported" if methods.include? "Kerberos"
|
||||||
|
print_good "Basic protocol supported" if methods.include? "Basic"
|
||||||
|
else
|
||||||
|
print_error "#{ip}:#{rport} Does not appear to be a WinRM server"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue