Merge branch 'dmaloney-r7-http/auth_methods' into rapid7
commit
5b3b0a8b6d
|
@ -188,7 +188,9 @@ module Anemone
|
|||
context,
|
||||
url.scheme == "https",
|
||||
'SSLv23',
|
||||
@opts[:proxies]
|
||||
@opts[:proxies],
|
||||
@opts[:username],
|
||||
@opts[:password]
|
||||
)
|
||||
|
||||
conn.set_config(
|
||||
|
|
|
@ -22,7 +22,9 @@ module Auxiliary::HttpCrawler
|
|||
Opt::Proxies,
|
||||
OptInt.new('MAX_PAGES', [ true, 'The maximum number of pages to crawl per URL', 500]),
|
||||
OptInt.new('MAX_MINUTES', [ true, 'The maximum number of minutes to spend on each URL', 5]),
|
||||
OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4])
|
||||
OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4]),
|
||||
OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']),
|
||||
OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication'])
|
||||
], self.class
|
||||
)
|
||||
|
||||
|
@ -34,8 +36,6 @@ module Auxiliary::HttpCrawler
|
|||
OptString.new('UserAgent', [true, 'The User-Agent header to use for all requests',
|
||||
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
||||
]),
|
||||
OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']),
|
||||
OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']),
|
||||
OptString.new('HTTPAdditionalHeaders', [false, "A list of additional headers to send (separated by \\x01)"]),
|
||||
OptString.new('HTTPCookie', [false, "A HTTP cookie header to send with each request"]),
|
||||
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]),
|
||||
|
@ -118,8 +118,9 @@ module Auxiliary::HttpCrawler
|
|||
:info => ""
|
||||
})
|
||||
|
||||
if datastore['BasicAuthUser']
|
||||
t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '')
|
||||
if datastore['USERNAME'] and datastore['USERNAME'] != ''
|
||||
t[:username] = datastore['USERNAME'].to_s
|
||||
t[:password] = datastore['PASSWORD'].to_s
|
||||
end
|
||||
|
||||
if datastore['HTTPCookie']
|
||||
|
@ -278,9 +279,8 @@ module Auxiliary::HttpCrawler
|
|||
opts[:cookies] = t[:cookies]
|
||||
end
|
||||
|
||||
if t[:http_basic_auth]
|
||||
opts[:http_basic_auth] = t[:http_basic_auth]
|
||||
end
|
||||
opts[:username] = t[:username] || ''
|
||||
opts[:password] =t[:password] || ''
|
||||
|
||||
opts
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ require 'uri'
|
|||
module Msf
|
||||
class Auxiliary::Web::HTTP
|
||||
|
||||
|
||||
class Request
|
||||
attr_accessor :url
|
||||
attr_reader :opts
|
||||
|
@ -69,6 +70,7 @@ class Auxiliary::Web::HTTP
|
|||
attr_reader :framework
|
||||
|
||||
attr_accessor :redirect_limit
|
||||
attr_accessor :username , :password
|
||||
|
||||
def initialize( opts = {} )
|
||||
@opts = opts.dup
|
||||
|
@ -84,8 +86,8 @@ class Auxiliary::Web::HTTP
|
|||
|
||||
@request_opts = {}
|
||||
if opts[:auth].is_a? Hash
|
||||
@request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' +
|
||||
opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' )
|
||||
@username = opts[:auth][:user].to_s
|
||||
@password = opts[:auth][:password].to_s
|
||||
end
|
||||
|
||||
self.redirect_limit = opts[:redirect_limit] || 20
|
||||
|
@ -105,7 +107,9 @@ class Auxiliary::Web::HTTP
|
|||
opts[:target].port,
|
||||
{},
|
||||
opts[:target].ssl,
|
||||
'SSLv23'
|
||||
'SSLv23',
|
||||
username,
|
||||
password
|
||||
)
|
||||
|
||||
c.set_config({
|
||||
|
@ -296,6 +300,10 @@ class Auxiliary::Web::HTTP
|
|||
opts['data'] = body if body
|
||||
|
||||
c = connect
|
||||
if opts['username'] and opts['username'] != ''
|
||||
c.username = opts['username'].to_s
|
||||
c.password = opts['password'].to_s
|
||||
end
|
||||
Response.from_rex_response c.send_recv( c.request_cgi( opts ), timeout )
|
||||
rescue ::Timeout::Error
|
||||
Response.timed_out
|
||||
|
|
|
@ -37,7 +37,9 @@ module Exploit::Remote::HttpClient
|
|||
Opt::RHOST,
|
||||
Opt::RPORT(80),
|
||||
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
|
||||
Opt::Proxies
|
||||
Opt::Proxies,
|
||||
OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']),
|
||||
OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']),
|
||||
], self.class
|
||||
)
|
||||
|
||||
|
@ -46,10 +48,6 @@ module Exploit::Remote::HttpClient
|
|||
OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests',
|
||||
Rex::Proto::Http::Client::DefaultUserAgent
|
||||
]),
|
||||
OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']),
|
||||
OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']),
|
||||
OptString.new('DigestAuthUser', [false, 'The HTTP username to specify for digest authentication']),
|
||||
OptString.new('DigestAuthPassword', [false, 'The HTTP password to specify for digest authentication']),
|
||||
OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]),
|
||||
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']]),
|
||||
|
@ -156,7 +154,9 @@ module Exploit::Remote::HttpClient
|
|||
},
|
||||
dossl,
|
||||
ssl_version,
|
||||
proxies
|
||||
proxies,
|
||||
datastore['USERNAME'],
|
||||
datastore['PASSWORD']
|
||||
)
|
||||
|
||||
# Configure the HTTP client with the supplied parameter
|
||||
|
@ -184,7 +184,15 @@ module Exploit::Remote::HttpClient
|
|||
'pad_post_params_count' => datastore['HTTP::pad_post_params_count'],
|
||||
'uri_fake_end' => datastore['HTTP::uri_fake_end'],
|
||||
'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'],
|
||||
'header_folding' => datastore['HTTP::header_folding']
|
||||
'header_folding' => datastore['HTTP::header_folding'],
|
||||
'usentlm2_session' => datastore['NTLM::UseNTLM2_session'],
|
||||
'use_ntlmv2' => datastore['NTLM::UseNTLMv2'],
|
||||
'send_lm' => datastore['NTLM::SendLM'],
|
||||
'send_ntlm' => datastore['NTLM::SendNTLM'],
|
||||
'SendSPN' => datastore['NTLM::SendSPN'],
|
||||
'UseLMKey' => datastore['NTLM::UseLMKey'],
|
||||
'domain' => datastore['DOMAIN'],
|
||||
'DigestAuthIIS' => datastore['DigestAuthIIS']
|
||||
)
|
||||
|
||||
# If this connection is global, persist it
|
||||
|
@ -266,6 +274,10 @@ module Exploit::Remote::HttpClient
|
|||
def send_request_cgi(opts={}, timeout = 20)
|
||||
begin
|
||||
c = connect(opts)
|
||||
if opts['username'] and opts['username'] != ''
|
||||
c.username = opts['username'].to_s
|
||||
c.password = opts['password'].to_s
|
||||
end
|
||||
r = c.request_cgi(opts)
|
||||
c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||
|
@ -277,241 +289,8 @@ module Exploit::Remote::HttpClient
|
|||
# Combine the user/pass into an auth string for the HTTP Client
|
||||
#
|
||||
def basic_auth
|
||||
return if not datastore['BasicAuthUser']
|
||||
datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '')
|
||||
end
|
||||
|
||||
#
|
||||
# Connect to the server, and perform NTLM authentication for this session.
|
||||
# Note the return value is [resp,c], so the caller can have access to both
|
||||
# the last response, and the connection itself -- this is important since
|
||||
# NTLM auth is bound to this particular TCP session.
|
||||
#
|
||||
# TODO: Fix up error messaging a lot more -- right now it's pretty hard
|
||||
# to tell what all went wrong.
|
||||
#
|
||||
def send_http_auth_ntlm(opts={}, timeout = 20)
|
||||
#ntlm_message_1 = "NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
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 = "NTLM " + Rex::Text::encode_base64(NTLM_UTILS::make_ntlmssp_blob_init( domain_name,
|
||||
workstation_name,
|
||||
ntlmssp_flags))
|
||||
to = opts[:timeout] || timeout
|
||||
begin
|
||||
c = connect(opts)
|
||||
|
||||
# First request to get the challenge
|
||||
r = c.request_cgi(opts.merge({
|
||||
'uri' => opts['uri'],
|
||||
'method' => 'GET',
|
||||
'headers' => { 'Authorization' => ntlm_message_1 }}))
|
||||
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(/NTLM ([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' => 'GET',
|
||||
'headers' => { 'Authorization' => "NTLM #{ntlm_message_3}"}}))
|
||||
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 send_digest_request_cgi(opts={}, timeout=20)
|
||||
@nonce_count = 0
|
||||
|
||||
return [nil,nil] if not (datastore['DigestAuthUser'] or opts['DigestAuthUser'])
|
||||
to = opts['timeout'] || timeout
|
||||
|
||||
digest_user = datastore['DigestAuthUser'] || opts['DigestAuthUser'] || ""
|
||||
digest_password = datastore['DigestAuthPassword'] || opts['DigestAuthPassword'] || ""
|
||||
|
||||
method = opts['method']
|
||||
path = opts['uri']
|
||||
iis = true
|
||||
if (opts['DigestAuthIIS'] == false or datastore['DigestAuthIIS'] == false)
|
||||
iis = false
|
||||
end
|
||||
|
||||
begin
|
||||
@nonce_count += 1
|
||||
|
||||
resp = opts['response']
|
||||
|
||||
if not resp
|
||||
# Get authentication-challenge from server, and read out parameters required
|
||||
c = connect(opts)
|
||||
r = c.request_cgi(opts.merge({
|
||||
'uri' => path,
|
||||
'method' => method }))
|
||||
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
|
||||
if resp.code != 401
|
||||
return resp
|
||||
end
|
||||
return [nil,nil] unless resp.headers['WWW-Authenticate']
|
||||
end
|
||||
|
||||
# Don't anchor this regex to the beginning of string because header
|
||||
# folding makes it appear later when the server presents multiple
|
||||
# WWW-Authentication options (such as is the case with IIS configured
|
||||
# for Digest or NTLM).
|
||||
resp['www-authenticate'] =~ /Digest (.*)/
|
||||
|
||||
parameters = {}
|
||||
$1.split(/,[[:space:]]*/).each do |p|
|
||||
k, v = p.split("=", 2)
|
||||
parameters[k] = v.gsub('"', '')
|
||||
end
|
||||
|
||||
qop = parameters['qop']
|
||||
|
||||
if parameters['algorithm'] =~ /(.*?)(-sess)?$/
|
||||
algorithm = case $1
|
||||
when 'MD5' then Digest::MD5
|
||||
when 'SHA1' then Digest::SHA1
|
||||
when 'SHA2' then Digest::SHA2
|
||||
when 'SHA256' then Digest::SHA256
|
||||
when 'SHA384' then Digest::SHA384
|
||||
when 'SHA512' then Digest::SHA512
|
||||
when 'RMD160' then Digest::RMD160
|
||||
else raise Error, "unknown algorithm \"#{$1}\""
|
||||
end
|
||||
algstr = parameters["algorithm"]
|
||||
sess = $2
|
||||
else
|
||||
algorithm = Digest::MD5
|
||||
algstr = "MD5"
|
||||
sess = false
|
||||
end
|
||||
|
||||
a1 = if sess then
|
||||
[
|
||||
algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"),
|
||||
parameters['nonce'],
|
||||
@cnonce
|
||||
].join ':'
|
||||
else
|
||||
"#{digest_user}:#{parameters['realm']}:#{digest_password}"
|
||||
end
|
||||
|
||||
ha1 = algorithm.hexdigest(a1)
|
||||
ha2 = algorithm.hexdigest("#{method}:#{path}")
|
||||
|
||||
request_digest = [ha1, parameters['nonce']]
|
||||
request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop
|
||||
request_digest << ha2
|
||||
request_digest = request_digest.join ':'
|
||||
|
||||
# Same order as IE7
|
||||
auth = [
|
||||
"Digest username=\"#{digest_user}\"",
|
||||
"realm=\"#{parameters['realm']}\"",
|
||||
"nonce=\"#{parameters['nonce']}\"",
|
||||
"uri=\"#{path}\"",
|
||||
"cnonce=\"#{@cnonce}\"",
|
||||
"nc=#{'%08x' % @nonce_count}",
|
||||
"algorithm=#{algstr}",
|
||||
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
|
||||
# The spec says the qop value shouldn't be enclosed in quotes, but
|
||||
# some versions of IIS require it and Apache accepts it. Chrome
|
||||
# and Firefox both send it without quotes but IE does it this way.
|
||||
# Use the non-compliant-but-everybody-does-it to be as compatible
|
||||
# as possible by default. The user can override if they don't like
|
||||
# it.
|
||||
if qop.nil? then
|
||||
elsif iis then
|
||||
"qop=\"#{qop}\""
|
||||
else
|
||||
"qop=#{qop}"
|
||||
end,
|
||||
if parameters.key? 'opaque' then
|
||||
"opaque=\"#{parameters['opaque']}\""
|
||||
end
|
||||
].compact
|
||||
|
||||
headers ={ 'Authorization' => auth.join(', ') }
|
||||
headers.merge!(opts['headers']) if opts['headers']
|
||||
|
||||
|
||||
# Send main request with authentication
|
||||
r = c.request_cgi(opts.merge({
|
||||
'uri' => path,
|
||||
'method' => method,
|
||||
'headers' => headers }))
|
||||
resp = c.send_recv(r, to)
|
||||
unless resp.kind_of? Rex::Proto::Http::Response
|
||||
return [nil,nil]
|
||||
end
|
||||
|
||||
return [resp,c]
|
||||
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||
end
|
||||
return if not datastore['USERNAME']
|
||||
datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '')
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -722,4 +501,4 @@ protected
|
|||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -94,3 +94,4 @@ require 'msf/core/exploit/winrm'
|
|||
|
||||
# WebApp
|
||||
require 'msf/core/exploit/web'
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ module Exploit::Remote::WinRM
|
|||
c = connect(opts)
|
||||
to = opts[:timeout] || timeout
|
||||
ctype = "application/soap+xml;charset=UTF-8"
|
||||
resp, c = send_request_cgi(opts.merge({
|
||||
resp, c = send_winrm_request(opts.merge({
|
||||
'uri' => opts['uri'],
|
||||
'method' => 'POST',
|
||||
'ctype' => ctype,
|
||||
|
@ -61,7 +61,7 @@ module Exploit::Remote::WinRM
|
|||
end
|
||||
|
||||
def winrm_run_cmd(cmd, timeout=20)
|
||||
resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
|
||||
resp = send_winrm_request(winrm_open_shell_msg,timeout)
|
||||
if resp.nil?
|
||||
print_error "Recieved no reply from server"
|
||||
return nil
|
||||
|
@ -76,17 +76,17 @@ module Exploit::Remote::WinRM
|
|||
return retval
|
||||
end
|
||||
shell_id = winrm_get_shell_id(resp)
|
||||
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
|
||||
resp = send_winrm_request(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)
|
||||
resp = send_winrm_request(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))
|
||||
resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
|
||||
resp = send_winrm_request(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)
|
||||
resp = send_winrm_request(winrm_open_shell_msg,timeout)
|
||||
if resp.nil?
|
||||
print_error "Recieved no reply from server"
|
||||
return nil
|
||||
|
@ -101,9 +101,9 @@ module Exploit::Remote::WinRM
|
|||
return retval
|
||||
end
|
||||
shell_id = winrm_get_shell_id(resp)
|
||||
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
|
||||
resp = send_winrm_request(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)
|
||||
resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
|
||||
streams = winrm_get_cmd_streams(resp)
|
||||
return streams
|
||||
end
|
||||
|
@ -219,98 +219,6 @@ module Exploit::Remote::WinRM
|
|||
::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']
|
||||
|
@ -329,6 +237,18 @@ module Exploit::Remote::WinRM
|
|||
return "/root/cimv2/"
|
||||
end
|
||||
|
||||
def send_winrm_request(data, timeout=20)
|
||||
opts = {
|
||||
'uri' => datastore['URI'],
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'username' => datastore['USERNAME'],
|
||||
'password' => datastore['PASSWORD'],
|
||||
'ctype' => "application/soap+xml;charset=UTF-8"
|
||||
}
|
||||
send_request_cgi(opts,timeout)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
require 'rex/socket'
|
||||
require 'rex/proto/http'
|
||||
require 'rex/text'
|
||||
require 'digest'
|
||||
require 'rex/proto/ntlm/crypt'
|
||||
require 'rex/proto/ntlm/constants'
|
||||
require 'rex/proto/ntlm/utils'
|
||||
require 'rex/proto/ntlm/exceptions'
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
|
@ -21,13 +26,15 @@ class Client
|
|||
#
|
||||
# Creates a new client instance
|
||||
#
|
||||
def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil)
|
||||
def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '')
|
||||
self.hostname = host
|
||||
self.port = port.to_i
|
||||
self.context = context
|
||||
self.ssl = ssl
|
||||
self.ssl_version = ssl_version
|
||||
self.proxies = proxies
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.config = {
|
||||
'read_max_data' => (1024*1024*1),
|
||||
'vhost' => self.hostname,
|
||||
|
@ -61,7 +68,21 @@ class Client
|
|||
'uri_fake_end' => false, # bool
|
||||
'uri_fake_params_start' => false, # bool
|
||||
'header_folding' => false, # bool
|
||||
'chunked_size' => 0 # integer
|
||||
'chunked_size' => 0, # integer
|
||||
#
|
||||
# NTLM Options
|
||||
#
|
||||
'usentlm2_session' => true,
|
||||
'use_ntlmv2' => true,
|
||||
'send_lm' => true,
|
||||
'send_ntlm' => true,
|
||||
'SendSPN' => true,
|
||||
'UseLMKey' => false,
|
||||
'domain' => 'WORKSTATION',
|
||||
#
|
||||
# Digest Options
|
||||
#
|
||||
'DigestAuthIIS' => true
|
||||
}
|
||||
|
||||
# This is not used right now...
|
||||
|
@ -130,27 +151,44 @@ class Client
|
|||
#
|
||||
# Create an arbitrary HTTP request
|
||||
#
|
||||
# @param opts [Hash]
|
||||
# @option opts 'agent' [String] User-Agent header value
|
||||
# @option opts 'basic_auth' [String] Basic-Auth header value
|
||||
# @option opts 'connection' [String] Connection header value
|
||||
# @option opts 'cookie' [String] Cookie header value
|
||||
# @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616)
|
||||
# @option opts 'encode' [Bool] URI encode the supplied URI, default: false
|
||||
# @option opts 'headers' [Hash] HTTP headers, e.g. <code>{ "X-MyHeader" => "value" }</code>
|
||||
# @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
|
||||
# @option opts 'proto' [String] protocol, default: HTTP
|
||||
# @option opts 'query' [String] raw query string
|
||||
# @option opts 'raw_headers' [Hash] HTTP headers
|
||||
# @option opts 'uri' [String] the URI to request
|
||||
# @option opts 'version' [String] version of the protocol, default: 1.1
|
||||
# @option opts 'vhost' [String] Host header value
|
||||
#
|
||||
# @return [Request]
|
||||
def request_raw(opts={})
|
||||
c_enc = opts['encode'] || false
|
||||
c_uri = opts['uri'] || '/'
|
||||
c_ag = opts['agent'] || config['agent']
|
||||
c_auth = opts['basic_auth'] || config['basic_auth'] || ''
|
||||
c_body = opts['data'] || ''
|
||||
c_conn = opts['connection']
|
||||
c_cook = opts['cookie'] || config['cookie']
|
||||
c_enc = opts['encode'] || false
|
||||
c_head = opts['headers'] || config['headers'] || {}
|
||||
c_host = opts['vhost'] || config['vhost'] || self.hostname
|
||||
c_meth = opts['method'] || 'GET'
|
||||
c_prot = opts['proto'] || 'HTTP'
|
||||
c_vers = opts['version'] || config['version'] || '1.1'
|
||||
c_qs = opts['query']
|
||||
c_ag = opts['agent'] || config['agent']
|
||||
c_cook = opts['cookie'] || config['cookie']
|
||||
c_host = opts['vhost'] || config['vhost'] || self.hostname
|
||||
c_head = opts['headers'] || config['headers'] || {}
|
||||
c_rawh = opts['raw_headers']|| config['raw_headers'] || ''
|
||||
c_conn = opts['connection']
|
||||
c_auth = opts['basic_auth'] || config['basic_auth'] || ''
|
||||
c_uri = opts['uri'] || '/'
|
||||
c_vers = opts['version'] || config['version'] || '1.1'
|
||||
|
||||
# An agent parameter was specified, but so was a header, prefer the header
|
||||
if c_ag and c_head.keys.map{|x| x.downcase }.include?('user-agent')
|
||||
c_ag = nil
|
||||
end
|
||||
|
||||
|
||||
uri = set_uri(c_uri)
|
||||
|
||||
req = ''
|
||||
|
@ -170,7 +208,6 @@ class Client
|
|||
req << set_host_header(c_host)
|
||||
req << set_agent_header(c_ag)
|
||||
|
||||
|
||||
if (c_auth.length > 0)
|
||||
req << set_basic_auth_header(c_auth)
|
||||
end
|
||||
|
@ -181,53 +218,45 @@ class Client
|
|||
req << set_raw_headers(c_rawh)
|
||||
req << set_body(c_body)
|
||||
|
||||
req
|
||||
request = Request.new
|
||||
request.parse(req)
|
||||
request.options = opts
|
||||
|
||||
request
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Create a CGI compatible request
|
||||
#
|
||||
# Options:
|
||||
# - agent: User-Agent header value
|
||||
# - basic_auth: Basic-Auth header value
|
||||
# - connection: Connection header value
|
||||
# - cookie: Cookie header value
|
||||
# - ctype: Content-Type header value, default: +application/x-www-form-urlencoded+
|
||||
# - data: HTTP data (only useful with some methods, see rfc2616)
|
||||
# - encode: URI encode the supplied URI, default: false
|
||||
# - encode_params: URI encode the GET or POST variables (names and values), default: true
|
||||
# - headers: HTTP headers as a hash, e.g. <code>{ "X-MyHeader" => "value" }</code>
|
||||
# - method: HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
|
||||
# - proto: protocol, default: HTTP
|
||||
# - query: raw query string
|
||||
# - raw_headers: HTTP headers as a hash
|
||||
# - uri: the URI to request
|
||||
# - vars_get: GET variables as a hash to be translated into a query string
|
||||
# - vars_post: POST variables as a hash to be translated into POST data
|
||||
# - version: version of the protocol, default: 1.1
|
||||
# - vhost: Host header value
|
||||
# @param (see #request_raw)
|
||||
# @option opts (see #request_raw)
|
||||
# @option opts 'ctype' [String] Content-Type header value, default: +application/x-www-form-urlencoded+
|
||||
# @option opts 'encode_params' [Bool] URI encode the GET or POST variables (names and values), default: true
|
||||
# @option opts 'vars_get' [Hash] GET variables as a hash to be translated into a query string
|
||||
# @option opts 'vars_post' [Hash] POST variables as a hash to be translated into POST data
|
||||
#
|
||||
# @return [Request]
|
||||
def request_cgi(opts={})
|
||||
c_ag = opts['agent'] || config['agent']
|
||||
c_body = opts['data'] || ''
|
||||
c_cgi = opts['uri'] || '/'
|
||||
c_conn = opts['connection']
|
||||
c_cook = opts['cookie'] || config['cookie']
|
||||
c_enc = opts['encode'] || false
|
||||
c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false)
|
||||
c_cgi = opts['uri'] || '/'
|
||||
c_body = opts['data'] || ''
|
||||
c_meth = opts['method'] || 'GET'
|
||||
c_prot = opts['proto'] || 'HTTP'
|
||||
c_vers = opts['version'] || config['version'] || '1.1'
|
||||
c_qs = opts['query'] || ''
|
||||
c_varg = opts['vars_get'] || {}
|
||||
c_varp = opts['vars_post'] || {}
|
||||
c_head = opts['headers'] || config['headers'] || {}
|
||||
c_host = opts['vhost'] || config['vhost']
|
||||
c_meth = opts['method'] || 'GET'
|
||||
c_path = opts['path_info']
|
||||
c_prot = opts['proto'] || 'HTTP'
|
||||
c_qs = opts['query'] || ''
|
||||
c_rawh = opts['raw_headers'] || config['raw_headers'] || ''
|
||||
c_type = opts['ctype'] || 'application/x-www-form-urlencoded'
|
||||
c_ag = opts['agent'] || config['agent']
|
||||
c_cook = opts['cookie'] || config['cookie']
|
||||
c_host = opts['vhost'] || config['vhost']
|
||||
c_conn = opts['connection']
|
||||
c_path = opts['path_info']
|
||||
c_auth = opts['basic_auth'] || config['basic_auth'] || ''
|
||||
c_varg = opts['vars_get'] || {}
|
||||
c_varp = opts['vars_post'] || {}
|
||||
c_vers = opts['version'] || config['version'] || '1.1'
|
||||
|
||||
uri = set_cgi(c_cgi)
|
||||
qstr = c_qs
|
||||
pstr = c_body
|
||||
|
@ -243,7 +272,7 @@ class Client
|
|||
|
||||
c_varg.each_pair do |var,val|
|
||||
qstr << '&' if qstr.length > 0
|
||||
qstr << (c_enc_p ? set_encode_uri(var) : var)
|
||||
qstr << (c_enc_p ? set_encode_uri(var) : var)
|
||||
qstr << '='
|
||||
qstr << (c_enc_p ? set_encode_uri(val) : val)
|
||||
end
|
||||
|
@ -284,10 +313,6 @@ class Client
|
|||
req << set_host_header(c_host)
|
||||
req << set_agent_header(c_ag)
|
||||
|
||||
if (c_auth.length > 0)
|
||||
req << set_basic_auth_header(c_auth)
|
||||
end
|
||||
|
||||
req << set_cookie_header(c_cook)
|
||||
req << set_connection_header(c_conn)
|
||||
req << set_extra_headers(c_head)
|
||||
|
@ -298,12 +323,19 @@ class Client
|
|||
req << set_raw_headers(c_rawh)
|
||||
req << set_body(pstr)
|
||||
|
||||
req
|
||||
request = Request.new
|
||||
request.parse(req)
|
||||
request.options = opts
|
||||
|
||||
request
|
||||
end
|
||||
|
||||
#
|
||||
# Connects to the remote server if possible.
|
||||
#
|
||||
# @param t [Fixnum] Timeout
|
||||
# @see Rex::Socket::Tcp.create
|
||||
# @return [Rex::Socket::Tcp]
|
||||
def connect(t = -1)
|
||||
# If we already have a connection and we aren't pipelining, close it.
|
||||
if (self.conn)
|
||||
|
@ -342,11 +374,30 @@ class Client
|
|||
end
|
||||
|
||||
#
|
||||
# Transmit an HTTP request and receive the response
|
||||
# If persist is set, then the request will attempt
|
||||
# to reuse an existing connection.
|
||||
# Sends a request and gets a response back
|
||||
#
|
||||
# If the request is a 401, and we have creds, it will attempt to complete
|
||||
# authentication and return the final response
|
||||
#
|
||||
def send_recv(req, t = -1, persist=false)
|
||||
res = _send_recv(req,t,persist)
|
||||
if res and res.code == 401 and res.headers['WWW-Authenticate'] and have_creds?
|
||||
res = send_auth(res, req.options, t, persist)
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
#
|
||||
# Transmit an HTTP request and receive the response
|
||||
#
|
||||
# If persist is set, then the request will attempt to reuse an existing
|
||||
# connection.
|
||||
#
|
||||
# Call this directly instead of {#send_recv} if you don't want automatic
|
||||
# authentication handling.
|
||||
#
|
||||
# @return [Response]
|
||||
def _send_recv(req, t = -1, persist=false)
|
||||
@pipeline = persist
|
||||
send_request(req, t)
|
||||
res = read_response(t)
|
||||
|
@ -357,11 +408,332 @@ class Client
|
|||
#
|
||||
# Send an HTTP request to the server
|
||||
#
|
||||
# @param req [Request,#to_s] The request to send
|
||||
# @param t (see #connect)
|
||||
def send_request(req, t = -1)
|
||||
connect(t)
|
||||
conn.put(req.to_s)
|
||||
end
|
||||
|
||||
# Validates that the client has creds
|
||||
def have_creds?
|
||||
!(self.username.nil?) && self.username != ''
|
||||
end
|
||||
|
||||
#
|
||||
# Params -
|
||||
# res = The 401 response we need to auth from
|
||||
# opts = the opts used to generate the request that created this response
|
||||
# t = the timeout for the http requests
|
||||
# persist = whether to persist the tcp connection for HTTP Pipelining
|
||||
#
|
||||
# Parses the response for what Authentication methods are supported.
|
||||
# Sets the corect authorization options and passes them on to the correct
|
||||
# method for sending the next request.
|
||||
def send_auth(res, opts, t, persist)
|
||||
supported_auths = res.headers['WWW-Authenticate']
|
||||
if supported_auths.include? 'Basic'
|
||||
if opts['headers']
|
||||
opts['headers']['Authorization'] = basic_auth_header(self.username,self.password)
|
||||
else
|
||||
opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)}
|
||||
end
|
||||
|
||||
req = request_cgi(opts)
|
||||
res = _send_recv(req,t,persist)
|
||||
return res
|
||||
elsif supported_auths.include? "Digest"
|
||||
opts['DigestAuthUser'] = self.username.to_s
|
||||
opts['DigestAuthPassword'] = self.password.to_s
|
||||
temp_response = digest_auth(opts)
|
||||
if temp_response.kind_of? Rex::Proto::Http::Response
|
||||
res = temp_response
|
||||
end
|
||||
return res
|
||||
elsif supported_auths.include? "NTLM"
|
||||
opts['provider'] = 'NTLM'
|
||||
temp_response = negotiate_auth(opts)
|
||||
if temp_response.kind_of? Rex::Proto::Http::Response
|
||||
res = temp_response
|
||||
end
|
||||
return res
|
||||
elsif supported_auths.include? "Negotiate"
|
||||
opts['provider'] = 'Negotiate'
|
||||
temp_response = negotiate_auth(opts)
|
||||
if temp_response.kind_of? Rex::Proto::Http::Response
|
||||
res = temp_response
|
||||
end
|
||||
return res
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
# Converts username and password into the HTTP Basic
|
||||
# authorization string.
|
||||
def basic_auth_header(username,password)
|
||||
auth_str = username.to_s + ":" + password.to_s
|
||||
auth_str = "Basic " + Rex::Text.encode_base64(auth_str)
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Opts -
|
||||
# Inherits all the same options as send_request_cgi
|
||||
# Also expects some specific opts
|
||||
# DigestAuthUser - The username for DigestAuth
|
||||
# DigestAuthPass - The password for DigestAuth
|
||||
# DigestAuthIIS - IIS uses a slighlty different implementation, set this for IIS support
|
||||
#
|
||||
# This method builds new request to complete a Digest Authentication cycle.
|
||||
# We do not persist the original connection , to clear state in preparation for our auth
|
||||
# We do persist the rest of the connection stream because Digest is a tcp session
|
||||
# based authentication method.
|
||||
#
|
||||
|
||||
def digest_auth(opts={})
|
||||
@nonce_count = 0
|
||||
|
||||
to = opts['timeout'] || 20
|
||||
|
||||
digest_user = opts['DigestAuthUser'] || ""
|
||||
digest_password = opts['DigestAuthPassword'] || ""
|
||||
|
||||
method = opts['method']
|
||||
path = opts['uri']
|
||||
iis = true
|
||||
if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS'] == false)
|
||||
iis = false
|
||||
end
|
||||
|
||||
begin
|
||||
@nonce_count += 1
|
||||
|
||||
resp = opts['response']
|
||||
|
||||
if not resp
|
||||
# Get authentication-challenge from server, and read out parameters required
|
||||
r = request_cgi(opts.merge({
|
||||
'uri' => path,
|
||||
'method' => method }))
|
||||
resp = _send_recv(r, to)
|
||||
unless resp.kind_of? Rex::Proto::Http::Response
|
||||
return nil
|
||||
end
|
||||
|
||||
if resp.code != 401
|
||||
return resp
|
||||
end
|
||||
return resp unless resp.headers['WWW-Authenticate']
|
||||
end
|
||||
|
||||
# Don't anchor this regex to the beginning of string because header
|
||||
# folding makes it appear later when the server presents multiple
|
||||
# WWW-Authentication options (such as is the case with IIS configured
|
||||
# for Digest or NTLM).
|
||||
resp['www-authenticate'] =~ /Digest (.*)/
|
||||
|
||||
parameters = {}
|
||||
$1.split(/,[[:space:]]*/).each do |p|
|
||||
k, v = p.split("=", 2)
|
||||
parameters[k] = v.gsub('"', '')
|
||||
end
|
||||
|
||||
qop = parameters['qop']
|
||||
|
||||
if parameters['algorithm'] =~ /(.*?)(-sess)?$/
|
||||
algorithm = case $1
|
||||
when 'MD5' then Digest::MD5
|
||||
when 'SHA1' then Digest::SHA1
|
||||
when 'SHA2' then Digest::SHA2
|
||||
when 'SHA256' then Digest::SHA256
|
||||
when 'SHA384' then Digest::SHA384
|
||||
when 'SHA512' then Digest::SHA512
|
||||
when 'RMD160' then Digest::RMD160
|
||||
else raise Error, "unknown algorithm \"#{$1}\""
|
||||
end
|
||||
algstr = parameters["algorithm"]
|
||||
sess = $2
|
||||
else
|
||||
algorithm = Digest::MD5
|
||||
algstr = "MD5"
|
||||
sess = false
|
||||
end
|
||||
|
||||
a1 = if sess then
|
||||
[
|
||||
algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"),
|
||||
parameters['nonce'],
|
||||
@cnonce
|
||||
].join ':'
|
||||
else
|
||||
"#{digest_user}:#{parameters['realm']}:#{digest_password}"
|
||||
end
|
||||
|
||||
ha1 = algorithm.hexdigest(a1)
|
||||
ha2 = algorithm.hexdigest("#{method}:#{path}")
|
||||
|
||||
request_digest = [ha1, parameters['nonce']]
|
||||
request_digest.push(('%08x' % @nonce_count), @cnonce, qop) if qop
|
||||
request_digest << ha2
|
||||
request_digest = request_digest.join ':'
|
||||
|
||||
# Same order as IE7
|
||||
auth = [
|
||||
"Digest username=\"#{digest_user}\"",
|
||||
"realm=\"#{parameters['realm']}\"",
|
||||
"nonce=\"#{parameters['nonce']}\"",
|
||||
"uri=\"#{path}\"",
|
||||
"cnonce=\"#{@cnonce}\"",
|
||||
"nc=#{'%08x' % @nonce_count}",
|
||||
"algorithm=#{algstr}",
|
||||
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
|
||||
# The spec says the qop value shouldn't be enclosed in quotes, but
|
||||
# some versions of IIS require it and Apache accepts it. Chrome
|
||||
# and Firefox both send it without quotes but IE does it this way.
|
||||
# Use the non-compliant-but-everybody-does-it to be as compatible
|
||||
# as possible by default. The user can override if they don't like
|
||||
# it.
|
||||
if qop.nil? then
|
||||
elsif iis then
|
||||
"qop=\"#{qop}\""
|
||||
else
|
||||
"qop=#{qop}"
|
||||
end,
|
||||
if parameters.key? 'opaque' then
|
||||
"opaque=\"#{parameters['opaque']}\""
|
||||
end
|
||||
].compact
|
||||
|
||||
headers ={ 'Authorization' => auth.join(', ') }
|
||||
headers.merge!(opts['headers']) if opts['headers']
|
||||
|
||||
# Send main request with authentication
|
||||
r = request_cgi(opts.merge({
|
||||
'uri' => path,
|
||||
'method' => method,
|
||||
'headers' => headers }))
|
||||
resp = _send_recv(r, to, true)
|
||||
unless resp.kind_of? Rex::Proto::Http::Response
|
||||
return nil
|
||||
end
|
||||
|
||||
return resp
|
||||
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Opts -
|
||||
# Inherits all the same options as send_request_cgi
|
||||
# provider - What Negotiate Provider to use (supports NTLM and Negotiate)
|
||||
#
|
||||
# Builds a series of requests to complete Negotiate Auth. Works essentially
|
||||
# the same way as Digest auth. Same pipelining concerns exist.
|
||||
#
|
||||
|
||||
def negotiate_auth(opts={})
|
||||
ntlm_options = {
|
||||
:signing => false,
|
||||
:usentlm2_session => self.config['usentlm2_session'],
|
||||
:use_ntlmv2 => self.config['use_ntlmv2'],
|
||||
:send_lm => self.config['send_lm'],
|
||||
:send_ntlm => self.config['send_ntlm']
|
||||
}
|
||||
|
||||
to = opts['timeout'] || 20
|
||||
opts['username'] ||= self.username.to_s
|
||||
opts['password'] ||= self.password.to_s
|
||||
|
||||
if opts['provider'] and opts['provider'].include? 'Negotiate'
|
||||
provider = "Negotiate "
|
||||
else
|
||||
provider = 'NTLM '
|
||||
end
|
||||
|
||||
opts['method']||= 'GET'
|
||||
opts['headers']||= {}
|
||||
|
||||
ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options)
|
||||
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
|
||||
domain_name = self.config['domain']
|
||||
|
||||
b64_blob = Rex::Text::encode_base64(
|
||||
::Rex::Proto::NTLM::Utils::make_ntlmssp_blob_init(
|
||||
domain_name,
|
||||
workstation_name,
|
||||
ntlmssp_flags
|
||||
))
|
||||
|
||||
ntlm_message_1 = provider + b64_blob
|
||||
|
||||
begin
|
||||
# First request to get the challenge
|
||||
opts['headers']['Authorization'] = ntlm_message_1
|
||||
r = request_cgi(opts)
|
||||
resp = _send_recv(r, to)
|
||||
unless resp.kind_of? Rex::Proto::Http::Response
|
||||
return nil
|
||||
end
|
||||
|
||||
return resp unless resp.code == 401 && resp.headers['WWW-Authenticate']
|
||||
|
||||
# Get the challenge and craft the response
|
||||
ntlm_challenge = resp.headers['WWW-Authenticate'].scan(/#{provider}([A-Z0-9\x2b\x2f=]+)/i).flatten[0]
|
||||
return resp unless ntlm_challenge
|
||||
|
||||
ntlm_message_2 = Rex::Text::decode_base64(ntlm_challenge)
|
||||
blob_data = ::Rex::Proto::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
|
||||
default_name = blob_data[:default_name] || '' #netbios name
|
||||
default_domain = blob_data[:default_domain] || '' #netbios domain
|
||||
dns_host_name = blob_data[:dns_host_name] || '' #dns name
|
||||
dns_domain_name = blob_data[:dns_domain_name] || '' #dns domain
|
||||
chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' #Client time
|
||||
|
||||
spnopt = {:use_spn => self.config['SendSPN'], :name => self.hostname}
|
||||
|
||||
resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = ::Rex::Proto::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 = ::Rex::Proto::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
|
||||
opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}"
|
||||
r = request_cgi(opts)
|
||||
resp = _send_recv(r, to, true)
|
||||
unless resp.kind_of? Rex::Proto::Http::Response
|
||||
return nil
|
||||
end
|
||||
return resp
|
||||
|
||||
rescue ::Errno::EPIPE, ::Timeout::Error
|
||||
return nil
|
||||
end
|
||||
end
|
||||
#
|
||||
# Read a response from the server
|
||||
#
|
||||
|
@ -839,6 +1211,9 @@ class Client
|
|||
#
|
||||
attr_accessor :proxies
|
||||
|
||||
# Auth
|
||||
attr_accessor :username, :password
|
||||
|
||||
|
||||
# When parsing the request, thunk off the first response from the server, since junk
|
||||
attr_accessor :junk_pipeline
|
||||
|
|
|
@ -48,6 +48,8 @@ class Request < Packet
|
|||
end
|
||||
end
|
||||
|
||||
attr_accessor :options
|
||||
|
||||
#
|
||||
# Initializes an instance of an HTTP request with the supplied method, URI,
|
||||
# and protocol.
|
||||
|
|
|
@ -38,10 +38,10 @@ class Metasploit4 < Msf::Auxiliary
|
|||
))
|
||||
|
||||
# disabling all the unnecessary options that someone might set to break our query
|
||||
deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN',
|
||||
deregister_options('RPORT','RHOST', 'DOMAIN',
|
||||
'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM',
|
||||
'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session',
|
||||
'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL')
|
||||
'NTLM::UseNTLMv2', 'SSL')
|
||||
|
||||
register_options(
|
||||
[
|
||||
|
|
|
@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'Name' => 'Cisco Device HTTP Device Manager Access',
|
||||
'Description' => %q{
|
||||
This module gathers data from a Cisco device (router or switch) with the device manager
|
||||
web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify
|
||||
web interface exposed. The USERNAME and PASSWORD options can be used to specify
|
||||
authentication.
|
||||
},
|
||||
'Author' => [ 'hdm' ],
|
||||
|
@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
print_good("#{rhost}:#{rport} Successfully authenticated to this device")
|
||||
|
||||
# Report a vulnerability only if no password was specified
|
||||
if datastore['BasicAuthPass'].to_s.length == 0
|
||||
if datastore['PASSWORD'].to_s.length == 0
|
||||
|
||||
report_vuln(
|
||||
{
|
||||
|
|
|
@ -26,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
[
|
||||
|
||||
],
|
||||
'Author' => [ 'hdm' ],
|
||||
'Author' => [ 'hdm' , 'thelightcosine'],
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '1999-0502'] # Weak password
|
||||
|
@ -48,9 +48,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ])
|
||||
end
|
||||
|
||||
def find_auth_uri_and_scheme
|
||||
|
||||
path_and_scheme = []
|
||||
def find_auth_uri
|
||||
if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0
|
||||
paths = [datastore['AUTH_URI']]
|
||||
else
|
||||
|
@ -80,21 +78,9 @@ class Metasploit3 < Msf::Auxiliary
|
|||
next if not res
|
||||
end
|
||||
|
||||
next if not res.code == 401
|
||||
next if not res.headers['WWW-Authenticate']
|
||||
path_and_scheme << path
|
||||
case res.headers['WWW-Authenticate']
|
||||
when /Basic/i
|
||||
path_and_scheme << "Basic"
|
||||
when /NTLM/i
|
||||
path_and_scheme << "NTLM"
|
||||
when /Digest/i
|
||||
path_and_scheme << "Digest"
|
||||
end
|
||||
return path_and_scheme
|
||||
return path
|
||||
end
|
||||
|
||||
return path_and_scheme
|
||||
end
|
||||
|
||||
def target_url
|
||||
|
@ -111,7 +97,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
print_error("You need need to set AUTH_URI when using PUT Method !")
|
||||
return
|
||||
end
|
||||
@uri, @scheme = find_auth_uri_and_scheme()
|
||||
@uri = find_auth_uri()
|
||||
if ! @uri
|
||||
print_error("#{target_url} No URI found that asks for HTTP authentication")
|
||||
return
|
||||
|
@ -119,12 +105,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
@uri = "/#{@uri}" if @uri[0,1] != "/"
|
||||
|
||||
if ! @scheme
|
||||
print_error("#{target_url} Incompatible authentication scheme")
|
||||
return
|
||||
end
|
||||
|
||||
print_status("Attempting to login to #{target_url} with #{@scheme} authentication")
|
||||
print_status("Attempting to login to #{target_url}")
|
||||
|
||||
each_user_pass { |user, pass|
|
||||
do_login(user, pass)
|
||||
|
@ -133,27 +114,23 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def do_login(user='admin', pass='admin')
|
||||
vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'")
|
||||
success = false
|
||||
proof = ""
|
||||
|
||||
ret = do_http_login(user,pass,@scheme)
|
||||
return :abort if ret == :abort
|
||||
if ret == :success
|
||||
proof = @proof.dup
|
||||
success = true
|
||||
end
|
||||
response = do_http_login(user,pass)
|
||||
result = determine_result(response)
|
||||
|
||||
if success
|
||||
return :abort if result == :abort
|
||||
|
||||
if result == :success
|
||||
print_good("#{target_url} - Successful login '#{user}' : '#{pass}'")
|
||||
|
||||
any_user = false
|
||||
any_pass = false
|
||||
|
||||
vprint_status("#{target_url} - Trying random username with password:'#{pass}'")
|
||||
any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme)
|
||||
any_user = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass))
|
||||
|
||||
vprint_status("#{target_url} - Trying username:'#{user}' with random password")
|
||||
any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme)
|
||||
any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8)))
|
||||
|
||||
if any_user == :success
|
||||
user = "anyuser"
|
||||
|
@ -175,7 +152,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
:sname => (ssl ? 'https' : 'http'),
|
||||
:user => user,
|
||||
:pass => pass,
|
||||
:proof => "WEBAPP=\"Generic\", PROOF=#{proof}",
|
||||
:proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}",
|
||||
:source_type => "user_supplied",
|
||||
:active => true
|
||||
)
|
||||
|
@ -188,143 +165,28 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
def do_http_login(user,pass,scheme)
|
||||
case scheme
|
||||
when /NTLM/i
|
||||
do_http_auth_ntlm(user,pass)
|
||||
when /Digest/i
|
||||
do_http_auth_digest(user,pass,datastore['REQUESTTYPE'])
|
||||
when /Basic/i
|
||||
do_http_auth_basic(user,pass)
|
||||
else
|
||||
vprint_error("#{target_url}: Unknown authentication scheme")
|
||||
return :abort
|
||||
end
|
||||
end
|
||||
|
||||
def do_http_auth_ntlm(user,pass)
|
||||
def do_http_login(user,pass)
|
||||
begin
|
||||
resp,c = send_http_auth_ntlm(
|
||||
response = send_request_cgi({
|
||||
'uri' => @uri,
|
||||
'method' => datastore['REQUESTTYPE'],
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
)
|
||||
c.close
|
||||
return :abort if (resp.code == 404)
|
||||
|
||||
if [200, 301, 302].include?(resp.code)
|
||||
@proof = resp
|
||||
return :success
|
||||
end
|
||||
|
||||
})
|
||||
return response
|
||||
rescue ::Rex::ConnectionError
|
||||
vprint_error("#{target_url} - Failed to connect to the web server")
|
||||
return :abort
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def determine_result(response)
|
||||
return :abort unless response.kind_of? Rex::Proto::Http::Response
|
||||
return :abort unless response.code
|
||||
return :success if [200, 301, 302].include?(response.code)
|
||||
return :fail
|
||||
end
|
||||
|
||||
def do_http_auth_basic(user,pass)
|
||||
user_pass = Rex::Text.encode_base64(user + ":" + pass)
|
||||
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'uri' => @uri,
|
||||
'method' => 'GET',
|
||||
'headers' =>
|
||||
{
|
||||
'Authorization' => "Basic #{user_pass}",
|
||||
}
|
||||
}, 25)
|
||||
|
||||
unless (res.kind_of? Rex::Proto::Http::Response)
|
||||
vprint_error("#{target_url} not responding")
|
||||
return :abort
|
||||
end
|
||||
|
||||
return :abort if (res.code == 404)
|
||||
|
||||
if [200, 301, 302].include?(res.code)
|
||||
@proof = res
|
||||
return :success
|
||||
end
|
||||
|
||||
rescue ::Rex::ConnectionError
|
||||
vprint_error("#{target_url} - Failed to connect to the web server")
|
||||
return :abort
|
||||
end
|
||||
|
||||
return :fail
|
||||
end
|
||||
|
||||
def do_http_auth_digest(user,pass,requesttype)
|
||||
path = datastore['AUTH_URI'] || "/"
|
||||
begin
|
||||
if requesttype == "PUT"
|
||||
res,c = send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => requesttype,
|
||||
'data' => 'Test123\r\n',
|
||||
#'DigestAuthIIS' => false,
|
||||
'DigestAuthUser' => user,
|
||||
'DigestAuthPassword' => pass
|
||||
}, 25)
|
||||
elsif requesttype == "PROPFIND"
|
||||
res,c = send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => requesttype,
|
||||
'data' => '<?xml version="1.0" encoding="utf-8"?><D:propfind xmlns:D="DAV:"><D:allprop/></D:propfind>',
|
||||
#'DigestAuthIIS' => false,
|
||||
'DigestAuthUser' => user,
|
||||
'DigestAuthPassword' => pass,
|
||||
'headers' => { 'Depth' => '0'}
|
||||
}, 25)
|
||||
else
|
||||
res,c = send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => requesttype,
|
||||
#'DigestAuthIIS' => false,
|
||||
'DigestAuthUser' => user,
|
||||
'DigestAuthPassword' => pass
|
||||
}, 25)
|
||||
end
|
||||
|
||||
unless (res.kind_of? Rex::Proto::Http::Response)
|
||||
vprint_error("#{target_url} not responding")
|
||||
return :abort
|
||||
end
|
||||
|
||||
return :abort if (res.code == 404)
|
||||
|
||||
if ( [200, 301, 302].include?(res.code) ) or (res.code == 201)
|
||||
if ((res.code == 201) and (requesttype == "PUT"))
|
||||
print_good("Trying to delete #{path}")
|
||||
del_res,c = send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => 'DELETE',
|
||||
'DigestAuthUser' => user,
|
||||
'DigestAuthPassword' => pass
|
||||
}, 25)
|
||||
if not (del_res.code == 204)
|
||||
print_error("#{path} could be created, but not deleted again. This may have been noisy ...")
|
||||
end
|
||||
end
|
||||
@proof = res
|
||||
return :success
|
||||
end
|
||||
|
||||
if (res.code == 207) and (requesttype == "PROPFIND")
|
||||
@proof = res
|
||||
return :success
|
||||
end
|
||||
|
||||
rescue ::Rex::ConnectionError
|
||||
vprint_error("#{target_url} - Failed to connect to the web server")
|
||||
return :abort
|
||||
end
|
||||
|
||||
return :fail
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -40,10 +40,6 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
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
|
||||
streams = winrm_run_cmd(datastore['CMD'])
|
||||
return unless streams.class == Hash
|
||||
print_error streams['stderr'] unless streams['stderr'] == ''
|
||||
|
|
|
@ -39,12 +39,8 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
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)
|
||||
resp = send_winrm_request(test_request)
|
||||
if resp.nil?
|
||||
print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out"
|
||||
return
|
||||
|
|
|
@ -42,12 +42,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
|
||||
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
|
||||
|
||||
resp,c = send_request_ntlm(winrm_wql_msg(datastore['WQL']))
|
||||
resp = send_winrm_request(winrm_wql_msg(datastore['WQL']))
|
||||
if resp.nil?
|
||||
print_error "Got no reply from the server"
|
||||
return
|
||||
|
|
|
@ -84,8 +84,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol'])
|
||||
], self.class)
|
||||
|
||||
deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword',
|
||||
'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
|
||||
deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
|
||||
'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2')
|
||||
end
|
||||
|
||||
|
|
|
@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'piranha']),
|
||||
OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'q']),
|
||||
OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'piranha']),
|
||||
OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'q']),
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
if res.code == 401
|
||||
print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!")
|
||||
print_error("401 Authorization Required! Our credentials were not accepted!")
|
||||
elsif (res.code == 200 and res.body =~ /The passwords you supplied match/)
|
||||
print_status("Command successfully executed (according to the server).")
|
||||
end
|
||||
|
|
|
@ -227,9 +227,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
authmsg = res.headers['WWW-Authenticate']
|
||||
end
|
||||
print_error("The remote server responded expecting authentication")
|
||||
if datastore['BasicAuthUser'] and datastore['BasicAuthPass']
|
||||
print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser'])
|
||||
elsif authmsg
|
||||
if authmsg
|
||||
print_error("WWW-Authenticate: %s" % authmsg)
|
||||
end
|
||||
cleanup_instructions(rpath, name) # display cleanup info
|
||||
|
|
|
@ -96,9 +96,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
|
||||
def exploit
|
||||
datastore['BasicAuthUser'] = datastore['USERNAME']
|
||||
datastore['BasicAuthPass'] = datastore['PASSWORD']
|
||||
|
||||
jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8))
|
||||
app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8))
|
||||
|
||||
|
|
|
@ -123,9 +123,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
|
||||
def exploit
|
||||
datastore['BasicAuthUser'] = datastore['USERNAME']
|
||||
datastore['BasicAuthPass'] = datastore['PASSWORD']
|
||||
|
||||
jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8))
|
||||
app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8))
|
||||
|
||||
|
|
|
@ -112,9 +112,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
def check
|
||||
datastore['BasicAuthUser'] = datastore['USERNAME']
|
||||
datastore['BasicAuthPass'] = datastore['PASSWORD']
|
||||
|
||||
res = query_serverinfo
|
||||
disconnect
|
||||
return CheckCode::Unknown if res.nil?
|
||||
|
@ -127,8 +124,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => (ssl ? "https" : "http"),
|
||||
:user => datastore['BasicAuthUser'],
|
||||
:pass => datastore['BasicAuthPass'],
|
||||
:user => datastore['USERNAME'],
|
||||
:pass => datastore['PASSWORD'],
|
||||
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
|
||||
:active => true
|
||||
)
|
||||
|
@ -164,9 +161,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
|
||||
def exploit
|
||||
datastore['BasicAuthUser'] = datastore['USERNAME']
|
||||
datastore['BasicAuthPass'] = datastore['PASSWORD']
|
||||
|
||||
mytarget = target
|
||||
if (target.name =~ /Automatic/)
|
||||
mytarget = auto_target
|
||||
|
@ -221,8 +215,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => (ssl ? "https" : "http"),
|
||||
:user => datastore['BasicAuthUser'],
|
||||
:pass => datastore['BasicAuthPass'],
|
||||
:user => datastore['USERNAME'],
|
||||
:pass => datastore['PASSWORD'],
|
||||
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
|
||||
:active => true
|
||||
)
|
||||
|
|
|
@ -67,9 +67,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
|
||||
def go(command)
|
||||
datastore['BasicAuthUser'] = datastore['USERNAME']
|
||||
datastore['BasicAuthPass'] = datastore['PASSWORD']
|
||||
|
||||
xml = <<-EOS
|
||||
<?xml version="1.0"?>
|
||||
<methodCall>
|
||||
|
|
|
@ -72,8 +72,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
register_options(
|
||||
[
|
||||
Opt::RPORT(8080),
|
||||
OptString.new('BasicAuthUser', [true, 'The HTTP username to specify for basic authentication', 'anonymous']),
|
||||
OptString.new('BasicAuthPass', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']),
|
||||
OptString.new('USERNAME', [true, 'The HTTP username to specify for basic authentication', 'anonymous']),
|
||||
OptString.new('PASSWORD', [true, 'The HTTP password to specify for basic authentication', 'mozilla@example.com']),
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
|
|
@ -36,8 +36,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
[
|
||||
OptString.new('PATH', [ true, "The path to attempt to upload", '/webdav/']),
|
||||
OptString.new('FILENAME', [ false , "The filename to give the payload. (Leave Blank for Random)"]),
|
||||
OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']),
|
||||
OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp'])
|
||||
OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']),
|
||||
OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -46,12 +46,10 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
def exploit
|
||||
uri = build_path
|
||||
print_status "Uploading Payload to #{uri}"
|
||||
res,c = send_digest_request_cgi({
|
||||
res = send_request_cgi({
|
||||
'uri' => uri,
|
||||
'method' => 'PUT',
|
||||
'data' => payload.raw,
|
||||
'DigestAuthUser' => datastore['RUSER'],
|
||||
'DigestAuthPassword' => datastore['RPASS']
|
||||
'data' => payload.raw
|
||||
}, 25)
|
||||
unless (res and res.code == 201)
|
||||
print_error "Failed to upload file!"
|
||||
|
|
|
@ -66,20 +66,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
@compat_mode = false
|
||||
end
|
||||
|
||||
def check
|
||||
unless accepts_ntlm_auth
|
||||
print_error "The Remote WinRM server does not appear to allow Negotiate (NTLM) auth"
|
||||
return Msf::Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
return Msf::Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
|
||||
def exploit
|
||||
unless check == Msf::Exploit::CheckCode::Vulnerable
|
||||
return
|
||||
end
|
||||
|
||||
unless valid_login?
|
||||
print_error "Login Failure. Recheck your credentials"
|
||||
return
|
||||
|
@ -141,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
def temp_dir
|
||||
print_status "Grabbing %TEMP%"
|
||||
resp,c = send_request_ntlm(winrm_open_shell_msg)
|
||||
resp = send_winrm_request(winrm_open_shell_msg)
|
||||
if resp.nil?
|
||||
print_error "Got no reply from the server"
|
||||
return nil
|
||||
|
@ -152,16 +140,16 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
end
|
||||
shell_id = winrm_get_shell_id(resp)
|
||||
cmd = "echo %TEMP%"
|
||||
resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id))
|
||||
resp= send_winrm_request(winrm_cmd_msg(cmd, shell_id))
|
||||
cmd_id = winrm_get_cmd_id(resp)
|
||||
resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id))
|
||||
resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id))
|
||||
streams = winrm_get_cmd_streams(resp)
|
||||
return streams['stdout'].chomp
|
||||
end
|
||||
|
||||
def check_remote_arch
|
||||
wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"}
|
||||
resp,c = send_request_ntlm(winrm_wql_msg(wql))
|
||||
resp = send_winrm_request(winrm_wql_msg(wql))
|
||||
#Default to x86 if we can't be sure
|
||||
return "x86" if resp.nil? or resp.code != 200
|
||||
resp_tbl = parse_wql_response(resp)
|
||||
|
@ -247,7 +235,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
def valid_login?
|
||||
data = winrm_wql_msg("Select Name,Status from Win32_Service")
|
||||
resp,c = send_request_ntlm(data)
|
||||
resp = send_winrm_request(data)
|
||||
unless resp.code == 200
|
||||
return false
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue