Fixes #2002. Needed to work with some pipelining to get this all to work right, but it seems to function now pretty well -- if the target takes Basic, do basic, if the target takes NTLM, do NTLM. Should implement Digest too, but I don't think hardly anyone uses that.

git-svn-id: file:///home/svn/framework3/trunk@9346 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Tod Beardsley 2010-05-22 17:58:01 +00:00
parent df5ce05fa1
commit 1a2be34a63
3 changed files with 113 additions and 17 deletions

View File

@ -224,6 +224,66 @@ module Exploit::Remote::HttpClient
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="
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
while(resp and resp.code == 100)
resp = c.reread_response(resp, to)
end
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
ntlm_message_2 = Net::NTLM::Message.decode64(ntlm_challenge)
ntlm_message_3 = ntlm_message_2.response(
{
:user => opts['username'],
:password => opts['password']
}, {:ntlmv2 => true}
)
# Send the response
r = c.request_cgi(opts.merge({
'uri' => opts['uri'],
'method' => 'GET',
'headers' => { 'Authorization' => "NTLM #{ntlm_message_3.encode64}"}}))
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
while(resp and resp.code == 100)
resp = c.reread_response(resp, to)
end
return [resp,c]
rescue ::Errno::EPIPE, ::Timeout::Error
end
end
##
#
# Wrappers for getters

View File

@ -302,8 +302,11 @@ class Client
#
# Transmit a HTTP request and receive the response
# If persist is set, then the request will attempt
# to reuse an existing connection.
#
def send_recv(req, t = -1)
def send_recv(req, t = -1, persist=false)
@pipeline = persist
send_request(req)
read_response(t)
end

View File

@ -11,6 +11,7 @@
require 'msf/core'
require 'net/ntlm'
class Metasploit3 < Msf::Auxiliary
@ -50,10 +51,9 @@ class Metasploit3 < Msf::Auxiliary
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
path_and_scheme << datastore['AUTH_URI']
paths = [datastore['AUTH_URI']]
else
paths = %W{
@ -69,9 +69,7 @@ class Metasploit3 < Msf::Auxiliary
res = send_request_cgi({
'uri' => path,
'method' => 'GET',
'headers' => {
'User-Agent' => datastore['UserAgent']
}
'headers' => { }
}, 10)
next if not res
@ -89,10 +87,10 @@ class Metasploit3 < Msf::Auxiliary
next if not res.headers['WWW-Authenticate']
path_and_scheme << path
case res.headers['WWW-Authenticate']
when /NTLMSSP/i
path_and_scheme << nil # "NTLMSSP"
when /Basic/i
path_and_scheme << "Basic"
when /NTLM/i
path_and_scheme << "NTLM"
end
return path_and_scheme
end
@ -101,15 +99,15 @@ class Metasploit3 < Msf::Auxiliary
next if not res.headers['WWW-Authenticate']
path_and_scheme << path
case res.headers['WWW-Authenticate']
when /NTLMSSP/i
path_and_scheme << nil # "NTLMSSP"
when /Basic/i
path_and_scheme << "Basic"
when /NTLM/i
path_and_scheme << "NTLM"
end
return path_and_scheme
end
return nil
return path_and_scheme
end
def target_url
@ -122,7 +120,7 @@ class Metasploit3 < Msf::Auxiliary
def run_host(ip)
@uri = find_auth_uri()[0]
@uri = find_auth_uri_and_scheme()[0]
if ! @uri
print_error("#{target_url} No URI found that asks for HTTP authentication")
return
@ -130,7 +128,7 @@ class Metasploit3 < Msf::Auxiliary
@uri = "/#{@uri}" if @uri[0,1] != "/"
@scheme = find_auth_uri()[1]
@scheme = find_auth_uri_and_scheme()[1]
if ! @scheme
print_error("#{target_url} Incompatible authentication scheme")
return
@ -149,7 +147,7 @@ class Metasploit3 < Msf::Auxiliary
success = false
proof = ""
ret = do_http_login(user,pass)
ret = do_http_login(user,pass,@scheme)
return :abort if ret == :abort
if ret == :success
proof = @proof.dup
@ -162,8 +160,8 @@ class Metasploit3 < Msf::Auxiliary
any_user = false
any_pass = false
any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass)
any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8))
any_user = do_http_login(Rex::Text.rand_text_alpha(8), pass, @scheme)
any_pass = do_http_login(user, Rex::Text.rand_text_alpha(8), @scheme)
if any_user == :success
user = "anyuser"
@ -194,7 +192,42 @@ class Metasploit3 < Msf::Auxiliary
end
end
def do_http_login(user,pass)
def do_http_login(user,pass,scheme)
case scheme
when /Basic/i
do_http_auth_basic(user,pass)
when /NTLM/i
do_http_auth_ntlm(user,pass)
else
vprint_error("#{target_url}: Unknown authentication scheme")
return :abort
end
end
def do_http_auth_ntlm(user,pass)
begin
resp,c = send_http_auth_ntlm(
'uri' => @uri,
'username' => user,
'password' => pass
)
c.close
return :abort if (resp.code == 404)
if resp.code == 200
@proof = resp
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_basic(user,pass)
user_pass = Rex::Text.encode_base64(user + ":" + pass)
begin