Parent

Class/Module Index [+]

Quicksearch

Rex::Proto::Http::Server::UnitTest::CliKlass

Acts as a client to an HTTP server, sending requests and receiving responses.

See the RFC: www.w3.org/Protocols/rfc2616/rfc2616.html

Constants

DefaultUserAgent

Attributes

config[RW]

The client request configuration

config_types[RW]

The client request configuration classes

conn[RW]

The underlying connection.

context[RW]

The calling context to pass to the socket

junk_pipeline[RW]

When parsing the request, thunk off the first response from the server, since junk

local_host[RW]

The local host of the client.

local_port[RW]

The local port of the client.

pipeline[RW]

Whether or not pipelining is in use.

proxies[RW]

The proxy list

Public Class Methods

new(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil) click to toggle source

Creates a new client instance

# File lib/rex/proto/http/client.rb, line 23
def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil)
        self.hostname = host
        self.port     = port.to_i
        self.context  = context
        self.ssl      = ssl
        self.ssl_version = ssl_version
        self.proxies  = proxies
        self.config = {
                'read_max_data'   => (1024*1024*1),
                'vhost'           => self.hostname,
                'version'         => '1.1',
                'agent'           => DefaultUserAgent,
                #
                # Evasion options
                #
                'uri_encode_mode'        => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all
                'uri_encode_count'       => 1,       # integer
                'uri_full_url'           => false,   # bool
                'pad_method_uri_count'   => 1,       # integer
                'pad_uri_version_count'  => 1,       # integer
                'pad_method_uri_type'    => 'space', # space, tab, apache
                'pad_uri_version_type'   => 'space', # space, tab, apache
                'method_random_valid'    => false,   # bool
                'method_random_invalid'  => false,   # bool
                'method_random_case'     => false,   # bool
                'version_random_valid'   => false,   # bool
                'version_random_invalid' => false,   # bool
                'version_random_case'    => false,   # bool
                'uri_dir_self_reference' => false,   # bool
                'uri_dir_fake_relative'  => false,   # bool
                'uri_use_backslashes'    => false,   # bool
                'pad_fake_headers'       => false,   # bool
                'pad_fake_headers_count' => 16,      # integer
                'pad_get_params'         => false,   # bool
                'pad_get_params_count'   => 8,       # integer
                'pad_post_params'        => false,   # bool
                'pad_post_params_count'  => 8,       # integer
                'uri_fake_end'           => false,   # bool
                'uri_fake_params_start'  => false,   # bool
                'header_folding'         => false,   # bool
                'chunked_size'           => 0        # integer
        }

        # This is not used right now...
        self.config_types = {
                'uri_encode_mode'        => ['hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-random', 'u-all'],
                'uri_encode_count'       => 'integer',
                'uri_full_url'           => 'bool',
                'pad_method_uri_count'   => 'integer',
                'pad_uri_version_count'  => 'integer',
                'pad_method_uri_type'    => ['space', 'tab', 'apache'],
                'pad_uri_version_type'   => ['space', 'tab', 'apache'],
                'method_random_valid'    => 'bool',
                'method_random_invalid'  => 'bool',
                'method_random_case'     => 'bool',
                'version_random_valid'   => 'bool',
                'version_random_invalid' => 'bool',
                'version_random_case'    => 'bool',
                'uri_dir_self_reference' => 'bool',
                'uri_dir_fake_relative'  => 'bool',
                'uri_use_backslashes'    => 'bool',
                'pad_fake_headers'       => 'bool',
                'pad_fake_headers_count' => 'integer',
                'pad_get_params'         => 'bool',
                'pad_get_params_count'   => 'integer',
                'pad_post_params'        => 'bool',
                'pad_post_params_count'  => 'integer',
                'uri_fake_end'           => 'bool',
                'uri_fake_params_start'  => 'bool',
                'header_folding'         => 'bool',
                'chunked_size'           => 'integer'
        }
end

Public Instance Methods

close() click to toggle source

Closes the connection to the remote server.

# File lib/rex/proto/http/client.rb, line 331
def close
        if (self.conn)
                self.conn.shutdown
                self.conn.close
        end

        self.conn = nil
end
conn?() click to toggle source

Returns whether or not the conn is valid.

# File lib/rex/proto/http/client.rb, line 456
def conn?
        conn != nil
end
connect(t = -1) click to toggle source

