Profile sharing works for the first time
parent
c18c5c7b6e
commit
f3e026db6c
|
@ -154,9 +154,9 @@ 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]}]
|
||||
Hash[tmp.map{|(k,v)| [k.to_s,v]}]
|
||||
end
|
||||
|
||||
|
||||
|
@ -214,22 +214,23 @@ module Msf
|
|||
# @param profile [Hash] The profile to check
|
||||
# @return [Array] An array of requirements not met
|
||||
def get_bad_requirements(profile)
|
||||
profile = profile.first[1]
|
||||
bad_reqs = []
|
||||
|
||||
@requirements.each do |k, v|
|
||||
expected = k != :vuln_test ? v : 'true'
|
||||
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
|
||||
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_s]}")
|
||||
|
||||
if k == :activex
|
||||
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
|
||||
bad_reqs << k if has_bad_activex?(profile[k.to_s])
|
||||
elsif k == :vuln_test
|
||||
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
|
||||
bad_reqs << k unless profile[k.to_s].to_s == 'true'
|
||||
elsif v.is_a? Regexp
|
||||
bad_reqs << k if profile[k.to_sym] !~ v
|
||||
bad_reqs << k if profile[k.to_s] !~ v
|
||||
elsif v.is_a? Proc
|
||||
bad_reqs << k unless v.call(profile[k.to_sym])
|
||||
bad_reqs << k unless v.call(profile[k.to_s])
|
||||
else
|
||||
bad_reqs << k if profile[k.to_sym] != v
|
||||
bad_reqs << k if profile[k.to_s] != v
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -246,7 +247,9 @@ module Msf
|
|||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||
def retrieve_tag(cli, request)
|
||||
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
|
||||
#$stderr.puts "Found cookie: #{cookie.inspect}"
|
||||
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
|
||||
#$stderr.puts "Found tag: #{tag}"
|
||||
|
||||
if tag.blank?
|
||||
# Browser probably doesn't allow cookies, plan B :-/
|
||||
|
@ -269,9 +272,10 @@ 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)
|
||||
update_profile(tag, :source, source.to_s)
|
||||
|
||||
found_ua_name = ''
|
||||
found_ua_ver = ''
|
||||
|
||||
# Gathering target info from the detection stage
|
||||
case source
|
||||
|
@ -279,7 +283,9 @@ module Msf
|
|||
# Gathers target data from a POST request
|
||||
parsed_body = CGI::parse(Rex::Text.decode_base64(request.body) || '')
|
||||
vprint_debug("Received sniffed browser data over POST: \n#{parsed_body}.")
|
||||
parsed_body.each { |k, v| update_profile(target_info, k.to_sym, v.first) }
|
||||
parsed_body.each { |k, v| update_profile(tag, k.to_s, 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.
|
||||
|
@ -288,19 +294,21 @@ module Msf
|
|||
# Kill this to save space.
|
||||
fp.delete(:ua_string)
|
||||
fp.each do |k, v|
|
||||
update_profile(target_info, k.to_sym, v)
|
||||
update_profile(tag, k.to_s, 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'] || '')
|
||||
update_profile(tag, :proxy, has_proxy?(request))
|
||||
update_profile(tag, :language, request.headers['Accept-Language'] || '')
|
||||
|
||||
report_client({
|
||||
:host => cli.peerhost,
|
||||
:ua_string => request.headers['User-Agent'],
|
||||
:ua_name => target_info[:ua_name],
|
||||
:ua_ver => target_info[:ua_ver]
|
||||
:ua_name => found_ua_name,
|
||||
:ua_ver => found_ua_ver
|
||||
})
|
||||
end
|
||||
|
||||
|
@ -402,6 +410,7 @@ module Msf
|
|||
datastore['CookieName'] || DEFAULT_COOKIE_NAME
|
||||
end
|
||||
|
||||
|
||||
def cookie_header(tag)
|
||||
cookie = "#{cookie_name}=#{tag};"
|
||||
if datastore['CookieExpiration'].present?
|
||||
|
@ -423,7 +432,7 @@ module Msf
|
|||
#
|
||||
# This is the information gathering stage
|
||||
#
|
||||
if get_profile(retrieve_tag(cli, request))
|
||||
unless get_profile_info(retrieve_tag(cli, request)).empty?
|
||||
send_redirect(cli, get_module_resource)
|
||||
return
|
||||
end
|
||||
|
@ -459,15 +468,15 @@ module Msf
|
|||
#
|
||||
tag = retrieve_tag(cli, request)
|
||||
vprint_status("Serving exploit to user with tag #{tag}")
|
||||
profile = get_profile(tag)
|
||||
if profile.nil?
|
||||
profile = get_profile_info(tag)
|
||||
if profile.empty?
|
||||
print_status("Browsing directly to the exploit URL is forbidden.")
|
||||
send_not_found(cli)
|
||||
elsif profile[:tried] and datastore['Retries'] == false
|
||||
print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.")
|
||||
send_not_found(cli)
|
||||
else
|
||||
update_profile(profile, :tried, true)
|
||||
update_profile(tag, :tried, true)
|
||||
vprint_status("Setting target \"#{tag}\" to :tried.")
|
||||
try_set_target(profile)
|
||||
bad_reqs = get_bad_requirements(profile)
|
||||
|
|
|
@ -1,69 +1,46 @@
|
|||
require 'Msgpack'
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::BrowserProfileManager
|
||||
def initialize(info={})
|
||||
super
|
||||
# See get_profile's documentation to understand what @target_profiles stores
|
||||
@target_profiles = {}
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
# 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]
|
||||
NOTE_TYPE_PREFIX = 'BrowserExploitServer.Client'
|
||||
|
||||
def get_profile_info(tag)
|
||||
normalized_tag = "#{NOTE_TYPE_PREFIX}.#{tag}"
|
||||
framework.db.notes.each do |note|
|
||||
return MessagePack.unpack(note.data) if note.ntype == normalized_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
|
||||
def update_profile(tag, key, value)
|
||||
profile = get_profile_info(tag)
|
||||
if profile.empty?
|
||||
init_profile(tag)
|
||||
profile = get_profile_info(tag)
|
||||
end
|
||||
|
||||
normalized_tag = "#{NOTE_TYPE_PREFIX}.#{tag}"
|
||||
profile[normalized_tag][key.to_s] = value
|
||||
framework.db.report_note(
|
||||
:type => normalized_tag,
|
||||
:data => profile.to_msgpack,
|
||||
:update => :unique
|
||||
)
|
||||
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
|
||||
normalized_tag = "#{NOTE_TYPE_PREFIX}.#{tag}"
|
||||
empty_profile = { normalized_tag => {} }
|
||||
framework.db.report_note(
|
||||
:type => normalized_tag,
|
||||
:data => empty_profile.to_msgpack,
|
||||
:update => :unique
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue