move httpauth to mixin
HttpAuth stuff gets it's own little mixin mix it in to Exploit::Http::Client mix in it to Auxiliary::Web::HTTPbug/bundler_fix
parent
8e870f3654
commit
5814c59620
|
@ -10,6 +10,8 @@ require 'uri'
|
|||
module Msf
|
||||
class Auxiliary::Web::HTTP
|
||||
|
||||
include Exploit::Remote::HttpAuth
|
||||
|
||||
class Request
|
||||
attr_accessor :url
|
||||
attr_reader :opts
|
||||
|
@ -147,6 +149,23 @@ class Auxiliary::Web::HTTP
|
|||
while rlimit >= 0
|
||||
rlimit -= 1
|
||||
res = _request( url, opts )
|
||||
if res.code == 401 and res.headers['WWW-Authenticate'] and opts['username']
|
||||
if res.headers['WWW-Authenticate'].include? 'Basic'
|
||||
opts['password']||= ''
|
||||
opts['basic_auth'] = opts['username'] + ":" + opts['password']
|
||||
res = _request( url, opts )
|
||||
elsif res.headers['WWW-Authenticate'].include? 'Digest'
|
||||
opts['DigestAuthUser'] = opts['username']
|
||||
opts['DigestAuthPassword'] = opts['password']
|
||||
res = send_digest_request_cgi(opts,timeout)
|
||||
elsif res.headers['WWW-Authenticate'].include? "Negotiate"
|
||||
opts['provider'] = 'Negotiate'
|
||||
res = send_request_auth_negotiate(opts,timeout)
|
||||
elsif res.headers['WWW-Authenticate'].include? "NTLM"
|
||||
opts['provider'] = 'NTLM'
|
||||
res = send_request_auth_negotiate(opts,timeout)
|
||||
end
|
||||
end
|
||||
return res if !opts[:follow_redirect] || !url = res.headers['location']
|
||||
end
|
||||
nil
|
||||
|
|
|
@ -16,6 +16,7 @@ module Msf
|
|||
module Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::Report
|
||||
include Exploit::Remote::NTLM::Client
|
||||
include Exploit::Remote::HttpAuth
|
||||
|
||||
#
|
||||
# Constants
|
||||
|
@ -273,151 +274,6 @@ module Exploit::Remote::HttpClient
|
|||
end
|
||||
end
|
||||
|
||||
#
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
#
|
||||
# Authenticates to the remote host based on the most appropriate authentication method,
|
||||
# and returns the HTTP response. If there are multiple auth methods supported, then it
|
||||
|
@ -435,6 +291,7 @@ module Exploit::Remote::HttpClient
|
|||
return res unless res.headers['WWW-Authenticate']
|
||||
|
||||
if res.headers['WWW-Authenticate'].include? "Basic"
|
||||
opts['password']||= ''
|
||||
opts['basic_auth'] = opts['username'] + ":" + opts['password']
|
||||
res = send_request_cgi(opts,timeout)
|
||||
return res
|
||||
|
@ -442,7 +299,7 @@ module Exploit::Remote::HttpClient
|
|||
elsif res.headers['WWW-Authenticate'].include? "Digest"
|
||||
opts['DigestAuthUser'] = opts['username']
|
||||
opts['DigestAuthPassword'] = opts['password']
|
||||
res, c = send_digest_request_cgi(opts,timeout)
|
||||
res = send_digest_request_cgi(opts,timeout)
|
||||
return res
|
||||
|
||||
elsif res.headers['WWW-Authenticate'].include? "Negotiate"
|
||||
|
@ -461,122 +318,6 @@ module Exploit::Remote::HttpClient
|
|||
end
|
||||
|
||||
|
||||
#
|
||||
# Handles both generic Negotiate and NTLM providers
|
||||
# This does not send back the client, instead it expects that you will
|
||||
# handshake for each request sent. While this does create additional
|
||||
# overhead on the wire, it makes dealing with the requests much easier
|
||||
# from a code point of view.
|
||||
#
|
||||
# Options:
|
||||
# - method: HTTP method, default: GET
|
||||
# - headers: HTTP headers as a hash
|
||||
# - provider: HTTP authentication provider, default: 'NTLM '
|
||||
# - username: The username to authenticate as
|
||||
# - password: The password to authenticate with
|
||||
#
|
||||
def send_request_auth_negotiate(opts ={}, timeout =20)
|
||||
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']
|
||||
}
|
||||
|
||||
if opts['provider'] and opts['provider'].include? 'Negotiate'
|
||||
provider = "Negotiate "
|
||||
else
|
||||
provider = 'NTLM '
|
||||
end
|
||||
|
||||
opts['method']||= 'GET'
|
||||
opts['headers']||= {}
|
||||
|
||||
ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)
|
||||
workstation_name = Rex::Text.rand_text_alpha(rand(8)+1)
|
||||
domain_name = datastore['DOMAIN']
|
||||
|
||||
b64_blob = Rex::Text::encode_base64(
|
||||
NTLM_UTILS::make_ntlmssp_blob_init(
|
||||
domain_name,
|
||||
workstation_name,
|
||||
ntlmssp_flags
|
||||
))
|
||||
|
||||
ntlm_message_1 = provider + b64_blob
|
||||
to = opts['timeout'] || timeout
|
||||
|
||||
begin
|
||||
c = connect(opts)
|
||||
|
||||
# First request to get the challenge
|
||||
opts['headers']['Authorization'] = ntlm_message_1
|
||||
r = c.request_cgi(opts)
|
||||
resp = c.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 = 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 => 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
|
||||
opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3}"
|
||||
r = c.request_cgi(opts)
|
||||
resp = c.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
|
||||
|
||||
##
|
||||
#
|
||||
# Wrappers for getters
|
||||
|
|
|
@ -37,6 +37,7 @@ require 'msf/core/exploit/ftp'
|
|||
require 'msf/core/exploit/tftp'
|
||||
require 'msf/core/exploit/telnet'
|
||||
require 'msf/core/exploit/ftpserver'
|
||||
require 'msf/core/exploit/http/auth'
|
||||
require 'msf/core/exploit/http/client'
|
||||
require 'msf/core/exploit/http/server'
|
||||
require 'msf/core/exploit/smtp'
|
||||
|
@ -94,3 +95,4 @@ require 'msf/core/exploit/winrm'
|
|||
|
||||
# WebApp
|
||||
require 'msf/core/exploit/web'
|
||||
|
||||
|
|
|
@ -204,12 +204,11 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def do_http_auth_ntlm(user,pass)
|
||||
begin
|
||||
resp,c = send_http_auth_ntlm(
|
||||
resp = send_request_auth_negotiate(
|
||||
'uri' => @uri,
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
)
|
||||
c.close
|
||||
return :abort if (resp.code == 404)
|
||||
|
||||
if [200, 301, 302].include?(resp.code)
|
||||
|
@ -262,7 +261,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
path = datastore['AUTH_URI'] || "/"
|
||||
begin
|
||||
if requesttype == "PUT"
|
||||
res,c = send_digest_request_cgi({
|
||||
res= send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => requesttype,
|
||||
'data' => 'Test123\r\n',
|
||||
|
@ -271,7 +270,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'DigestAuthPassword' => pass
|
||||
}, 25)
|
||||
elsif requesttype == "PROPFIND"
|
||||
res,c = send_digest_request_cgi({
|
||||
res = 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>',
|
||||
|
@ -281,7 +280,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'headers' => { 'Depth' => '0'}
|
||||
}, 25)
|
||||
else
|
||||
res,c = send_digest_request_cgi({
|
||||
res= send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => requesttype,
|
||||
#'DigestAuthIIS' => false,
|
||||
|
@ -300,7 +299,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
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({
|
||||
del_res = send_digest_request_cgi({
|
||||
'uri' => path,
|
||||
'method' => 'DELETE',
|
||||
'DigestAuthUser' => user,
|
||||
|
|
Loading…
Reference in New Issue