require 'rex/service_manager' module Msf ### # # This module provides methods for acting as an HTTP client when # exploiting an HTTP server. # ### module Exploit::Remote::HttpClient # # Initializes an exploit module that exploits a vulnerability in an HTTP # server. # def initialize(info = {}) super register_options( [ Opt::RHOST, Opt::RPORT(80), OptString.new('VHOST', [ false, "HTTP server virtual host" ]), Opt::SSL, ], Exploit::Remote::HttpClient ) register_evasion_options( [ OptEnum.new('HTTP::uri_encode', [false, 'Enable URI encoding', 'none', ['none','hex-normal', 'hex-all', 'u-normal', 'u-all'], 'none']), 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']), 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::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']), ], Exploit::Remote::HttpClient ) end # # Connects to an HTTP server. # def connect(opts={}) nclient = Rex::Proto::Http::Client.new( rhost, rport.to_i, { 'Msf' => framework, 'MsfExploit' => self, }, ssl ) # Configure the HTTP client with the supplied parameter nclient.config( { 'vhost' => datastore['VHOST'], }.update(opts) ) # If this connection is global, persist it if (opts['global']) if (self.client) disconnect end self.client = nclient end return nclient end # # Passes the client connection down to the handler to see if it's of any # use. # def handler(nsock = nil) # If no socket was provided, try the global one. if ((!nsock) and (self.client)) nsock = self.client.conn end # If the parent claims the socket associated with the HTTP client, then # we rip the socket out from under the HTTP client. if (((rv = super(nsock)) == Handler::Claimed) and (self.client) and (nsock == self.client.conn)) self.client.conn = nil end rv end # # Disconnects the HTTP client # def disconnect(nclient = self.client) if (nclient) nclient.close end if (nclient == self.client) self.client = nil end end # # Performs cleanup as necessary, disconnecting the HTTP client if it's # still established. # def cleanup super disconnect end # # Connects to the server, creates a request, sends the request, reads the response # def request(opts={}, timeout = -1) c = connect(opts) if (datastore['HTTP::junk_pipeline'].to_i > 0) 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 ## # # Wrappers for getters # ## # # Returns the target host # def rhost datastore['RHOST'] end # # Returns the remote port # def rport datastore['RPORT'] end # # Returns the VHOST of the HTTP server. # def vhost datastore['VHOST'] || datastore['RHOST'] end # # Returns the boolean indicating SSL # def ssl datastore['SSL'] end protected attr_accessor :client end ### # # This module provides methods for exploiting an HTTP client by acting # as an HTTP server. # ### module Exploit::Remote::HttpServer include Msf::Exploit::Remote::TcpServer protected def initialize(info = {}) super register_options( [ OptString.new('URIPATH', [ false, "The URI to use for this exploit (default is random)"]), ], Exploit::Remote::HttpServer ) register_evasion_options( [ OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP responses 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']), OptEnum.new('HTTP::compression', [false, 'Enable compression of HTTP responses via content encoding', 'none', ['none','gzip','deflate']]), ], Exploit::Remote::HttpServer ) end # # Ensures that gzip can be used. If not, an exception is generated. The # exception is only raised if the DisableGzip advanced option has not been # set. # def use_zlib if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true) raise RuntimeError, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!" end end # # This method gives a derived class the opportunity to ensure that all # dependencies are present before initializing the service. # def check_dependencies end # # This mixin starts the HTTP server listener. This routine takes a few # different hash parameters: # # ServerHost => Override the server host to listen on (default to SRVHOST). # ServerPort => Override the server port to listen on (default to SRVPORT). # Uri => The URI to handle and the associated procedure to call. # def start_service(opts = {}) check_dependencies # Default the server host and port to what is required by the mixin. opts = { 'ServerHost' => datastore['SRVHOST'], 'ServerPort' => datastore['SRVPORT'], }.update(opts) # Start the HTTP server service. self.service = Rex::ServiceManager.start( Rex::Proto::Http::Server, opts['ServerPort'].to_i, opts['ServerHost'], { 'Msf' => framework, 'MsfExploit' => self, } ) self.service.server_name = 'Apache' # Default the procedure of the URI to on_request_uri if one isn't # provided. uopts = { 'Proc' => Proc.new { |cli, req| evasions(cli) on_request_uri(cli, req) }, 'Path' => resource_uri }.update(opts['Uri'] || {}) print_status("Using URL: http://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}") add_resource(uopts) end # # Adds a URI resource using the supplied hash parameters. # # Path => The path to associate the procedure with. # Proc => The procedure to call when the URI is requested. # LongCall => Indicates that the request is a long call. # def add_resource(opts) @service_path = opts['Path'] service.add_resource(opts['Path'], opts) end # # Returns the last-used resource path # def get_resource @service_path end # # Removes a URI resource. # def remove_resource(name) service.remove_resource(name) end # # Closes a client connection. # def close_client(cli) service.close_client(cli) end # # Creates an HTTP response packet. # def create_response(code = 200, message = "OK", proto = Rex::Proto::Http::DefaultProtocol) return Rex::Proto::Http::Response.new(code, message, proto); end # # Transmits a response to the supplied client, default content-type is text/html # # Payload evasions are implemented here! # def send_response(cli, body, headers = {}) response = create_response response['Content-Type'] = 'text/html' response.body = body if (datastore['HTTP::compression']) self.use_zlib # make sure... response.compress = datastore['HTTP::compression'] end if (datastore['HTTP::chunked'] == true) response.auto_cl = false response.transfer_chunked = true end if (datastore['HTTP::header_folding'] == true) response.headers.fold = 1 end if (datastore['HTTP::junk_headers'] == true) response.headers.junk_headers = 1 end headers.each_pair { |k,v| response[k] = v } cli.send_response(response) end # # Sends a 302 redirect to the client # def send_redirect(cli, location='/', body='') response = create_response(302, 'Moved') response['Content-Type'] = 'text/html' response['Location'] = location response.body = body cli.send_response(response) end # # Sends a 302 redirect relative to our base path # def send_local_redirect(cli, location) send_redirect(cli, get_resource + location) end # # Returns the configured (or random, if not configured) URI path # def resource_uri path = datastore['URIPATH'] || random_uri path = '/' + path if path !~ /^\// return path end # # Generates a random URI for use with making finger printing more # challenging. # def random_uri "/" + Rex::Text.rand_text_alphanumeric(rand(64) + 10) end # # Re-generates the payload, substituting the current RHOST and RPORT with # the supplied client host and port. # def regenerate_payload(cli) # Update the datastore with the supplied client peerhost/peerport datastore['RHOST'] = cli.peerhost datastore['RPORT'] = cli.peerport # If the payload fails to generate for some reason, send a 403. if ((p = super()) == nil) print_error("Failed to generate payload, sending 403.") cli.send_response( create_response(403, 'Forbidden')) return nil end p end ## # # Override methods # ## # # Called when a request is made to a single URI registered during the # start_service. Subsequent registrations will not result in a call to # on_request_uri. # def on_request_uri(cli, request) end end ### # # This module provides methods for exploiting an HTTP client by acting # as an HTTP server. # ### module Exploit::Remote::HttpServer::HTML include Msf::Exploit::Remote::HttpServer protected def initialize(info = {}) super register_evasion_options( [ # utf-8, utf-7 and utf-7-all are currently not supported by # most browsers. as such, they are not added by default. The # mixin supports encoding using them, however they are not # listed in the Option. OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', 'none', ['none', 'utf-16le', 'utf-16be', 'utf-16be-marker', 'utf-32le', 'utf-32be']]), OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]), OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]), ], Exploit::Remote::HttpServer::HTML ) end # Transmits a html response to the supplied client # # HTML evasions are implemented here. def send_response_html(cli, body, headers = {}) if datastore['HTML::base64'] != 'none' case datastore['HTML::base64'] when 'plain' body = Rex::Text.encode_base64(body) when 'single_pad' body = Rex::Text.encode_base64(' ' + body) when 'double_pad' body = Rex::Text.encode_base64(' ' + body) when 'random_space_injection' body = Rex::Text.encode_base64(body) new = '' while (body.size > 0) new += body.slice!(0, rand(3) + 1) + Rex::Text.rand_text(rand(5) + 1, '', " \n") end body = new end body = 'Could not render object' end if datastore['HTML::javascript::escape'] > 0 datastore['HTML::javascript::escape'].times { body = '' } end if ['utf-16le','utf-16be','utf32-le','utf32-be','utf-7','utf-8'].include?(datastore['HTML::unicode']) headers['Content-Type'] = 'text/html; charset: ' + datastore['HTML::unicode'] body = Rex::Text.to_unicode(body, datastore['HTML::unicode']) else # special cases case datastore['HTML::unicode'] when 'utf-16be-marker' headers['Content-Type'] = 'text/html' body = "\xFE\xFF" + Rex::Text.to_unicode(body, 'utf-16be') when 'utf-7-all' headers['Content-Type'] = 'text/html; charset: utf-7' body = Rex::Text.to_unicode(body, 'utf-7', 'all') when 'none' # do nothing else raise RuntimeError, 'Invalid unicode. how did you get here?' end end super(cli, body, headers) end end end