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::HTTP
bug/bundler_fix
David Maloney 2013-01-30 12:42:46 -06:00
parent 8e870f3654
commit 5814c59620
4 changed files with 29 additions and 268 deletions

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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,