Connects to the remote server if possible.

# File lib/rex/proto/http/client.rb, line 303
def connect(t = -1)
        # If we already have a connection and we aren't pipelining, close it.
        if (self.conn)
                if !pipelining?
                        close
                else
                        return self.conn
                end
        end

        timeout = (t.nil? or t == -1) ? 0 : t

        self.conn = Rex::Socket::Tcp.create(
                'PeerHost'  => self.hostname,
                'PeerPort'  => self.port.to_i,
                'LocalHost' => self.local_host,
                'LocalPort' => self.local_port,
                'Context'   => self.context,
                'SSL'       => self.ssl,
                'SSLVersion'=> self.ssl_version,
                'Proxies'   => self.proxies,
                'Timeout'   => timeout
        )
end
pipelining?() click to toggle source

Whether or not connections should be pipelined.

# File lib/rex/proto/http/client.rb, line 463
def pipelining?
        pipeline
end
read_response(t = -1) click to toggle source

Read a response from the server

# File lib/rex/proto/http/client.rb, line 364
def read_response(t = -1)

        resp = Response.new
        resp.max_data = config['read_max_data']

        # Wait at most t seconds for the full response to be read in.  We only
        # do this if t was specified as a negative value indicating an infinite
        # wait cycle.  If t were specified as nil it would indicate that no
        # response parsing is required.

        return resp if not t

        Timeout.timeout((t < 0) ? nil : t) do

                rv = nil
                while (
                         rv != Packet::ParseCode::Completed and
                         rv != Packet::ParseCode::Error
                  )

                        begin

                                buff = conn.get_once(-1, 1)
                                rv   = resp.parse( buff || '' )

                        ##########################################################################
                        # XXX: NOTE: BUG: get_once currently (as of r10042) rescues "Exception"
                        # As such, the following rescue block will ever be reached.  -jjd
                        ##########################################################################

                        # Handle unexpected disconnects
                        rescue ::Errno::EPIPE, ::EOFError, ::IOError
                                case resp.state
                                when Packet::ParseState::ProcessingHeader
                                        resp = nil
                                when Packet::ParseState::ProcessingBody
                                        # truncated request, good enough
                                        resp.error = :truncated
                                end
                                break
                        end

                        # This is a dirty hack for broken HTTP servers
                        if rv == Packet::ParseCode::Completed
                                rbody = resp.body
                                rbufq = resp.bufq

                                rblob = rbody.to_s + rbufq.to_s
                                tries = 0
                                begin
                                        # XXX: This doesn't deal with chunked encoding or "Content-type: text/html; charset=..."
                                        while tries < 1000 and resp.headers["Content-Type"]== "text/html" and rblob !~ /<\/html>/
                                                buff = conn.get_once(-1, 0.05)
                                                break if not buff
                                                rblob += buff
                                                tries += 1
                                        end
                                rescue ::Errno::EPIPE, ::EOFError, ::IOError
                                end

                                resp.bufq = ""
                                resp.body = rblob
                        end
                end
        end

        return resp if not resp

        # As a last minute hack, we check to see if we're dealing with a 100 Continue here.
        if resp.proto == '1.1' and resp.code == 100
                # If so, our real response becaome the body, so we re-parse it.
                body = resp.body
                resp = Response.new
                resp.max_data = config['read_max_data']
                rv = resp.parse(body)
                # XXX: At some point, this may benefit from processing post-completion code
                # as seen above.
        end

        resp
end
request_cgi(opts={}) click to toggle source

Create a CGI compatible request

Options:

  • agent: User-Agent header value

  • basic_auth: Basic-Auth header value

  • connection: Connection header value

  • cookie: Cookie header value

  • ctype: Content-Type header value, default: application/x-www-form-urlencoded

  • data: HTTP data (only useful with some methods, see rfc2616)

  • encode: URI encode the supplied URI

  • headers: HTTP headers as a hash, e.g. { "X-MyHeader" => "value" }

  • method: HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET

  • proto: protocol, default: HTTP

  • query: raw query string

  • raw_headers: HTTP headers as a hash

  • uri: the URI to request

  • vars_get: GET variables as a hash to be translated into a query string

  • vars_post: POST variables as a hash to be translated into POST data

  • version: version of the protocol, default: 1.1

  • vhost: Host header value

