357 lines
7.5 KiB
Ruby
357 lines
7.5 KiB
Ruby
require 'uri'
|
|
require 'rex/proto/http'
|
|
|
|
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'
|
|
|
|
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?$/))
|
|
self.method = md[1]
|
|
self.raw_uri = URI.decode(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
|
|
|
|
|
|
protected
|
|
|
|
#
|
|
# 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
|