Skip to content
Browse files

Merge branch 'bug/unmerge-1444-and-1476' of https://github.com/todb-r…

…7/metasploit-framework into todb-r7-bug/unmerge-1444-and-1476
  • Loading branch information...
2 parents 44d984d + 8ddc19e commit e298866fdd28b7545710caeaa4d7cfbc320597fb jvazquez-r7 committed
View
4 lib/anemone/rex_http.rb
@@ -188,9 +188,7 @@ def connection(url)
context,
url.scheme == "https",
'SSLv23',
- @opts[:proxies],
- @opts[:username],
- @opts[:password]
+ @opts[:proxies]
)
conn.set_config(
View
16 lib/msf/core/auxiliary/crawler.rb
@@ -22,9 +22,7 @@ def initialize(info = {})
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]),
- OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication']),
- OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication'])
+ OptInt.new('MAX_THREADS', [ true, 'The maximum number of concurrent requests', 4])
], self.class
)
@@ -36,6 +34,8 @@ def initialize(info = {})
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,9 +118,8 @@ def run
:info => ""
})
- if datastore['USERNAME'] and datastore['USERNAME'] != ''
- t[:username] = datastore['USERNAME'].to_s
- t[:password] = datastore['PASSWORD'].to_s
+ if datastore['BasicAuthUser']
+ t[:http_basic_auth] = [ "#{datastore['BasicAuthUser']}:#{datastore['BasicAuthPass']}" ].pack("m*").gsub(/\s+/, '')
end
if datastore['HTTPCookie']
@@ -279,8 +278,9 @@ def crawler_options(t)
opts[:cookies] = t[:cookies]
end
- opts[:username] = t[:username] || ''
- opts[:password] =t[:password] || ''
+ if t[:http_basic_auth]
+ opts[:http_basic_auth] = t[:http_basic_auth]
+ end
opts
end
View
14 lib/msf/core/auxiliary/web/http.rb
@@ -10,7 +10,6 @@
module Msf
class Auxiliary::Web::HTTP
-
class Request
attr_accessor :url
attr_reader :opts
@@ -70,7 +69,6 @@ def timed_out
attr_reader :framework
attr_accessor :redirect_limit
- attr_accessor :username , :password
def initialize( opts = {} )
@opts = opts.dup
@@ -86,8 +84,8 @@ def initialize( opts = {} )
@request_opts = {}
if opts[:auth].is_a? Hash
- @username = opts[:auth][:user].to_s
- @password = opts[:auth][:password].to_s
+ @request_opts['basic_auth'] = [ opts[:auth][:user].to_s + ':' +
+ opts[:auth][:password] ]. pack( 'm*' ).gsub( /\s+/, '' )
end
self.redirect_limit = opts[:redirect_limit] || 20
@@ -107,9 +105,7 @@ def connect
opts[:target].port,
{},
opts[:target].ssl,
- 'SSLv23',
- username,
- password
+ 'SSLv23'
)
c.set_config({
@@ -300,10 +296,6 @@ def _request( url, opts = {} )
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
View
265 lib/msf/core/exploit/http/client.rb
@@ -37,9 +37,7 @@ def initialize(info = {})
Opt::RHOST,
Opt::RPORT(80),
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
- Opt::Proxies,
- OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']),
- OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']),
+ Opt::Proxies
], self.class
)
@@ -48,6 +46,10 @@ def initialize(info = {})
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']]),
@@ -154,9 +156,7 @@ def connect(opts={})
},
dossl,
ssl_version,
- proxies,
- datastore['USERNAME'],
- datastore['PASSWORD']
+ proxies
)
# Configure the HTTP client with the supplied parameter
@@ -184,15 +184,7 @@ def connect(opts={})
'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'],
- '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']
+ 'header_folding' => datastore['HTTP::header_folding']
)
# If this connection is global, persist it
@@ -274,10 +266,6 @@ def send_request_raw(opts={}, timeout = 20)
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
@@ -289,8 +277,241 @@ def send_request_cgi(opts={}, timeout = 20)
# Combine the user/pass into an auth string for the HTTP Client
#
def basic_auth
- return if not datastore['USERNAME']
- datastore['USERNAME'].to_s + ":" + (datastore['PASSWORD'].to_s || '')
+ 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
end
##
@@ -501,4 +722,4 @@ def make_cnonce
end
-end
+end
View
1 lib/msf/core/exploit/mixins.rb
@@ -94,4 +94,3 @@
# WebApp
require 'msf/core/exploit/web'
-
View
122 lib/msf/core/exploit/winrm.rb
@@ -42,7 +42,7 @@ def winrm_poke(timeout = 20)
c = connect(opts)
to = opts[:timeout] || timeout
ctype = "application/soap+xml;charset=UTF-8"
- resp, c = send_winrm_request(opts.merge({
+ resp, c = send_request_cgi(opts.merge({
'uri' => opts['uri'],
'method' => 'POST',
'ctype' => ctype,
@@ -61,7 +61,7 @@ def parse_auth_methods(resp)
end
def winrm_run_cmd(cmd, timeout=20)
- resp = send_winrm_request(winrm_open_shell_msg,timeout)
+ resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
if resp.nil?
print_error "Recieved no reply from server"
return nil
@@ -76,17 +76,17 @@ def winrm_run_cmd(cmd, timeout=20)
return retval
end
shell_id = winrm_get_shell_id(resp)
- resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout)
+ resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
cmd_id = winrm_get_cmd_id(resp)
- resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
+ resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
streams = winrm_get_cmd_streams(resp)
- resp = send_winrm_request(winrm_terminate_cmd_msg(shell_id,cmd_id),timeout)
- resp = send_winrm_request(winrm_delete_shell_msg(shell_id))
+ 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 = send_winrm_request(winrm_open_shell_msg,timeout)
+ resp,c = send_request_ntlm(winrm_open_shell_msg,timeout)
if resp.nil?
print_error "Recieved no reply from server"
return nil
@@ -101,9 +101,9 @@ def winrm_run_cmd_hanging(cmd, timeout=20)
return retval
end
shell_id = winrm_get_shell_id(resp)
- resp = send_winrm_request(winrm_cmd_msg(cmd, shell_id),timeout)
+ resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id),timeout)
cmd_id = winrm_get_cmd_id(resp)
- resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
+ resp,c = send_request_ntlm(winrm_cmd_recv_msg(shell_id,cmd_id),timeout)
streams = winrm_get_cmd_streams(resp)
return streams
end
@@ -219,6 +219,98 @@ 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']
@@ -237,18 +329,6 @@ def wmi_namespace
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
View
488 lib/rex/proto/http/client.rb
@@ -2,11 +2,6 @@
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
@@ -26,15 +21,13 @@ class Client
#
# Creates a new client instance
#
- def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '')
+ def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil)
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,
@@ -68,21 +61,7 @@ def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, prox
'uri_fake_end' => false, # bool
'uri_fake_params_start' => false, # bool
'header_folding' => false, # bool
- '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
+ 'chunked_size' => 0 # integer
}
# This is not used right now...
@@ -151,44 +130,27 @@ def set_config(opts = {})
#
# 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_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_uri = 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_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_uri = opts['uri'] || '/'
- c_vers = opts['version'] || config['version'] || '1.1'
+ c_conn = opts['connection']
+ c_auth = opts['basic_auth'] || config['basic_auth'] || ''
# 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 = ''
@@ -208,10 +170,9 @@ def request_raw(opts={})
req << set_host_header(c_host)
req << set_agent_header(c_ag)
+
if (c_auth.length > 0)
- unless c_head['Authorization'] and c_head['Authorization'].include? "Basic"
- req << set_basic_auth_header(c_auth)
- end
+ req << set_basic_auth_header(c_auth)
end
req << set_cookie_header(c_cook)
@@ -220,46 +181,53 @@ def request_raw(opts={})
req << set_raw_headers(c_rawh)
req << set_body(c_body)
- request = Request.new
- request.parse(req)
- request.options = opts
-
- request
+ req
end
#
# Create a CGI compatible request
#
- # @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
+ # 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
#
- # @return [Request]
def request_cgi(opts={})
- c_ag = opts['agent'] || config['agent']
- c_auth = opts['basic_auth'] || config['basic_auth'] || ''
- 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_head = opts['headers'] || config['headers'] || {}
- c_host = opts['vhost'] || config['vhost']
+ c_cgi = opts['uri'] || '/'
+ c_body = opts['data'] || ''
c_meth = opts['method'] || 'GET'
- c_path = opts['path_info']
c_prot = opts['proto'] || 'HTTP'
+ c_vers = opts['version'] || config['version'] || '1.1'
c_qs = opts['query'] || ''
- c_rawh = opts['raw_headers'] || config['raw_headers'] || ''
- c_type = opts['ctype'] || 'application/x-www-form-urlencoded'
c_varg = opts['vars_get'] || {}
c_varp = opts['vars_post'] || {}
- c_vers = opts['version'] || config['version'] || '1.1'
-
+ c_head = opts['headers'] || config['headers'] || {}
+ 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'] || ''
uri = set_cgi(c_cgi)
qstr = c_qs
pstr = c_body
@@ -275,7 +243,7 @@ def request_cgi(opts={})
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
@@ -317,9 +285,7 @@ def request_cgi(opts={})
req << set_agent_header(c_ag)
if (c_auth.length > 0)
- unless c_head['Authorization'] and c_head['Authorization'].include? "Basic"
- req << set_basic_auth_header(c_auth)
- end
+ req << set_basic_auth_header(c_auth)
end
req << set_cookie_header(c_cook)
@@ -332,19 +298,12 @@ def request_cgi(opts={})
req << set_raw_headers(c_rawh)
req << set_body(pstr)
- request = Request.new
- request.parse(req)
- request.options = opts
-
- request
+ req
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)
@@ -383,30 +342,11 @@ def close
end
#
- # 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.
#
- # 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)
+ def send_recv(req, t = -1, persist=false)
@pipeline = persist
send_request(req, t)
res = read_response(t)
@@ -417,332 +357,11 @@ def _send_recv(req, t = -1, persist=false)
#
# 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
#
@@ -1220,9 +839,6 @@ def set_formatted_header(var, val)
#
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
View
2 lib/rex/proto/http/request.rb
@@ -48,8 +48,6 @@ def initialize(uri = '/', proto = DefaultProtocol)
end
end
- attr_accessor :options
-
#
# Initializes an instance of an HTTP request with the supplied method, URI,
# and protocol.
View
4 modules/auxiliary/gather/shodan_search.rb
@@ -38,10 +38,10 @@ def initialize(info = {})
))
# disabling all the unnecessary options that someone might set to break our query
- deregister_options('RPORT','RHOST', 'DOMAIN',
+ deregister_options('RPORT','RHOST', 'BasicAuthPass', 'BasicAuthUser', 'DOMAIN',
'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM',
'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session',
- 'NTLM::UseNTLMv2', 'SSL')
+ 'NTLM::UseNTLMv2', 'DigestAuthPassword', 'DigestAuthUser', 'SSL')
register_options(
[
View
4 modules/auxiliary/scanner/http/cisco_device_manager.rb
@@ -26,7 +26,7 @@ def initialize(info={})
'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 USERNAME and PASSWORD options can be used to specify
+ web interface exposed. The BasicAuthUser and BasicAuthPass options can be used to specify
authentication.
},
'Author' => [ 'hdm' ],
@@ -61,7 +61,7 @@ def run_host(ip)
print_good("#{rhost}:#{rport} Successfully authenticated to this device")
# Report a vulnerability only if no password was specified
- if datastore['PASSWORD'].to_s.length == 0
+ if datastore['BasicAuthPass'].to_s.length == 0
report_vuln(
{
View
186 modules/auxiliary/scanner/http/http_login.rb
@@ -26,7 +26,7 @@ def initialize
[
],
- 'Author' => [ 'hdm' , 'thelightcosine'],
+ 'Author' => [ 'hdm' ],
'References' =>
[
[ 'CVE', '1999-0502'] # Weak password
@@ -48,7 +48,9 @@ def initialize
register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ])
end
- def find_auth_uri
+ def find_auth_uri_and_scheme
+
+ path_and_scheme = []
if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0
paths = [datastore['AUTH_URI']]
else
@@ -78,9 +80,21 @@ def find_auth_uri
next if not res
end
- return path
+ 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
end
+ return path_and_scheme
end
def target_url
@@ -97,7 +111,7 @@ def run_host(ip)
print_error("You need need to set AUTH_URI when using PUT Method !")
return
end
- @uri = find_auth_uri()
+ @uri, @scheme = find_auth_uri_and_scheme()
if ! @uri
print_error("#{target_url} No URI found that asks for HTTP authentication")
return
@@ -105,7 +119,12 @@ def run_host(ip)
@uri = "/#{@uri}" if @uri[0,1] != "/"
- print_status("Attempting to login to #{target_url}")
+ if ! @scheme
+ print_error("#{target_url} Incompatible authentication scheme")
+ return
+ end
+
+ print_status("Attempting to login to #{target_url} with #{@scheme} authentication")
each_user_pass { |user, pass|
do_login(user, pass)
@@ -114,23 +133,27 @@ def run_host(ip)
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)
-
- return :abort if result == :abort
-
- if result == :success
+ if 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 = determine_result(do_http_login(Rex::Text.rand_text_alpha(8), pass))
+ any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme)
vprint_status("#{target_url} - Trying username:'#{user}' with random password")
- any_pass = determine_result(do_http_login(user, Rex::Text.rand_text_alpha(8)))
+ any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme)
if any_user == :success
user = "anyuser"
@@ -152,7 +175,7 @@ def do_login(user='admin', pass='admin')
:sname => (ssl ? 'https' : 'http'),
:user => user,
:pass => pass,
- :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}",
+ :proof => "WEBAPP=\"Generic\", PROOF=#{proof}",
:source_type => "user_supplied",
:active => true
)
@@ -165,28 +188,143 @@ def do_login(user='admin', pass='admin')
end
end
- def do_http_login(user,pass)
+ 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)
begin
- response = send_request_cgi({
+ resp,c = send_http_auth_ntlm(
'uri' => @uri,
- 'method' => datastore['REQUESTTYPE'],
'username' => user,
'password' => pass
- })
- return response
+ )
+ c.close
+ return :abort if (resp.code == 404)
+
+ if [200, 301, 302].include?(resp.code)
+ @proof = resp
+ return :success
+ end
+
rescue ::Rex::ConnectionError
vprint_error("#{target_url} - Failed to connect to the web server")
- return nil
+ return :abort
end
+
+ return :fail
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)
+ 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
View
10 modules/auxiliary/scanner/http/tomcat_mgr_login.rb
@@ -87,6 +87,10 @@ def run_host(ip)
vprint_error("http://#{rhost}:#{rport}#{uri} - No response")
return
end
+ if res.code != 401
+ vprint_error("http://#{rhost}:#{rport} - Authorization not requested")
+ return
+ end
each_user_pass { |user, pass|
do_login(user, pass)
@@ -103,8 +107,10 @@ def do_login(user='tomcat', pass='tomcat')
res = send_request_cgi({
'uri' => uri,
'method' => 'GET',
- 'username' => user,
- 'password' => pass
+ 'headers' =>
+ {
+ 'Authorization' => "Basic #{user_pass}",
+ }
}, 25)
unless (res.kind_of? Rex::Proto::Http::Response)
vprint_error("http://#{rhost}:#{rport}#{uri} not responding")
View
4 modules/auxiliary/scanner/winrm/winrm_cmd.rb
@@ -40,6 +40,10 @@ def initialize
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'] == ''
View
6 modules/auxiliary/scanner/winrm/winrm_login.rb
@@ -39,8 +39,12 @@ module without SSL, the 'AllowUnencrypted' winrm option must be set.
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 = send_winrm_request(test_request)
+ resp,c = send_request_ntlm(test_request)
if resp.nil?
print_error "#{ip}:#{rport}: Got no reply from the server, connection may have timed out"
return
View
7 modules/auxiliary/scanner/winrm/winrm_wql.rb
@@ -42,7 +42,12 @@ def initialize
def run_host(ip)
- resp = send_winrm_request(winrm_wql_msg(datastore['WQL']))
+ 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']))
if resp.nil?
print_error "Got no reply from the server"
return
View
3 modules/auxiliary/server/http_ntlmrelay.rb
@@ -84,7 +84,8 @@ def initialize(info = {})
'IPC$,ADMIN$,C$,D$,CCMLOGS$,ccmsetup$,share,netlogon,sysvol'])
], self.class)
- deregister_options('DOMAIN', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
+ deregister_options('BasicAuthPass', 'BasicAuthUser', 'DOMAIN', 'DigestAuthPassword',
+ 'DigestAuthUser', 'NTLM::SendLM', 'NTLM::SendSPN', 'NTLM::SendNTLM', 'NTLM::UseLMKey',
'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2')
end
View
6 modules/exploits/linux/http/piranha_passwd_exec.rb
@@ -72,8 +72,8 @@ def initialize(info = {})
register_options(
[
- 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']),
+ 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']),
], self.class)
end
@@ -96,7 +96,7 @@ def exploit
end
if res.code == 401
- print_error("401 Authorization Required! Our credentials were not accepted!")
+ print_error("401 Authorization Required! Our BasicAuthUser and BasicAuthPass credentials not accepted!")
elsif (res.code == 200 and res.body =~ /The passwords you supplied match/)
print_status("Command successfully executed (according to the server).")
end
View
4 modules/exploits/multi/http/axis2_deployer.rb
@@ -227,7 +227,9 @@ def upload_exec(session,rpath)
authmsg = res.headers['WWW-Authenticate']
end
print_error("The remote server responded expecting authentication")
- if authmsg
+ if datastore['BasicAuthUser'] and datastore['BasicAuthPass']
+ print_error("BasicAuthUser \"%s\" failed to authenticate" % datastore['BasicAuthUser'])
+ elsif authmsg
print_error("WWW-Authenticate: %s" % authmsg)
end
cleanup_instructions(rpath, name) # display cleanup info
View
3 modules/exploits/multi/http/jboss_bshdeployer.rb
@@ -96,6 +96,9 @@ def initialize(info = {})
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))
View
3 modules/exploits/multi/http/jboss_maindeployer.rb
@@ -123,6 +123,9 @@ def auto_target
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))
View
14 modules/exploits/multi/http/tomcat_mgr_deploy.rb
@@ -112,6 +112,9 @@ def initialize(info = {})
end
def check
+ datastore['BasicAuthUser'] = datastore['USERNAME']
+ datastore['BasicAuthPass'] = datastore['PASSWORD']
+
res = query_serverinfo
disconnect
return CheckCode::Unknown if res.nil?
@@ -124,8 +127,8 @@ def check
:host => rhost,
:port => rport,
:sname => (ssl ? "https" : "http"),
- :user => datastore['USERNAME'],
- :pass => datastore['PASSWORD'],
+ :user => datastore['BasicAuthUser'],
+ :pass => datastore['BasicAuthPass'],
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
:active => true
)
@@ -161,6 +164,9 @@ def auto_target
def exploit
+ datastore['BasicAuthUser'] = datastore['USERNAME']
+ datastore['BasicAuthPass'] = datastore['PASSWORD']
+
mytarget = target
if (target.name =~ /Automatic/)
mytarget = auto_target
@@ -215,8 +221,8 @@ def exploit
:host => rhost,
:port => rport,
:sname => (ssl ? "https" : "http"),
- :user => datastore['USERNAME'],
- :pass => datastore['PASSWORD'],
+ :user => datastore['BasicAuthUser'],
+ :pass => datastore['BasicAuthPass'],
:proof => "WEBAPP=\"Tomcat Manager App\", VHOST=#{vhost}, PATH=#{datastore['PATH']}",
:active => true
)
View
3 modules/exploits/unix/webapp/oracle_vm_agent_utl.rb
@@ -67,6 +67,9 @@ def initialize(info = {})
end
def go(command)
+ datastore['BasicAuthUser'] = datastore['USERNAME']
+ datastore['BasicAuthPass'] = datastore['PASSWORD']
+
xml = <<-EOS
<?xml version="1.0"?>
<methodCall>
View
4 modules/exploits/windows/http/easyftp_list.rb
@@ -72,8 +72,8 @@ def initialize(info = {})
register_options(
[
Opt::RPORT(8080),
- 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']),
+ 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']),
], self.class)
end
View
10 modules/exploits/windows/http/xampp_webdav_upload_php.rb
@@ -36,8 +36,8 @@ def initialize
[
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('USERNAME', [false, 'The HTTP username to specify for authentication', 'wampp']),
- OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', 'xampp'])
+ OptString.new('RUSER', [ true, "The Username to use for Authentication", 'wampp']),
+ OptString.new('RPASS', [ true, "The Password to use for Authentication", 'xampp'])
], self.class)
end
@@ -46,10 +46,12 @@ def initialize
def exploit
uri = build_path
print_status "Uploading Payload to #{uri}"
- res = send_request_cgi({
+ res,c = send_digest_request_cgi({
'uri' => uri,
'method' => 'PUT',
- 'data' => payload.raw
+ 'data' => payload.raw,
+ 'DigestAuthUser' => datastore['RUSER'],
+ 'DigestAuthPassword' => datastore['RPASS']
}, 25)
unless (res and res.code == 201)
print_error "Failed to upload file!"
View
24 modules/exploits/windows/winrm/winrm_script_exec.rb
@@ -66,8 +66,20 @@ def initialize(info = {})
@compat_mode = false
end
- def exploit
+ 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
@@ -129,7 +141,7 @@ def encoded_psh(script)
def temp_dir
print_status "Grabbing %TEMP%"
- resp = send_winrm_request(winrm_open_shell_msg)
+ resp,c = send_request_ntlm(winrm_open_shell_msg)
if resp.nil?
print_error "Got no reply from the server"
return nil
@@ -140,16 +152,16 @@ def temp_dir
end
shell_id = winrm_get_shell_id(resp)
cmd = "echo %TEMP%"
- resp= send_winrm_request(winrm_cmd_msg(cmd, shell_id))
+ resp,c = send_request_ntlm(winrm_cmd_msg(cmd, shell_id))
cmd_id = winrm_get_cmd_id(resp)
- resp = send_winrm_request(winrm_cmd_recv_msg(shell_id,cmd_id))
+ resp,c = send_request_ntlm(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 = send_winrm_request(winrm_wql_msg(wql))
+ resp,c = send_request_ntlm(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)
@@ -235,7 +247,7 @@ def powershell2?
def valid_login?
data = winrm_wql_msg("Select Name,Status from Win32_Service")
- resp = send_winrm_request(data)
+ resp,c = send_request_ntlm(data)
unless resp.code == 200
return false
end

0 comments on commit e298866

Please sign in to comment.
Something went wrong with that request. Please try again.