rapid7/metasploit-framework

View on GitHub
lib/rex/proto/http/request.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# -*- coding: binary -*-
require 'uri'


module Rex
module Proto
module Http

###
#
# HTTP request class.
#
###
class Request < Packet

  PostRequests = ['POST', 'SEARCH']

  ##
  #
  # Some individual request types.
  #
  ##

  #
  # HTTP GET request class wrapper.
  #
  class Get < Request
    def initialize(uri = '/', proto = DefaultProtocol)
      super('GET', uri, proto)
    end
  end

  #
  # HTTP POST request class wrapper.
  #
  class Post < Request
    def initialize(uri = '/', proto = DefaultProtocol)
      super('POST', uri, proto)
    end
  end

  #
  # HTTP PUT request class wrapper.
  #
  class Put < Request
    def initialize(uri = '/', proto = DefaultProtocol)
      super('PUT', uri, proto)
    end
  end

  #
  # Initializes an instance of an HTTP request with the supplied method, URI,
  # and protocol.
  #
  def initialize(method = 'GET', uri = '/', proto = DefaultProtocol)
    super()

    self.method    = method
    self.raw_uri   = uri
    self.uri_parts = {}
    self.proto     = proto || DefaultProtocol
    self.chunk_min_size = 1
    self.chunk_max_size = 10
    self.uri_encode_mode = 'hex-normal'

    if self.method == 'GET' || self.method == 'CONNECT'
      self.auto_cl = false
    end

    update_uri_parts
  end

  #
  # Updates the command parts for this specific packet type.
  #
  def update_cmd_parts(str)
    if (md = str.match(/^(.+?)\s+(.+?)\s+HTTP\/(.+?)\r?\n?$/i))
      self.method  = md[1]
      self.raw_uri = CGI.unescape(md[2])
      self.proto   = md[3]

      update_uri_parts
    else
      raise RuntimeError, "Invalid request command string", caller
    end
  end

  #
  # Split the URI into the resource being requested and its query string.
  #
  def update_uri_parts
    # If it has a query string, get the parts.
    if ((self.raw_uri) and (md = self.raw_uri.match(/(.+?)\?(.*)$/)))
      self.uri_parts['QueryString'] = parse_cgi_qstring(md[2])
      self.uri_parts['Resource']    = md[1]
    # Otherwise, just assume that the URI is equal to the resource being
    # requested.
    else
      self.uri_parts['QueryString'] = {}
      self.uri_parts['Resource']    = self.raw_uri
    end

    self.normalize!(resource)
    # Set the relative resource to the actual resource.
    self.relative_resource = resource
  end

  # normalize out multiple slashes, directory traversal, and self referrential directories
  def normalize!(str)
    i = 0
    while (str.gsub!(/(\/\.\/|\/\w+\/\.\.\/|\/\/)/,'/')); i += 1; end
    i
  end

  # Puts a URI back together based on the URI parts
  def uri
    str = self.uri_parts['Resource'].dup || '/'

    # /././././
    if self.junk_self_referring_directories
      str.gsub!(/\//) {
        '/.' * (rand(3) + 1) + '/'
      }
    end

    # /%3faaa=bbbbb
    # which could possibly decode to "/?aaa=bbbbb", which if the IDS normalizes first, then splits the URI on ?, then it can be bypassed
    if self.junk_param_start
      str.sub!(/\//, '/%3f' + Rex::Text.rand_text_alpha(rand(5) + 1) + '=' + Rex::Text.rand_text_alpha(rand(10) + 1) + '/../')
    end

    # /RAND/../RAND../
    if self.junk_directories
      str.gsub!(/\//) {
        dirs = ''
        (rand(5)+5).times {
          dirs << '/' + Rex::Text.rand_text_alpha(rand(5) + 1) + '/..'
        }
        dirs + '/'
      }
    end

    # ////
    #
    # NOTE: this must be done after all other odd directory junk, since they would cancel this out, except junk_end_of_uri, since that a specific slash in a specific place
    if self.junk_slashes
      str.gsub!(/\//) {
        '/' * (rand(3) + 2)
      }
      str.sub!(/^[\/]+/, '/') # only one beginning slash!
    end

    # /%20HTTP/1.0%0d%0a/../../
    # which decodes to "/ HTTP/1.0\r\n"
    if self.junk_end_of_uri
      str.sub!(/^\//, '/%20HTTP/1.0%0d%0a/../../')
    end

    Rex::Text.uri_encode(str, self.uri_encode_mode)

    if !PostRequests.include?(self.method)
      if param_string.size > 0
        str << '?' + param_string
      end
    end
    str
  end

  def param_string
    params=[]
    self.uri_parts['QueryString'].each_pair { |param, value|
      # inject a random number of params in between each param
      if self.junk_params
        rand(10)+5.times {
          params.push(Rex::Text.rand_text_alpha(rand(16) + 5) + '=' + Rex::Text.rand_text_alpha(rand(10) + 1))
        }
      end
      if value.kind_of?(Array)
        value.each { |subvalue|
                    params.push(Rex::Text.uri_encode(param, self.uri_encode_mode) + '=' + Rex::Text.uri_encode(subvalue, self.uri_encode_mode))
        }
      else
        if !value.nil?
          params.push(Rex::Text.uri_encode(param, self.uri_encode_mode) + '=' + Rex::Text.uri_encode(value, self.uri_encode_mode))
        else
          params.push(Rex::Text.uri_encode(param, self.uri_encode_mode))
        end
      end
    }

    # inject some junk params at the end of the param list, just to be sure :P
    if self.junk_params
      rand(10)+5.times {
        params.push(Rex::Text.rand_text_alpha(rand(32) + 5) + '=' + Rex::Text.rand_text_alpha(rand(64) + 5))
      }
    end
    params.join('&')
  end

  # Updates the underlying URI structure
  def uri=(str)
    self.raw_uri = str
    update_uri_parts
  end

  # Returns a request packet
  def to_s
    str = ''
    if self.junk_pipeline
      host = ''
      if self.headers['Host']
        host = "Host: #{self.headers['Host']}\r\n"
      end
      str << "GET / HTTP/1.1\r\n#{host}Connection: Keep-Alive\r\n\r\n" * self.junk_pipeline
      self.headers['Connection'] = 'Closed'
    end
    str + super
  end

  def body
    str = super || ''
    if str.length > 0
      return str
    end

    if PostRequests.include?(self.method)
      return param_string
    end
    ''
  end

  #
  # Returns the command string derived from the three values.
  #
  def cmd_string
    proto_str = (self.proto =~ /^\d/) ? "HTTP/#{self.proto}" : self.proto

    "#{self.method} #{self.uri} #{proto_str}\r\n"
  end

  #
  # Returns the resource that is being requested.
  #
  def resource
    self.uri_parts['Resource']
  end

  #
  # Changes the resource URI.  This is used when making a request relative to
  # a given mount point.
  #
  def resource=(rsrc)
    self.uri_parts['Resource'] = rsrc
  end

  #
  # If there were CGI parameters in the URI, this will hold a hash of each
  # variable to value.  If there is more than one value for a given variable,
  # an array of each value is returned.
  #
  def qstring
    self.uri_parts['QueryString']
  end

  #
  # Returns a hash of variables that contain information about the request,
  # such as the remote host information.
  #
  # TODO
  #
  def meta_vars
  end

  #
  # The method being used for the request (e.g. GET).
  #
  attr_accessor :method
  #
  # The raw URI being requested, before any mucking gets to it
  #
  attr_accessor :raw_uri

  #
  # The split up parts of the URI.
  #
  attr_accessor :uri_parts
  #
  # The protocol to be sent with the request.
  #
  attr_accessor :proto

  #
  # The resource path relative to the root of a server mount point.
  #
  attr_accessor :relative_resource

  # add junk directories
  attr_accessor :junk_directories

  # add junk slashes
  attr_accessor :junk_slashes

  # add junk self referring directories (aka  /././././)
  attr_accessor :junk_self_referring_directories

  # add junk params
  attr_accessor :junk_params

  # add junk pipeline requests
  attr_accessor :junk_pipeline

  # add junk start of params
  attr_accessor :junk_param_start

  # add junk end of URI
  attr_accessor :junk_end_of_uri

  # encoding uri
  attr_accessor :uri_encode_mode

  #
  # Parses a CGI query string into the var/val combinations.
  #
  def parse_cgi_qstring(str)
    qstring = {}

    # Delimit on each variable
    str.split(/[;&]/).each { |vv|
      var = vv
      val = ''

      if (md = vv.match(/(.+?)=(.*)/))
        var = md[1]
        val = md[2]
      end

      # Add the item to the hash with logic to convert values to an array
      # if so desired.
      if (qstring.include?(var))
        if (qstring[var].kind_of?(Array))
          qstring[var] << val
        else
          curr = self.qstring[var]
          qstring[var] = [ curr, val ]
        end
      else
        qstring[var] = val
      end
    }

    return qstring
  end

end

end
end
end