metasploit-framework/lib/msf/core/exploit/http.rb

526 lines
13 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(
[
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]),
], Exploit::Remote::HttpClient
)
end
#
# Connects to an HTTP server.
#
def connect(opts={})
nclient = Rex::Proto::Http::Client.new(
datastore['RHOST'],
datastore['RPORT'].to_i,
{
'Msf' => framework,
'MsfExploit' => self,
},
datastore['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={})
c = connect
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::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::junk_self_referring_directories'] == true)
request.junk_self_referring_directories = 1
end
c.send_request(request)
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
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(
[
OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', 'none', ['none','little', 'big']]),
OptInt.new('HTML::javascript::base64', [false, 'Enable HTML obfuscation via base64 (number of iterations)', 0]),
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::javascript::base64'] > 0
datastore['HTML::javascript::base64'].times {
body = '<script>document.write(d("' + Rex::Text.encode_base64(body).gsub(/\r?\n/smi,'') + '"))</script>'
}
body = '<script>
var k = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + "=";
function d(x) {
var o = "";
var c1, c2, c3, e1, e2, e3, e4 = "";
var i = 0;
do {
e1 = k.indexOf(x.charAt(i++));
e2 = k.indexOf(x.charAt(i++));
e3 = k.indexOf(x.charAt(i++));
e4 = k.indexOf(x.charAt(i++));
c1 = (e1 << 2) | (e2 >> 4);
c2 = ((e2 & 15) << 4) | (e3 >> 2);
c3 = ((e3 & 3) << 6) | e4;
o = o + String.fromCharCode(c1);
if (e3 != 64) {
o = o + String.fromCharCode(c2);
}
if (e4 != 64) {
o = o + String.fromCharCode(c3);
}
c1 = c2 = c3 = e1 = e2 = e3 = e4 = "";
} while (i < x.length);
return o;
}</script>'.gsub(/[\s\r\n]{2,}/smi,'') + body
end
if datastore['HTML::javascript::escape'] > 0
datastore['HTML::javascript::escape'].times {
body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>'
}
end
# body = "<html><body>#{body}</body></html>"
if datastore['HTML::unicode'] == 'little'
body = Rex::Text.to_unicode(body)
elsif datastore['HTML::unicode'] == 'big'
body = "\xFE\xFF" + Rex::Text.to_unicode(body, 1)
end
super(cli, body, headers)
end
end
end