# -*- 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::RPORT(5985),
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_autofilter_ports([ 80,443,5985,5986 ])
register_autofilter_services(%W{ winrm })
end
def winrm_poke(timeout = 20)
opts = {
'uri' => datastore['URI'],
'data' => Rex::Text.rand_text_alpha(8)
}
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']
}), to)
return resp
end
def parse_auth_methods(resp)
return [] unless resp and 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.nil?
print_error "Recieved no reply from server"
return nil
end
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}"
retval = resp.code || 0
return retval
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_run_cmd_hanging(cmd, timeout=20)
resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
if resp.nil?
print_error "Recieved no reply from server"
return nil
end
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}"
retval = resp.code || 0
return retval
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)
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)
return nil if response.nil?
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)
return nil if response.nil?
xml = response.body
shell_id = REXML::Document.new(xml).elements["//w:Selector"].text
end
def winrm_get_cmd_id(response)
return nil if response.nil?
xml = response.body
cmd_id = REXML::Document.new(xml).elements["//rsp:CommandId"].text
end
def winrm_get_cmd_streams(response)
return nil if response.nil?
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']] << Rex::Text.decode_base64(node.text)
end
return streams
end
def generate_uuid
::Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
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
def wmi_namespace
return datastore['NAMESPACE'] if datastore['NAMESPACE']
return @namespace_override if @namespace_override
return "/root/cimv2/"
end
private
def winrm_option_set(options)
xml = ""
options.each do |option_pair|
xml << winrm_option(*option_pair)
end
xml << ""
return xml
end
def winrm_option(name,value)
%Q{#{value}}
end
def winrm_selector_set(selectors)
xml = ""
selectors.each do |selector_pair|
xml << winrm_selector(*selector_pair)
end
xml << ""
return xml
end
def winrm_selector(name,value)
%Q{#{value}}
end
def winrm_wql_body(wql)
%Q{
32000
#{wql}
}
end
def winrm_open_shell_body
%q{
stdin
stdout stderr
}
end
def winrm_cmd_body(cmd)
%Q{
"#{cmd}"
}
end
def winrm_cmd_recv_body(cmd_id)
%Q{
stdout stderr
}
end
def winrm_terminate_cmd_body(cmd_id)
%Q{
http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate
}
end
def winrm_empty_body
%q{}
end
def winrm_envelope(data)
%Q{
#{data}
}
end
def winrm_header(data)
%Q{
#{target_url}
http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
153600
uuid:#{generate_uuid}
PT60S
#{data}
}
end
def winrm_uri_action(type)
case type
when "wql"
return %Q{http://schemas.microsoft.com/wbem/wsman/1/wmi#{wmi_namespace}*
http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate}
when "create_shell"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
http://schemas.xmlsoap.org/ws/2004/09/transfer/Create}
when "send_cmd"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command}
when "recv_cmd"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive}
when "signal_shell"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal}
when "delete_shell"
return %q{http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete}
end
end
end
end