# File lib/rex/proto/http/client.rb, line 209
def request_cgi(opts={})
        c_enc  = opts['encode']     || false
        c_cgi  = opts['uri']        || '/'
        c_body = opts['data']       || ''
        c_meth = opts['method']     || 'GET'
        c_prot = opts['proto']      || 'HTTP'
        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_rawh = opts['raw_headers']|| config['raw_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']
        c_auth = opts['basic_auth'] || config['basic_auth'] || ''

        uri    = set_cgi(c_cgi)
        qstr   = c_qs
        pstr   = c_body

        if (config['pad_get_params'])
                1.upto(config['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 << '='
                        qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
                end
        end

        c_varg.each_pair do |var,val|
                qstr << '&' if qstr.length > 0
                qstr << set_encode_uri(var)
                qstr << '='
                qstr << set_encode_uri(val)
        end

        if (config['pad_post_params'])
                1.upto(config['pad_post_params_count'].to_i) do |i|
                        pstr << '&' if qstr.length > 0
                        pstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
                        pstr << '='
                        pstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
                end
        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 << (c_enc ? set_encode_uri(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_prot, c_vers)
        req << set_host_header(c_host)
        req << set_agent_header(c_ag)

        if (c_auth.length > 0)
                req << set_basic_auth_header(c_auth)
        end

        req << set_cookie_header(c_cook)
        req << set_connection_header(c_conn)
        req << set_extra_headers(c_head)

        req << set_content_type_header(c_type)
        req << set_content_len_header(pstr.length)
        req << set_chunked_header()
        req << set_raw_headers(c_rawh)
        req << set_body(pstr)

        req
end
request_raw(opts={}) click to toggle source

Create an arbitrary HTTP request

# File lib/rex/proto/http/client.rb, line 132
def request_raw(opts={})
        c_enc  = opts['encode']     || false
        c_uri  = opts['uri']        || '/'
        c_body = opts['data']       || ''
        c_meth = opts['method']     || 'GET'
        c_prot = opts['proto']      || 'HTTP'
        c_vers = opts['version']    || config['version'] || '1.1'
        c_qs   = opts['query']
        c_ag   = opts['agent']      || config['agent']
        c_cook = opts['cookie']     || config['cookie']
        c_host = opts['vhost']      || config['vhost'] || self.hostname
        c_head = opts['headers']    || config['headers'] || {}
        c_rawh = opts['raw_headers']|| config['raw_headers'] || ''
        c_conn = opts['connection']
        c_auth = opts['basic_auth'] || config['basic_auth'] || ''

        # An agent parameter was specified, but so was a header, prefer the header
        if c_ag and c_head.keys.map{|x| x.downcase }.include?('user-agent')
                c_ag = nil
        end
        
        uri    = set_uri(c_uri)

        req = ''
        req << set_method(c_meth)
        req << set_method_uri_spacer()
        req << set_uri_prepend()
        req << (c_enc ? set_encode_uri(uri) : uri)

        if (c_qs)
                req << '?'
                req << (c_enc ? set_encode_qs(c_qs) : c_qs)
        end

        req << set_uri_append()
        req << set_uri_version_spacer()
        req << set_version(c_prot, c_vers)
        req << set_host_header(c_host)
        req << set_agent_header(c_ag)


        if (c_auth.length > 0)
                req << set_basic_auth_header(c_auth)
        end

        req << set_cookie_header(c_cook)
        req << set_connection_header(c_conn)
        req << set_extra_headers(c_head)
        req << set_raw_headers(c_rawh)
        req << set_body(c_body)

        req
end
send_recv(req, t = -1, persist=false) click to toggle source

Transmit an HTTP request and receive the response If persist is set, then the request will attempt to reuse an existing connection.

# File lib/rex/proto/http/client.rb, line 345
def send_recv(req, t = -1, persist=false)
        @pipeline = persist
        send_request(req, t)
        res = read_response(t)
        res.request = req.to_s if res
        res
end
send_request(req, t = -1) click to toggle source

Send an HTTP request to the server

# File lib/rex/proto/http/client.rb, line 356
def send_request(req, t = -1)
        connect(t)
        conn.put(req.to_s)
end
set_agent_header(agent) click to toggle source

Return the HTTP agent header

# File lib/rex/proto/http/client.rb, line 713
def set_agent_header(agent)
        agent ? set_formatted_header("User-Agent", agent) : ""
end
set_basic_auth_header(auth) click to toggle source

Return the Authorization basic-auth header

# File lib/rex/proto/http/client.rb, line 748
def set_basic_auth_header(auth)
        auth ? set_formatted_header("Authorization", "Basic " + Rex::Text.encode_base64(auth)) : ""
end
set_body(data) click to toggle source

Return the HTTP seperator and body string

# File lib/rex/proto/http/client.rb, line 601
def set_body(data)
        return "\r\n" + data if self.config['chunked_size'] == 0
        str = data.dup
        chunked = ''
        while str.size > 0
                chunk = str.slice!(0,rand(self.config['chunked_size']) + 1)
                chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n"
        end
        "\r\n" + chunked + "0\r\n\r\n"
end
set_cgi(uri) click to toggle source

Return the cgi

# File lib/rex/proto/http/client.rb, line 525
def set_cgi(uri)

        if (self.config['uri_dir_self_reference'])
                uri.gsub!('/', '/./')
        end

        if (self.config['uri_dir_fake_relative'])
                buf = ""
                uri.split('/').each do |part|
                        cnt = rand(8)+2
                        1.upto(cnt) { |idx|
                                buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
                        }
                        buf << ("/.." * cnt)
                        buf << "/" + part
                end
                uri = buf
        end

        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
set_chunked_header() click to toggle source
# File lib/rex/proto/http/client.rb, line 774
def set_chunked_header()
        return "" if self.config['chunked_size'] == 0
        set_formatted_header('Transfer-Encoding', 'chunked')
end
set_config(opts = {}) click to toggle source

Set configuration options

# File lib/rex/proto/http/client.rb, line 100
def set_config(opts = {})
        opts.each_pair do |var,val|
                # Default type is string
                typ = self.config_types[var] || 'string'

                # These are enum types
                if(typ.class.to_s == 'Array')
                        if not typ.include?(val)
                                raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
                        end
                end

                # The caller should have converted these to proper ruby types, but
                # take care of the case where they didn't before setting the
                # config.

                if(typ == 'bool')
                        val = (val =~ /^(t|y|1)$/ ? true : false || val === true)
                end

                if(typ == 'integer')
                        val = val.to_i
                end

                self.config[var]=val
        end

end
set_connection_header(conn) click to toggle source

Return the HTTP connection header

# File lib/rex/proto/http/client.rb, line 727
def set_connection_header(conn)
        conn ? set_formatted_header("Connection", conn) : ""
end
set_content_len_header(clen) click to toggle source

Return the content length header

# File lib/rex/proto/http/client.rb, line 740
def set_content_len_header(clen)
        return "" if self.config['chunked_size'] > 0
        set_formatted_header("Content-Length", clen)
end
set_content_type_header(ctype) click to toggle source

Return the content type header

# File lib/rex/proto/http/client.rb, line 734
def set_content_type_header(ctype)
        set_formatted_header("Content-Type", ctype)
end
set_encode_qs(qs) click to toggle source

Return the encoded query string

# File lib/rex/proto/http/client.rb, line 481
def set_encode_qs(qs)
        a = qs
        self.config['uri_encode_count'].times {
                a = Rex::Text.uri_encode(a, self.config['uri_encode_mode'])
        }
        return a
end
set_encode_uri(uri) click to toggle source

Return the encoded URI

'none','hex-normal', 'hex-all', 'u-normal', 'u-all'
# File lib/rex/proto/http/client.rb, line 470
def set_encode_uri(uri)
        a = uri
        self.config['uri_encode_count'].times {
                a = Rex::Text.uri_encode(a, self.config['uri_encode_mode'])
        }
        return a
end
set_extra_headers(headers) click to toggle source

Return a string of formatted extra headers

# File lib/rex/proto/http/client.rb, line 755
def set_extra_headers(headers)
        buf = ''

        if (self.config['pad_fake_headers'])
                1.upto(self.config['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)
                        )
                end
        end

        headers.each_pair do |var,val|
                buf << set_formatted_header(var, val)
        end

        buf
end
set_formatted_header(var, val) click to toggle source

Return a formatted header string

# File lib/rex/proto/http/client.rb, line 789
def set_formatted_header(var, val)
        if (self.config['header_folding'])
                "#{var}:\r\n\t#{val}\r\n"
        else
                "#{var}: #{val}\r\n"
        end
end
set_host_header(host=nil) click to toggle source

Return the HTTP Host header

# File lib/rex/proto/http/client.rb, line 693
def set_host_header(host=nil)
        return "" if self.config['uri_full_url']
        host ||= self.config['vhost']

        # IPv6 addresses must be placed in brackets
        if Rex::Socket.is_ipv6?(host)
                host = "[#{host}]"
        end

        # The port should be appended if non-standard
        if not [80,443].include?(self.port)
                host = host + ":#{port}"
        end

        set_formatted_header("Host", host)
end
set_method(method) click to toggle source

Return the HTTP method string

# File lib/rex/proto/http/client.rb, line 559
def set_method(method)
        ret = method

        if (self.config['method_random_valid'])
                ret = ['GET', 'POST', 'HEAD'][rand(3)]
        end

        if (self.config['method_random_invalid'])
                ret = Rex::Text.rand_text_alpha(rand(20)+1)
        end

        if (self.config['method_random_case'])
                ret = Rex::Text.to_rand_case(ret)
        end

        ret
end
set_method_uri_spacer() click to toggle source

Return the spacing between the method and uri

# File lib/rex/proto/http/client.rb, line 623
def set_method_uri_spacer
        len = self.config['pad_method_uri_count'].to_i
        set = " "
        buf = ""

        case self.config['pad_method_uri_type']
        when 'tab'
                set = "\t"
        when 'apache'
                set = "\t \x0b\x0c\x0d"
        end

        while(buf.length < len)
                buf << set[ rand(set.length) ]
        end

        return buf
end
set_path_info(path) click to toggle source

Return the HTTP path info TODO:

* Encode path information
# File lib/rex/proto/http/client.rb, line 616
def set_path_info(path)
        path ? path : ''
end
set_raw_headers(data) click to toggle source

Return a string of raw header data

# File lib/rex/proto/http/client.rb, line 782
def set_raw_headers(data)
        data
end
set_uri(uri) click to toggle source

Return the uri

# File lib/rex/proto/http/client.rb, line 492
def set_uri(uri)

        if (self.config['uri_dir_self_reference'])
                uri.gsub!('/', '/./')
        end

        if (self.config['uri_dir_fake_relative'])
                buf = ""
                uri.split('/').each do |part|
                        cnt = rand(8)+2
                        1.upto(cnt) { |idx|
                                buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
                        }
                        buf << ("/.." * cnt)
                        buf << "/" + part
                end
                uri = buf
        end

        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
set_uri_append() click to toggle source

Return the padding to place before the uri

# File lib/rex/proto/http/client.rb, line 684
def set_uri_append
        # TODO:
        #  * Support different padding types
        ""
end
set_uri_prepend() click to toggle source

Return the padding to place before the uri

# File lib/rex/proto/http/client.rb, line 667
def set_uri_prepend
        prefix = ""

        if (self.config['uri_fake_params_start'])
                prefix << '/%3fa=b/../'
        end

        if (self.config['uri_fake_end'])
                prefix << '/%20HTTP/1.0/../../'
        end

        prefix
end
set_uri_version_spacer() click to toggle source

Return the spacing between the uri and the version

# File lib/rex/proto/http/client.rb, line 645
def set_uri_version_spacer
        len = self.config['pad_uri_version_count'].to_i
        set = " "
        buf = ""

        case self.config['pad_uri_version_type']
        when 'tab'
                set = "\t"
        when 'apache'
                set = "\t \x0b\x0c\x0d"
        end

        while(buf.length < len)
                buf << set[ rand(set.length) ]
        end

        return buf
end
set_version(protocol, version) click to toggle source

Return the HTTP version string

# File lib/rex/proto/http/client.rb, line 580
def set_version(protocol, version)
        ret = protocol + "/" + version

        if (self.config['version_random_valid'])
                ret = protocol + "/" +  ['1.0', '1.1'][rand(2)]
        end

        if (self.config['version_random_invalid'])
                ret = Rex::Text.rand_text_alphanumeric(rand(20)+1)
        end

        if (self.config['version_random_case'])
                ret = Rex::Text.to_rand_case(ret)
        end

        ret << "\r\n"
end
stop() click to toggle source

Cleans up any outstanding connections and other resources.

# File lib/rex/proto/http/client.rb, line 449
def stop
        close
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.