Initial overhaul of the HTTP client api. This removes nearly all of the client evasion methods, but leaves the code in a great state to reimplement them with less issues.
git-svn-id: file:///home/svn/framework3/trunk@4222 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
239fe1e8ae
commit
804df25240
|
@ -23,23 +23,34 @@ module Exploit::Remote::HttpClient
|
||||||
Opt::RPORT(80),
|
Opt::RPORT(80),
|
||||||
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
|
OptString.new('VHOST', [ false, "HTTP server virtual host" ]),
|
||||||
Opt::SSL,
|
Opt::SSL,
|
||||||
], Exploit::Remote::HttpClient
|
], self.class
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register_advanced_options(
|
||||||
|
[
|
||||||
|
OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests'])
|
||||||
|
], self.class
|
||||||
|
)
|
||||||
|
|
||||||
register_evasion_options(
|
register_evasion_options(
|
||||||
[
|
[
|
||||||
OptEnum.new('HTTP::uri_encode', [false, 'Enable URI encoding', 'none', ['none','hex-normal', 'hex-all', 'u-normal', 'u-all'], 'none']),
|
OptEnum.new('HTTP::uri_encode', [false, 'Enable URI encoding', 'none', ['none','hex-normal', 'hex-all', 'u-normal', 'u-all'], 'hex-normal'])
|
||||||
OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']),
|
|
||||||
OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', 'false']),
|
#
|
||||||
OptBool.new('HTTP::junk_headers', [false, 'Enable insertion of random junk HTTP headers', 'false']),
|
# Still re-implementing the following options
|
||||||
OptBool.new('HTTP::junk_slashes', [false, 'Enable insertion of random junk HTTP headers', 'false']),
|
#
|
||||||
OptBool.new('HTTP::junk_directories', [false, 'Enable insertion of random junk directories in the URI', 'false']),
|
|
||||||
OptBool.new('HTTP::junk_params', [false, 'Enable insertion of random junk parameters', 'false']),
|
# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']),
|
||||||
OptBool.new('HTTP::junk_self_referring_directories', [false, 'Enable insertion of random self referring directories (eg: /./././)', 'false']),
|
# OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', 'false']),
|
||||||
OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
|
# OptBool.new('HTTP::junk_headers', [false, 'Enable insertion of random junk HTTP headers', 'false']),
|
||||||
OptBool.new('HTTP::fake_uri_end', [false, 'Add a fake end of URI (eg: /%20HTTP/1.0/../../)', 'false']),
|
# OptBool.new('HTTP::junk_slashes', [false, 'Enable insertion of random junk HTTP headers', 'false']),
|
||||||
OptBool.new('HTTP::fake_param_start', [false, 'Add a fake start of params to the URI (eg: /%3fa=b/../)', 'false']),
|
# OptBool.new('HTTP::junk_directories', [false, 'Enable insertion of random junk directories in the URI', 'false']),
|
||||||
], Exploit::Remote::HttpClient
|
# OptBool.new('HTTP::junk_params', [false, 'Enable insertion of random junk parameters', 'false']),
|
||||||
|
# OptBool.new('HTTP::junk_self_referring_directories', [false, 'Enable insertion of random self referring directories (eg: /./././)', 'false']),
|
||||||
|
# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
|
||||||
|
# OptBool.new('HTTP::fake_uri_end', [false, 'Add a fake end of URI (eg: /%20HTTP/1.0/../../)', 'false']),
|
||||||
|
# OptBool.new('HTTP::fake_param_start', [false, 'Add a fake start of params to the URI (eg: /%3fa=b/../)', 'false']),
|
||||||
|
], self.class
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -58,7 +69,11 @@ module Exploit::Remote::HttpClient
|
||||||
)
|
)
|
||||||
|
|
||||||
# Configure the HTTP client with the supplied parameter
|
# Configure the HTTP client with the supplied parameter
|
||||||
nclient.config( { 'vhost' => datastore['VHOST'], }.update(opts) )
|
nclient.set_config(
|
||||||
|
'vhost' => datastore['VHOST'],
|
||||||
|
'uri_encode_mode' => datastore['HTTP::uri_encode'],
|
||||||
|
'agent' => datastore['UserAgent']
|
||||||
|
)
|
||||||
|
|
||||||
# If this connection is global, persist it
|
# If this connection is global, persist it
|
||||||
if (opts['global'])
|
if (opts['global'])
|
||||||
|
@ -120,59 +135,21 @@ module Exploit::Remote::HttpClient
|
||||||
#
|
#
|
||||||
# Connects to the server, creates a request, sends the request, reads the response
|
# Connects to the server, creates a request, sends the request, reads the response
|
||||||
#
|
#
|
||||||
def request(opts={}, timeout = -1)
|
def send_request_raw(opts={}, timeout = -1)
|
||||||
c = connect(opts)
|
c = connect(opts)
|
||||||
|
r = c.request_raw(opts)
|
||||||
if (datastore['HTTP::junk_pipeline'].to_i > 0)
|
c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
|
||||||
c.junk_pipeline = datastore['HTTP::junk_pipeline'].to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
request = c.request(opts)
|
|
||||||
|
|
||||||
if (datastore['HTTP::uri_encode'])
|
|
||||||
request.uri_encode_mode = datastore['HTTP::uri_encode']
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::chunked'] == true)
|
|
||||||
request.auto_cl = false
|
|
||||||
request.transfer_chunked = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::header_folding'] == true)
|
|
||||||
request.headers.fold = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::junk_headers'] == true)
|
|
||||||
request.headers.junk_headers = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::junk_directories'] == true)
|
|
||||||
request.junk_directories = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::junk_slashes'] == true)
|
|
||||||
request.junk_slashes = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::junk_params'] == true)
|
|
||||||
request.junk_params = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::fake_uri_end'] == true)
|
|
||||||
request.junk_end_of_uri = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::fake_param_start'] == true)
|
|
||||||
request.junk_param_start = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if (datastore['HTTP::junk_self_referring_directories'] == true)
|
|
||||||
request.junk_self_referring_directories = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
c.send_request(request, opts[:timeout] ? opts[:timeout] : timeout)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Connects to the server, creates a request, sends the request, reads the response
|
||||||
|
#
|
||||||
|
def send_request_cgi(opts={}, timeout = -1)
|
||||||
|
c = connect(opts)
|
||||||
|
r = c.request_cgi(opts)
|
||||||
|
c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Wrappers for getters
|
# Wrappers for getters
|
||||||
|
|
|
@ -718,19 +718,23 @@ protected
|
||||||
client = Rex::Proto::Http::Client.new(server_host, server_port)
|
client = Rex::Proto::Http::Client.new(server_host, server_port)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|
||||||
|
# Create the CGI parameter list
|
||||||
|
vars = { 'method' => method }
|
||||||
|
|
||||||
|
opts.each_pair do |k, v|
|
||||||
|
vars[k] = xlate_param(v)
|
||||||
|
end
|
||||||
|
|
||||||
# Initialize the request with the POST body.
|
# Initialize the request with the POST body.
|
||||||
request = client.gen_post(server_uri)
|
request = client.request_cgi(
|
||||||
|
'method' => 'POST',
|
||||||
request.body += "method=#{method}"
|
'cgi' => server_uri,
|
||||||
|
'vars_post' => vars
|
||||||
# Enumerate each option filter specified and convert it into a CGI
|
)
|
||||||
# parameter.
|
|
||||||
opts.each_pair { |k, v|
|
|
||||||
request.body += "&#{k}=#{xlate_param(v)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Send the request and grab the response.
|
# Send the request and grab the response.
|
||||||
response = client.send_request(request, 300)
|
response = client.send_recv(request, 300)
|
||||||
|
|
||||||
# Non-200 return code?
|
# Non-200 return code?
|
||||||
if (response.code != 200)
|
if (response.code != 200)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'rex/socket'
|
require 'rex/socket'
|
||||||
require 'rex/proto/http'
|
require 'rex/proto/http'
|
||||||
|
require 'rex/text'
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
module Proto
|
module Proto
|
||||||
|
@ -8,132 +9,148 @@ module Http
|
||||||
###
|
###
|
||||||
#
|
#
|
||||||
# Acts as a client to an HTTP server, sending requests and receiving
|
# Acts as a client to an HTTP server, sending requests and receiving
|
||||||
# responses. This is modeled somewhat after Net::HTTP.
|
# responses.
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
class Client
|
class Client
|
||||||
|
|
||||||
include Proto
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Performs a block-based HTTP operation.
|
# Creates a new client instance
|
||||||
#
|
#
|
||||||
def self.start(host, &block)
|
|
||||||
c = Client.new(host)
|
|
||||||
|
|
||||||
begin
|
|
||||||
block.call(c)
|
|
||||||
ensure
|
|
||||||
c.stop
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Initializes a GET request and returns it to the caller.
|
|
||||||
#
|
|
||||||
def gen_get(uri = '/', proto = DefaultProtocol)
|
|
||||||
return init_request(Request::Get.new(uri, proto))
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Initializes a POST request and returns it to the caller.
|
|
||||||
#
|
|
||||||
def gen_post(uri = '/', proto = DefaultProtocol)
|
|
||||||
return init_request(Request::Post.new(uri, proto))
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(host, port = 80, context = {}, ssl = nil)
|
def initialize(host, port = 80, context = {}, ssl = nil)
|
||||||
self.hostname = host
|
self.hostname = host
|
||||||
self.port = port.to_i
|
self.port = port.to_i
|
||||||
self.context = context
|
self.context = context
|
||||||
self.ssl = ssl
|
self.ssl = ssl
|
||||||
self.request_config = {}
|
self.config = {
|
||||||
self.client_config = {}
|
'read_max_data' => (1024*1024*1),
|
||||||
end
|
'vhost' => self.hostname,
|
||||||
|
'version' => '1.1',
|
||||||
#
|
'agent' => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
|
||||||
# HTTP client.
|
'uri_encode_mode' => 'hex-normal',
|
||||||
#
|
'uri_full_url' => false
|
||||||
def alias
|
|
||||||
"HTTP Client"
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Configures the Client object and the Request factory.
|
|
||||||
#
|
|
||||||
def config(chash)
|
|
||||||
req_opts = %w{ user-agent vhost cookie proto }
|
|
||||||
cli_opts = %w{ max-data }
|
|
||||||
chash.each_pair { |k,v|
|
|
||||||
req_opts.include?(k) ?
|
|
||||||
self.request_config[k] = v : self.client_config[k] = v
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Set parameters for the Request factory.
|
# Set configuration options
|
||||||
#
|
#
|
||||||
def request_option(k, v)
|
def set_config(opts = {})
|
||||||
(v != nil) ? self.request_config[k] = v : self.request_config[k]
|
opts.each_pair do |var,val|
|
||||||
|
config[var]=val
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Set parameters for the actual Client.
|
# Create an arbitrary HTTP request
|
||||||
#
|
#
|
||||||
def client_option(k, v)
|
def request_raw(opts={})
|
||||||
(v != nil) ? self.client_config[k] = v : self.client_config[k]
|
c_enc = opts['encode'] || false
|
||||||
end
|
c_uri = opts['uri'] || '/'
|
||||||
|
c_body = opts['body'] || ''
|
||||||
#
|
c_meth = opts['method'] || 'GET'
|
||||||
# The Request factory.
|
c_vers = opts['version'] || config['version'] || '1.1'
|
||||||
#
|
c_qs = opts['query']
|
||||||
def request(chash)
|
c_ag = opts['agent'] || config['agent']
|
||||||
method = chash['method'] || 'GET'
|
c_cook = opts['cookie'] || config['cookie']
|
||||||
proto = chash['proto'] || self.request_config['proto'] || DefaultProtocol
|
c_host = opts['vhost'] || config['vhost']
|
||||||
uri = chash['uri'] || '/'
|
c_head = opts['headers'] || config['headers'] || {}
|
||||||
|
c_conn = opts['connection']
|
||||||
|
uri = set_uri(c_uri)
|
||||||
|
|
||||||
req = Rex::Proto::Http::Request.new(method, uri, proto)
|
req = ''
|
||||||
|
req += set_method(c_meth)
|
||||||
# pass on the junk_pipeline config
|
req += set_method_uri_spacer()
|
||||||
if self.junk_pipeline
|
req += set_uri_prepend()
|
||||||
req.junk_pipeline = self.junk_pipeline
|
req += (c_enc ? set_encode_uri(uri) : uri)
|
||||||
end
|
|
||||||
|
|
||||||
#
|
if (c_qs)
|
||||||
# Configure the request headers using the Client configuration
|
req += '?'
|
||||||
#
|
req += (c_enc ? set_encode_qs(c_qs) : c_qs)
|
||||||
|
|
||||||
if self.request_config['cookie']
|
|
||||||
req['Cookie'] = self.request_config['cookie']
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
req += set_uri_append()
|
||||||
|
req += set_uri_version_spacer()
|
||||||
|
req += set_version(c_vers)
|
||||||
|
req += set_host_header(c_host)
|
||||||
|
req += set_agent_header(c_ag)
|
||||||
|
req += set_cookie_header(c_cook)
|
||||||
|
req += set_connection_header(c_conn)
|
||||||
|
req += set_extra_headers(c_head)
|
||||||
|
req += set_body(c_body)
|
||||||
|
|
||||||
if self.request_config['user-agent']
|
|
||||||
req['User-Agent'] = self.request_config['user-agent']
|
|
||||||
end
|
|
||||||
|
|
||||||
#
|
|
||||||
# Configure the rest of the request based on config hash
|
|
||||||
#
|
|
||||||
req['Host'] = (self.request_config['vhost'] || self.hostname) + ':' + self.port.to_s
|
|
||||||
|
|
||||||
# Set the request body if a data chunk has been specified
|
|
||||||
if chash['data']
|
|
||||||
req.body = chash['data']
|
|
||||||
end
|
|
||||||
|
|
||||||
# Merge headers supplied by the caller.
|
|
||||||
if chash['headers']
|
|
||||||
req.headers.merge!(chash['headers'])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Set the content-type
|
|
||||||
if chash['content-type']
|
|
||||||
req['Content-Type'] = chash['content-type']
|
|
||||||
end
|
|
||||||
|
|
||||||
req
|
req
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a CGI compatible request
|
||||||
|
#
|
||||||
|
def request_cgi(opts={})
|
||||||
|
c_enc = opts['encode'] || false
|
||||||
|
c_cgi = opts['cgi'] || '/'
|
||||||
|
c_body = opts['body'] || ''
|
||||||
|
c_meth = opts['method'] || 'GET'
|
||||||
|
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_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']
|
||||||
|
uri = set_cgi(c_cgi)
|
||||||
|
qstr = c_qs
|
||||||
|
pstr = c_body
|
||||||
|
|
||||||
|
c_varg.each_pair do |var,val|
|
||||||
|
qstr << '&' if qstr.length > 0
|
||||||
|
qstr << set_encode_uri(var)
|
||||||
|
qstr << '='
|
||||||
|
qstr << set_encode_uri(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
c_varp.each_pair do |var,val|
|
||||||
|
pstr << '&' if pstr.length > 0
|
||||||
|
pstr << set_encode_uri(var)
|
||||||
|
pstr << '='
|
||||||
|
pstr << set_encode_uri(val)
|
||||||
|
end
|
||||||
|
|
||||||
|
req = ''
|
||||||
|
req += set_method(c_meth)
|
||||||
|
req += set_method_uri_spacer()
|
||||||
|
req += set_uri_prepend()
|
||||||
|
req += set_encode_uri(uri)
|
||||||
|
|
||||||
|
if (qstr.length > 0)
|
||||||
|
req += '?'
|
||||||
|
req += qstr
|
||||||
|
end
|
||||||
|
|
||||||
|
req += set_path_info(c_path)
|
||||||
|
req += set_uri_append()
|
||||||
|
req += set_uri_version_spacer()
|
||||||
|
req += set_version(c_vers)
|
||||||
|
req += set_host_header(c_host)
|
||||||
|
req += set_agent_header(c_ag)
|
||||||
|
req += set_cookie_header(c_cook)
|
||||||
|
req += set_connection_header(c_conn)
|
||||||
|
req += set_extra_headers(c_head)
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# * Implement chunked transfer
|
||||||
|
|
||||||
|
req += set_content_type_header(c_type)
|
||||||
|
req += set_content_len_header(pstr.length)
|
||||||
|
req += set_body(pstr)
|
||||||
|
|
||||||
|
req
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Connects to the remote server if possible.
|
# Connects to the remote server if possible.
|
||||||
#
|
#
|
||||||
|
@ -170,21 +187,18 @@ class Client
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Initializes a request by setting the host header and other cool things.
|
# Transmit a HTTP request and receive the response
|
||||||
#
|
#
|
||||||
def init_request(req)
|
def send_recv(req, t = -1)
|
||||||
req['Host'] = "#{request_config.has_key?('vhost') ? request_config['vhost'] : hostname}:#{port}"
|
send_request(req)
|
||||||
|
read_response(t)
|
||||||
return req
|
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Transmits a request and reads in a response.
|
# Send a HTTP request to the server
|
||||||
#
|
# TODO:
|
||||||
def send_request(req, t = -1)
|
# * Handle junk pipeline requests
|
||||||
resp = Response.new
|
def send_request(req)
|
||||||
resp.max_data = self.client_config['max-data']
|
|
||||||
|
|
||||||
# Connect to the server
|
# Connect to the server
|
||||||
connect
|
connect
|
||||||
|
|
||||||
|
@ -192,7 +206,20 @@ class Client
|
||||||
req_string = req.to_s
|
req_string = req.to_s
|
||||||
|
|
||||||
# Send it on over
|
# Send it on over
|
||||||
conn.put(req_string)
|
ret = conn.put(req)
|
||||||
|
|
||||||
|
# Tell the remote side if we aren't pipelining
|
||||||
|
conn.shutdown(::Socket::SHUT_WR) if (!pipelining?)
|
||||||
|
|
||||||
|
ret
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Read a response from the server
|
||||||
|
#
|
||||||
|
def read_response(t = -1)
|
||||||
|
resp = Response.new
|
||||||
|
resp.max_data = config['read_max_data']
|
||||||
|
|
||||||
# Tell the remote side if we aren't pipelining
|
# Tell the remote side if we aren't pipelining
|
||||||
conn.shutdown(::Socket::SHUT_WR) if (!pipelining?)
|
conn.shutdown(::Socket::SHUT_WR) if (!pipelining?)
|
||||||
|
@ -288,7 +315,201 @@ class Client
|
||||||
def pipelining?
|
def pipelining?
|
||||||
pipeline
|
pipeline
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the encoded URI
|
||||||
|
# ['none','hex-normal', 'hex-all', 'u-normal', 'u-all']
|
||||||
|
def set_encode_uri(uri)
|
||||||
|
Rex::Text.uri_encode(uri, self.config['uri_encode_mode'])
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the encoded query string
|
||||||
|
#
|
||||||
|
def set_encode_qs(qs)
|
||||||
|
Rex::Text.uri_encode(uri, self.config['uri_encode_mode'])
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the uri
|
||||||
|
#
|
||||||
|
def set_uri(uri)
|
||||||
|
if (self.config['uri_full_url'])
|
||||||
|
url = self.ssl ? "https" : "http"
|
||||||
|
url += self.config['vhost']
|
||||||
|
url += (self.port == 80) ? "" : ":#{self.port}"
|
||||||
|
url += uri
|
||||||
|
url
|
||||||
|
else
|
||||||
|
uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the cgi
|
||||||
|
# TODO:
|
||||||
|
# * Implement self-referential directories
|
||||||
|
# * Implement bogus relative directories
|
||||||
|
def set_cgi(uri)
|
||||||
|
|
||||||
|
url = uri
|
||||||
|
|
||||||
|
if (self.config['uri_full_url'])
|
||||||
|
url = self.ssl ? "https" : "http"
|
||||||
|
url += self.config['vhost']
|
||||||
|
url += (self.port == 80) ? "" : ":#{self.port}"
|
||||||
|
url += uri
|
||||||
|
end
|
||||||
|
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP method string
|
||||||
|
#
|
||||||
|
def set_method(method)
|
||||||
|
# TODO:
|
||||||
|
# * Randomize case
|
||||||
|
# * Replace with random valid method
|
||||||
|
# * Replace with random invalid method
|
||||||
|
method
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP version string
|
||||||
|
#
|
||||||
|
def set_version(version)
|
||||||
|
# TODO:
|
||||||
|
# * Randomize case
|
||||||
|
# * Replace with random valid versions
|
||||||
|
# * Replace with random invalid versions
|
||||||
|
"HTTP/" + version + "\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP seperator and body string
|
||||||
|
#
|
||||||
|
def set_body(data)
|
||||||
|
"\r\n" + data
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP path info
|
||||||
|
# TODO:
|
||||||
|
# * Encode path information
|
||||||
|
def set_path_info(path)
|
||||||
|
path ? path : ''
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the spacing between the method and uri
|
||||||
|
#
|
||||||
|
def set_method_uri_spacer
|
||||||
|
# TODO:
|
||||||
|
# * Support different space types
|
||||||
|
" "
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the spacing between the uri and the version
|
||||||
|
#
|
||||||
|
def set_uri_version_spacer
|
||||||
|
# TODO:
|
||||||
|
# * Support different space types
|
||||||
|
" "
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the padding to place before the uri
|
||||||
|
#
|
||||||
|
def set_uri_prepend
|
||||||
|
# TODO:
|
||||||
|
# * Support different padding types
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the padding to place before the uri
|
||||||
|
#
|
||||||
|
def set_uri_append
|
||||||
|
# TODO:
|
||||||
|
# * Support different padding types
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP Host header
|
||||||
|
#
|
||||||
|
def set_host_header(host)
|
||||||
|
return "" if self.config['uri_full_url']
|
||||||
|
host ||= self.config['vhost']
|
||||||
|
set_formatted_header("Host", host)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP agent header
|
||||||
|
#
|
||||||
|
def set_agent_header(agent)
|
||||||
|
agent ? set_formatted_header("User-Agent", agent) : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP cookie header
|
||||||
|
#
|
||||||
|
def set_cookie_header(cookie)
|
||||||
|
cookie ? set_formatted_header("Cookie", cookie) : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the HTTP connection header
|
||||||
|
#
|
||||||
|
def set_connection_header(conn)
|
||||||
|
conn ? set_formatted_header("Connection", conn) : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the content type header
|
||||||
|
#
|
||||||
|
def set_content_type_header(ctype)
|
||||||
|
set_formatted_header("Content-Type", ctype)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return the content length header
|
||||||
|
# TODO:
|
||||||
|
# * Ignore this if chunked encoding is set
|
||||||
|
def set_content_len_header(clen)
|
||||||
|
set_formatted_header("Content-Length", clen)
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a string of formatted extra headers
|
||||||
|
# TODO:
|
||||||
|
# * Implement junk header stuffing
|
||||||
|
def set_extra_headers(headers)
|
||||||
|
buf = ''
|
||||||
|
|
||||||
|
headers.each_pair do |var,val|
|
||||||
|
buf += set_formatted_header(var, val)
|
||||||
|
end
|
||||||
|
|
||||||
|
buf
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a formatted header string
|
||||||
|
# TODO:
|
||||||
|
# * Implement header folder
|
||||||
|
def set_formatted_header(var, val)
|
||||||
|
"#{var}: #{val}\r\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# The client request configuration
|
||||||
|
#
|
||||||
|
attr_accessor :config
|
||||||
#
|
#
|
||||||
# Whether or not pipelining is in use.
|
# Whether or not pipelining is in use.
|
||||||
#
|
#
|
||||||
|
@ -302,10 +523,6 @@ class Client
|
||||||
#
|
#
|
||||||
attr_accessor :local_port
|
attr_accessor :local_port
|
||||||
#
|
#
|
||||||
# Client configuration attributes.
|
|
||||||
#
|
|
||||||
attr_accessor :client_config
|
|
||||||
#
|
|
||||||
# The underlying connection.
|
# The underlying connection.
|
||||||
#
|
#
|
||||||
attr_accessor :conn
|
attr_accessor :conn
|
||||||
|
@ -323,7 +540,7 @@ protected
|
||||||
attr_accessor :ssl
|
attr_accessor :ssl
|
||||||
|
|
||||||
attr_accessor :hostname, :port # :nodoc:
|
attr_accessor :hostname, :port # :nodoc:
|
||||||
attr_accessor :request_config, :client_config # :nodoc:
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,27 +13,27 @@ class Rex::Proto::Http::Client::UnitTest < Test::Unit::TestCase
|
||||||
c = Klass.new('www.google.com')
|
c = Klass.new('www.google.com')
|
||||||
|
|
||||||
# Set request factory parameters
|
# Set request factory parameters
|
||||||
c.config(
|
c.set_config(
|
||||||
'vhost' => 'www.google.com',
|
'vhost' => 'www.google.com',
|
||||||
'user-agent' => 'Metasploit Framework/3.0',
|
'agent' => 'Metasploit Framework/3.0',
|
||||||
'proto' => '1.1',
|
'version' => '1.1',
|
||||||
'cookie' => 'NoCookie=NotACookie'
|
'cookie' => 'NoCookie=NotACookie'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set client parameters
|
# Set client parameters
|
||||||
c.config(
|
c.set_config(
|
||||||
'max-data' => 1024 * 1024
|
'read_max_data' => 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Request the main web pagfe
|
# Request the main web pagfe
|
||||||
#
|
#
|
||||||
r = c.request(
|
r = c.request_raw(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => '/'
|
'uri' => '/'
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = c.send_request(r)
|
resp = c.send_recv(r)
|
||||||
|
|
||||||
assert_equal(200, resp.code)
|
assert_equal(200, resp.code)
|
||||||
assert_equal('OK', resp.message)
|
assert_equal('OK', resp.message)
|
||||||
|
@ -42,12 +42,12 @@ class Rex::Proto::Http::Client::UnitTest < Test::Unit::TestCase
|
||||||
#
|
#
|
||||||
# Request a file that does not exist
|
# Request a file that does not exist
|
||||||
#
|
#
|
||||||
r = c.request(
|
r = c.request_raw(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => '/NoFileHere.404'
|
'uri' => '/NoFileHere.404'
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = c.send_request(r)
|
resp = c.send_recv(r)
|
||||||
|
|
||||||
assert_equal(404, resp.code)
|
assert_equal(404, resp.code)
|
||||||
assert_equal('Not Found', resp.message)
|
assert_equal('Not Found', resp.message)
|
||||||
|
@ -58,45 +58,34 @@ class Rex::Proto::Http::Client::UnitTest < Test::Unit::TestCase
|
||||||
# Send a POST request that results in a 302
|
# Send a POST request that results in a 302
|
||||||
#
|
#
|
||||||
c = Klass.new('beta.microsoft.com')
|
c = Klass.new('beta.microsoft.com')
|
||||||
c.request_option('vhost', 'beta.microsoft.com')
|
c.set_config('vhost' => 'beta.microsoft.com')
|
||||||
|
|
||||||
r = c.request(
|
r = c.request_cgi(
|
||||||
'method' => 'POST',
|
'method' => 'POST',
|
||||||
'uri' => '/',
|
'cgi' => '/',
|
||||||
'data' => 'var=val',
|
'vars_post' => { 'var' => 'val' },
|
||||||
'content-type' => 'application/x-www-form-urlencoded'
|
'ctype' => 'application/x-www-form-urlencoded'
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = c.send_request(r)
|
resp = c.send_recv(r)
|
||||||
|
|
||||||
assert_equal(302, resp.code)
|
assert_equal(200, resp.code)
|
||||||
assert_equal('Object moved', resp.message)
|
assert_equal('OK', resp.message)
|
||||||
assert_equal('1.1', resp.proto)
|
assert_equal('1.1', resp.proto)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_ssl
|
def test_ssl
|
||||||
c = Klass.new('www.geotrust.com', '443', {}, 'true')
|
c = Klass.new('www.geotrust.com', 443, {}, true)
|
||||||
c.request_option('vhost', 'www.geotrust.com')
|
c.set_config('vhost' => 'www.geotrust.com')
|
||||||
r = c.request(
|
r = c.request_raw(
|
||||||
'method' => 'GET',
|
'method' => 'GET',
|
||||||
'uri' => '/'
|
'uri' => '/'
|
||||||
)
|
)
|
||||||
resp = c.send_request(r)
|
resp = c.send_recv(r)
|
||||||
assert_equal(200, resp.code)
|
assert_equal(200, resp.code)
|
||||||
assert_equal('OK', resp.message)
|
assert_equal('OK', resp.message)
|
||||||
assert_equal('1.1', resp.proto)
|
assert_equal('1.1', resp.proto)
|
||||||
c.close
|
c.close
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_junk_pipeline
|
|
||||||
host = 'www.apache.org'
|
|
||||||
client = Klass.new(host)
|
|
||||||
client.junk_pipeline = 5
|
|
||||||
client.request_option('vhost', host)
|
|
||||||
request = client.request('method' => 'GET', 'uri' => '/no-such-uri.html')
|
|
||||||
response = client.send_request(request)
|
|
||||||
assert_equal(404, response.code, 'pipeline response')
|
|
||||||
client.close
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,8 +19,8 @@ class Rex::Proto::Http::Server::UnitTest < Test::Unit::TestCase
|
||||||
c = CliKlass.new(ListenHost, ListenPort)
|
c = CliKlass.new(ListenHost, ListenPort)
|
||||||
|
|
||||||
1.upto(10) {
|
1.upto(10) {
|
||||||
req = Rex::Proto::Http::Request::Get.new('/')
|
req = c.request_raw('uri' => '/')
|
||||||
res = c.send_request(req)
|
res = c.send_recv(req)
|
||||||
assert_not_nil(res)
|
assert_not_nil(res)
|
||||||
assert_equal(404, res.code)
|
assert_equal(404, res.code)
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ class Rex::Proto::Http::Server::UnitTest < Test::Unit::TestCase
|
||||||
})
|
})
|
||||||
|
|
||||||
1.upto(10) {
|
1.upto(10) {
|
||||||
req = Rex::Proto::Http::Request::Get.new('/foo')
|
req = c.request_raw('uri' => '/foo')
|
||||||
res = c.send_request(req)
|
res = c.send_recv(req)
|
||||||
assert_not_nil(res)
|
assert_not_nil(res)
|
||||||
assert_equal(200, res.code)
|
assert_equal(200, res.code)
|
||||||
assert_equal("Chickens everywhere", res.body)
|
assert_equal("Chickens everywhere", res.body)
|
||||||
|
@ -53,8 +53,8 @@ class Rex::Proto::Http::Server::UnitTest < Test::Unit::TestCase
|
||||||
|
|
||||||
s.remove_resource('/foo')
|
s.remove_resource('/foo')
|
||||||
|
|
||||||
req = Rex::Proto::Http::Request::Get.new('/foo')
|
req = c.request_raw('uri' => '/foo')
|
||||||
res = c.send_request(req)
|
res = c.send_recv(req)
|
||||||
assert_not_nil(res)
|
assert_not_nil(res)
|
||||||
assert_equal(404, res.code)
|
assert_equal(404, res.code)
|
||||||
ensure
|
ensure
|
||||||
|
|
|
@ -306,7 +306,7 @@ module Text
|
||||||
return str if mode == 'none' # fast track no encoding
|
return str if mode == 'none' # fast track no encoding
|
||||||
|
|
||||||
all = /[^\/\\]+/
|
all = /[^\/\\]+/
|
||||||
normal = /[^a-zA-Z1-9]+/
|
normal = /[^a-zA-Z1-9\/\/\\]+/
|
||||||
|
|
||||||
case mode
|
case mode
|
||||||
when 'hex-normal'
|
when 'hex-normal'
|
||||||
|
|
Loading…
Reference in New Issue