549 lines
14 KiB
Ruby
549 lines
14 KiB
Ruby
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, 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']
|
|
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(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
|
|
|
|
super(cli, body, headers)
|
|
end
|
|
|
|
end
|
|
|
|
end
|