diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index 5402b3ba3e..b10af20c3c 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -23,7 +23,6 @@ module Http ### class Client - DefaultUserAgent = Rex::Proto::Http::ClientRequest::DefaultUserAgent # # Creates a new client instance @@ -41,8 +40,6 @@ class Client self.config = { 'read_max_data' => (1024*1024*1), 'vhost' => self.hostname, - 'version' => '1.1', - 'agent' => DefaultUserAgent, }.merge(Http::ClientRequest::DefaultConfig) self.config_types = { diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index 1b55784e99..31933fe5f7 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -14,6 +14,25 @@ class ClientRequest DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)" DefaultConfig = { + # + # Regular HTTP stuff + # + 'agent' => DefaultUserAgent, + 'cgi' => true, + 'cookie' => nil, + 'data' => '', + 'headers' => {}, + 'raw_headers' => '', + 'method' => 'GET', + 'path_info' => '', + 'port' => 80, + 'proto' => 'HTTP', + 'ssl' => false, + 'uri' => '/', + 'vars_get' => {}, + 'vars_post' => {}, + 'version' => '1.1', + # # Evasion options # @@ -45,6 +64,7 @@ class ClientRequest 'uri_fake_params_start' => false, # bool 'header_folding' => false, # bool 'chunked_size' => 0, # integer + # # NTLM Options # @@ -61,68 +81,25 @@ class ClientRequest 'DigestAuthIIS' => true } - attr_accessor :authorization - attr_accessor :cgi - attr_accessor :config - attr_accessor :connection - attr_accessor :content_type - attr_accessor :cookie - attr_accessor :data - attr_accessor :headers - attr_accessor :host - attr_accessor :method - attr_accessor :path - attr_accessor :port - attr_accessor :protocol - attr_accessor :query - attr_accessor :raw_headers - attr_accessor :ssl - attr_accessor :uri - attr_accessor :user_agent - attr_accessor :vars_get - attr_accessor :vars_post - attr_accessor :version - attr_reader :opts def initialize(opts={}) - @cgi = (opts['cgi'].nil? ? true : opts['cgi']) - @config = DefaultConfig.merge(opts['client_config'] || {}) - @connection = opts['connection'] - @content_type = opts['ctype'] - @cookie = opts['cookie'] - @data = opts['data'] || "" - @headers = opts['headers'] || {} - @host = opts['vhost'] - @method = opts['method'] || "GET" - @path = opts['path_info'] - @port = opts['port'] || 80 - @protocol = opts['proto'] || "HTTP" - @query = opts['query'] || "" - @ssl = opts['ssl'] - @raw_headers = opts['raw_headers'] || "" - @uri = opts['uri'] - @user_agent = opts['agent'] - @vars_get = opts['vars_get'] || {} - @vars_post = opts['vars_post'] || {} - @version = opts['version'] || "1.1" - @opts = opts - + @opts = DefaultConfig.merge(opts) end def to_s # Start GET query string - qstr = query.dup + qstr = opts['query'] ? opts['query'].dup : "" # Start POST data string - pstr = data.dup + pstr = opts['data'] ? opts['data'].dup : "" - if cgi + if opts['cgi'] uri_str= set_cgi - if (config['pad_get_params']) - 1.upto(config['pad_get_params_count'].to_i) do |i| + if (opts['pad_get_params']) + 1.upto(opts['pad_get_params_count'].to_i) do |i| qstr << '&' if qstr.length > 0 qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1)) qstr << '=' @@ -130,33 +107,33 @@ class ClientRequest end end - vars_get.each_pair do |var,val| + opts['vars_get'].each_pair do |var,val| qstr << '&' if qstr.length > 0 - qstr << (config['encode_params'] ? set_encode_uri(var) : var) + qstr << (opts['encode_params'] ? set_encode_uri(var) : var) qstr << '=' - qstr << (config['encode_params'] ? set_encode_uri(val) : val) + qstr << (opts['encode_params'] ? set_encode_uri(val) : val) end - if (config['pad_post_params']) - 1.upto(config['pad_post_params_count'].to_i) do |i| + if (opts['pad_post_params']) + 1.upto(opts['pad_post_params_count'].to_i) do |i| rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1) rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1) pstr << '&' if pstr.length > 0 - pstr << (config['encode_params'] ? set_encode_uri(rand_var) : rand_var) + pstr << (opts['encode_params'] ? set_encode_uri(rand_var) : rand_var) pstr << '=' - pstr << (config['encode_params'] ? set_encode_uri(rand_val) : rand_val) + pstr << (opts['encode_params'] ? set_encode_uri(rand_val) : rand_val) end end - vars_post.each_pair do |var,val| + opts['vars_post'].each_pair do |var,val| pstr << '&' if pstr.length > 0 - pstr << (config['encode_params'] ? set_encode_uri(var) : var) + pstr << (opts['encode_params'] ? set_encode_uri(var) : var) pstr << '=' - pstr << (config['encode_params'] ? set_encode_uri(val) : val) + pstr << (opts['encode_params'] ? set_encode_uri(val) : val) end else uri_str = set_uri - if config['encode'] + if opts['encode'] qstr = set_encode_uri(qstr) end end @@ -166,7 +143,7 @@ class ClientRequest req << set_method_uri_spacer() req << set_uri_prepend() - if config['encode'] + if opts['encode'] req << set_encode_uri(uri_str) else req << uri_str @@ -185,7 +162,7 @@ class ClientRequest req << set_host_header # If an explicit User-Agent header is set, then use that instead of the value of user_agent - unless headers.keys.map{|x| x.downcase }.include?('user-agent') + unless opts['headers'].keys.map{|x| x.downcase }.include?('user-agent') req << set_agent_header end @@ -197,19 +174,19 @@ class ClientRequest req << set_content_type_header req << set_content_len_header(pstr.length) req << set_chunked_header() - req << raw_headers + req << opts['raw_headers'] req << set_body(pstr) end protected def set_uri - uri_str = uri.dup - if (config['uri_dir_self_reference']) + uri_str = opts['uri'].dup + if (opts['uri_dir_self_reference']) uri_str.gsub!('/', '/./') end - if (config['uri_dir_fake_relative']) + if (opts['uri_dir_fake_relative']) buf = "" uri_str.split('/').each do |part| cnt = rand(8)+2 @@ -222,10 +199,10 @@ class ClientRequest uri_str = buf end - if (config['uri_full_url']) - url = self.ssl ? "https://" : "http://" - url << self.config['vhost'] - url << ((self.port == 80) ? "" : ":#{self.port}") + if (opts['uri_full_url']) + url = opts['ssl'] ? "https://" : "http://" + url << opts['vhost'] + url << ((opts['port'] == 80) ? "" : ":#{opts['port']}") url << uri_str url else @@ -234,12 +211,12 @@ class ClientRequest end def set_cgi - uri_str = uri.dup - if (config['uri_dir_self_reference']) + uri_str = opts['uri'].dup + if (opts['uri_dir_self_reference']) uri_str.gsub!('/', '/./') end - if (config['uri_dir_fake_relative']) + if (opts['uri_dir_fake_relative']) buf = "" uri_str.split('/').each do |part| cnt = rand(8)+2 @@ -254,10 +231,10 @@ class ClientRequest url = uri_str - if (config['uri_full_url']) - url = self.ssl ? "https" : "http" - url << self.config['vhost'] - url << (self.port == 80) ? "" : ":#{self.port}" + if (opts['uri_full_url']) + url = opts['ssl'] ? "https" : "http" + url << opts['vhost'] + url << (opts['port'] == 80) ? "" : ":#{opts['port']}" url << uri_str end @@ -266,24 +243,24 @@ class ClientRequest def set_encode_uri(str) a = str.dup - config['uri_encode_count'].times { - a = Rex::Text.uri_encode(a, config['uri_encode_mode']) + opts['uri_encode_count'].times { + a = Rex::Text.uri_encode(a, opts['uri_encode_mode']) } return a end def set_method - ret = method.dup + ret = opts['method'].dup - if (config['method_random_valid']) + if (opts['method_random_valid']) ret = ['GET', 'POST', 'HEAD'][rand(3)] end - if (config['method_random_invalid']) + if (opts['method_random_invalid']) ret = Rex::Text.rand_text_alpha(rand(20)+1) end - if (config['method_random_case']) + if (opts['method_random_case']) ret = Rex::Text.to_rand_case(ret) end @@ -291,11 +268,11 @@ class ClientRequest end def set_method_uri_spacer - len = config['pad_method_uri_count'].to_i + len = opts['pad_method_uri_count'].to_i set = " " buf = "" - case config['pad_method_uri_type'] + case opts['pad_method_uri_type'] when 'tab' set = "\t" when 'apache' @@ -315,11 +292,11 @@ class ClientRequest def set_uri_prepend prefix = "" - if (config['uri_fake_params_start']) + if (opts['uri_fake_params_start']) prefix << '/%3fa=b/../' end - if (config['uri_fake_end']) + if (opts['uri_fake_end']) prefix << '/%20HTTP/1.0/../../' end @@ -331,7 +308,7 @@ class ClientRequest # TODO: # * Encode path information def set_path_info - path ? path : '' + opts['path_info'] ? opts['path_info'] : '' end # @@ -347,11 +324,11 @@ class ClientRequest # Return the spacing between the uri and the version # def set_uri_version_spacer - len = config['pad_uri_version_count'].to_i + len = opts['pad_uri_version_count'].to_i set = " " buf = "" - case config['pad_uri_version_type'] + case opts['pad_uri_version_type'] when 'tab' set = "\t" when 'apache' @@ -369,17 +346,17 @@ class ClientRequest # Return the HTTP version string # def set_version - ret = protocol + "/" + version + ret = opts['proto'] + "/" + opts['version'] - if (config['version_random_valid']) - ret = protocol + "/" + ['1.0', '1.1'][rand(2)] + if (opts['version_random_valid']) + ret = opts['proto'] + "/" + ['1.0', '1.1'][rand(2)] end - if (config['version_random_invalid']) + if (opts['version_random_invalid']) ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) end - if (config['version_random_case']) + if (opts['version_random_case']) ret = Rex::Text.to_rand_case(ret) end @@ -390,7 +367,7 @@ class ClientRequest # Return a formatted header string # def set_formatted_header(var, val) - if (self.config['header_folding']) + if (self.opts['header_folding']) "#{var}:\r\n\t#{val}\r\n" else "#{var}: #{val}\r\n" @@ -401,38 +378,38 @@ class ClientRequest # Return the HTTP agent header # def set_agent_header - user_agent ? set_formatted_header("User-Agent", user_agent) : "" + opts['agent'] ? set_formatted_header("User-Agent", opts['agent']) : "" end def set_auth_header - authorization ? set_formatted_header("Authorization", authorization) : "" + opts['authorization'] ? set_formatted_header("Authorization", opts['authorization']) : "" end # # Return the HTTP cookie header # def set_cookie_header - cookie ? set_formatted_header("Cookie", cookie) : "" + opts['cookie'] ? set_formatted_header("Cookie", opts['cookie']) : "" end # # Return the HTTP connection header # def set_connection_header - connection ? set_formatted_header("Connection", connection) : "" + opts['connection'] ? set_formatted_header("Connection", opts['connection']) : "" end # # Return the content type header # def set_content_type_header - set_formatted_header("Content-Type", content_type) + opts['ctype'] ? set_formatted_header("Content-Type", opts['ctype']) : "" end # # Return the content length header def set_content_len_header(clen) - return "" if config['chunked_size'] > 0 + return "" if opts['chunked_size'] > 0 set_formatted_header("Content-Length", clen) end @@ -440,8 +417,8 @@ class ClientRequest # Return the HTTP Host header # def set_host_header - return "" if config['uri_full_url'] - host ||= config['vhost'] + return "" if opts['uri_full_url'] + host ||= opts['vhost'] # IPv6 addresses must be placed in brackets if Rex::Socket.is_ipv6?(host) @@ -449,8 +426,8 @@ class ClientRequest end # The port should be appended if non-standard - if not [80,443].include?(port) - host = host + ":#{port}" + if not [80,443].include?(opts['port']) + host = host + ":#{opts['port']}" end set_formatted_header("Host", host) @@ -462,8 +439,8 @@ class ClientRequest def set_extra_headers buf = '' - if (config['pad_fake_headers']) - 1.upto(config['pad_fake_headers_count'].to_i) do |i| + if (opts['pad_fake_headers']) + 1.upto(opts['pad_fake_headers_count'].to_i) do |i| buf << set_formatted_header( Rex::Text.rand_text_alphanumeric(rand(32)+1), Rex::Text.rand_text_alphanumeric(rand(32)+1) @@ -471,7 +448,7 @@ class ClientRequest end end - headers.each_pair do |var,val| + opts['headers'].each_pair do |var,val| buf << set_formatted_header(var, val) end @@ -479,7 +456,7 @@ class ClientRequest end def set_chunked_header - return "" if config['chunked_size'] == 0 + return "" if opts['chunked_size'] == 0 set_formatted_header('Transfer-Encoding', 'chunked') end @@ -487,11 +464,11 @@ class ClientRequest # Return the HTTP seperator and body string # def set_body(bdata) - return "\r\n" + bdata if config['chunked_size'] == 0 + return "\r\n" + bdata if opts['chunked_size'] == 0 str = bdata.dup chunked = '' while str.size > 0 - chunk = str.slice!(0,rand(config['chunked_size']) + 1) + chunk = str.slice!(0,rand(opts['chunked_size']) + 1) chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n" end "\r\n" + chunked + "0\r\n\r\n" diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb index 154a1a9485..29db9873bf 100644 --- a/spec/lib/rex/proto/http/client_request_spec.rb +++ b/spec/lib/rex/proto/http/client_request_spec.rb @@ -4,7 +4,7 @@ require 'rex/proto/http/client_request' shared_context "with 'uri_dir_self_reference'" do before(:all) do - client_request.config['uri_dir_self_reference'] = true + client_request.opts['uri_dir_self_reference'] = true end it "should return the unmodified uri" do @@ -14,9 +14,9 @@ end shared_context "with no evasions" do before(:all) do - client_request.config['uri_dir_self_reference'] = false - client_request.config['uri_fake_params_start'] = false - client_request.config['uri_full_url'] = false + client_request.opts['uri_dir_self_reference'] = false + client_request.opts['uri_fake_params_start'] = false + client_request.opts['uri_full_url'] = false end it "should return the unmodified uri" do @@ -27,11 +27,11 @@ end shared_context "with 'uri_full_url'" do before(:all) do - client_request.config['uri_full_url'] = true + client_request.opts['uri_full_url'] = true end before(:each) do - client_request.config['vhost'] = host + client_request.opts['vhost'] = host end context "with ipv4 host" do @@ -43,7 +43,7 @@ shared_context "with 'uri_full_url'" do context "with ipv6 host" do let(:host) { '2001:DB8::1' } #before(:each) do - # client_request.config['vhost'] = "[#{host}]" + # client_request.opts['vhost'] = "[#{host}]" #end it_behaves_like "uri_full_url" @@ -83,9 +83,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with reasonable default options", default_options.merge({ 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", - # Yes, vhost is in the config. There is no godly reason why this - # should be so. - 'client_config' => { 'vhost' => 'www.example.com', }, + 'vhost' => 'www.example.com', }), { :set_cgi => { :result => "/" }, @@ -106,7 +104,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with header folding", default_options.merge({ 'agent' => "Mozilla/4.0 (compatible; Metasploit RSPEC)", - 'client_config' => { 'header_folding' => true, } + 'header_folding' => true, }), { :set_uri => { :result => "/" }, @@ -124,7 +122,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with ipv6 host", default_options.merge({ - 'client_config' => { 'vhost' => "2001:DB8::1" }, + 'vhost' => "2001:DB8::1", }), { :set_host_header => { :result => "Host: [2001:DB8::1]\r\n" }, @@ -134,7 +132,7 @@ describe Rex::Proto::Http::ClientRequest do [ "with ipv6 host and non-default port", default_options.merge({ 'port' => 1234, - 'client_config' => { 'vhost' => "2001:DB8::1" }, + 'vhost' => "2001:DB8::1", }), { :set_host_header => { :result => "Host: [2001:DB8::1]:1234\r\n" }, @@ -162,11 +160,9 @@ describe Rex::Proto::Http::ClientRequest do context "with GET paramaters" do subject(:client_request) { options_with_params = default_options.merge({ - 'client_config' => { - 'uri_encode_mode' => encode_mode, - 'encode_params' => encode_params, - 'encode' => false, - }, + 'uri_encode_mode' => encode_mode, + 'encode_params' => encode_params, + 'encode' => false, 'vars_get' => vars_get, }) Rex::Proto::Http::ClientRequest.new(options_with_params) diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index 8dc10b8c46..11177b90fe 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -56,8 +56,6 @@ describe Rex::Proto::Http::Client do req = cli.request_cgi req.should be_a_kind_of Rex::Proto::Http::ClientRequest - req.port.should == 80 - req.ssl.should be_false end it "should attempt to connect to a server" do