mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-29 18:07:27 +01:00
Land #4992, @wchen-r7's support for multiple ActiveX controls on BrowserExploitServerMerge
This commit is contained in:
commit
72a0909e9b
@ -49,24 +49,30 @@ module Msf
|
||||
|
||||
# Requirements a browser module can define in either BrowserRequirements or in targets
|
||||
REQUIREMENT_KEY_SET = Set.new([
|
||||
:source, # Either 'script' or 'headers'
|
||||
:ua_name, # Example: MSIE
|
||||
:ua_ver, # Example: 8.0, 9.0
|
||||
:os_name, # Example: Windows 7, Linux
|
||||
:os_device, # Example: iPad, iPhone, etc
|
||||
:os_vendor, # Example: Microsoft, Ubuntu, Apple, etc
|
||||
:os_sp, # Example: SP2
|
||||
:language, # Example: en-us
|
||||
:arch, # Example: x86
|
||||
:proxy, # 'true' or 'false'
|
||||
:silverlight, # 'true' or 'false'
|
||||
:office, # Example: "2007", "2010"
|
||||
:java, # Example: 1.6, 1.6.0.0
|
||||
:clsid, # ActiveX clsid. Also requires the :method key
|
||||
:method, # ActiveX method. Also requires the :clsid key
|
||||
:mshtml_build, # mshtml build. Example: "65535"
|
||||
:flash, # Example: "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'}]
|
||||
# Where each Hash is a test case
|
||||
# But when BES receives this information, the JavaScript will return this format:
|
||||
# "{CLSID}=>Method=>Boolean;"
|
||||
# Also see: #has_bad_activex?
|
||||
:activex
|
||||
])
|
||||
|
||||
def initialize(info={})
|
||||
@ -105,68 +111,61 @@ module Msf
|
||||
super
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns the custom 404 URL set by the user
|
||||
#
|
||||
# @return [String]
|
||||
#
|
||||
def get_custom_404_url
|
||||
datastore['Custom404'].to_s
|
||||
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
|
||||
#
|
||||
def get_module_resource
|
||||
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns the absolute URL to the module's resource that points to on_request_exploit
|
||||
#
|
||||
# @return [String] absolute URI to the exploit page
|
||||
#
|
||||
def get_module_uri
|
||||
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns the current target
|
||||
#
|
||||
def get_target
|
||||
@target
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns a hash of recognizable requirements
|
||||
#
|
||||
# @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)}
|
||||
# Make sure keys are always symbols
|
||||
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Sets the target automatically based on what requirements are met.
|
||||
# If there's a possible matching target, it will also merge the requirements.
|
||||
# You can use the get_target() method to retrieve the most current target.
|
||||
#
|
||||
# @param profile [Hash] The profile to check
|
||||
#
|
||||
def try_set_target(profile)
|
||||
match_counts = []
|
||||
target_requirements = {}
|
||||
@ -195,30 +194,36 @@ module Msf
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns true if there's a bad ActiveX, otherwise false.
|
||||
# @param ax [String] The raw activex the JavaScript detection will return in this format:
|
||||
# "{CLSID}=>Method=>Boolean;"
|
||||
# @return [Boolean] True if there's a bad ActiveX, otherwise false
|
||||
def has_bad_activex?(ax)
|
||||
ax.split(';').each do |a|
|
||||
bool = a.split('=>')[2]
|
||||
if bool == 'false'
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Returns an array of items that do not meet the requirements
|
||||
#
|
||||
# @param profile [Hash] The profile to check
|
||||
# @return [Array] An array of requirements not met
|
||||
#
|
||||
def get_bad_requirements(profile)
|
||||
bad_reqs = []
|
||||
|
||||
# At this point the check is already done.
|
||||
# If :activex is true, that means the clsid + method had a match,
|
||||
# if not, then false.
|
||||
if @requirements[:clsid] and @requirements[:method]
|
||||
@requirements[:activex] = 'true' # Script passes boolean as string
|
||||
end
|
||||
|
||||
@requirements.each do |k, v|
|
||||
# Special keys to ignore because the script registers this as [:activex] = true or false
|
||||
next if k == :clsid or k == :method
|
||||
|
||||
expected = k != :vuln_test ? v : 'true'
|
||||
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
|
||||
|
||||
if k == :vuln_test
|
||||
if k == :activex
|
||||
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
|
||||
elsif k == :vuln_test
|
||||
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
|
||||
elsif v.is_a? Regexp
|
||||
bad_reqs << k if profile[k.to_sym] !~ v
|
||||
@ -232,7 +237,6 @@ module Msf
|
||||
bad_reqs
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the target profile based on the tag. Each profile has the following structure:
|
||||
# 'cookie_name' =>
|
||||
# {
|
||||
@ -253,7 +257,7 @@ module Msf
|
||||
#
|
||||
# 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 method is available from an ActiveX control (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
|
||||
@ -261,45 +265,41 @@ module Msf
|
||||
#
|
||||
# @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.
|
||||
# First it obtains the tag from the browser's "Cookie" header.
|
||||
# If the header is empty (possible if the browser has cookies disabled),
|
||||
# then it will return a tag based on IP + the user-agent.
|
||||
#
|
||||
# @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)
|
||||
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
|
||||
@ -317,13 +317,12 @@ module Msf
|
||||
tag
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Registers target information to @target_profiles
|
||||
#
|
||||
# @param source [Symbol] Either :script, or :headers
|
||||
# @param cli [Socket] Socket for the browser
|
||||
# @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)
|
||||
@ -361,27 +360,28 @@ module Msf
|
||||
})
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Checks if the target is running a proxy
|
||||
#
|
||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||
# @return [Boolean] True if found, otherwise false
|
||||
#
|
||||
def has_proxy?(request)
|
||||
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
|
||||
!proxy_header_set.empty?
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Returns the code for client-side detection
|
||||
#
|
||||
# @param user_agent [String] The user-agent of the browser
|
||||
# @return [String] Returns the HTML for detection
|
||||
#
|
||||
def get_detection_html(user_agent)
|
||||
print_debug(user_agent)
|
||||
ua_info = fingerprint_user_agent(user_agent)
|
||||
os = ua_info[:os_name]
|
||||
client = ua_info[:ua_name]
|
||||
print_debug(os.inspect)
|
||||
print_debug(client.inspect)
|
||||
|
||||
code = ERB.new(%Q|
|
||||
<%= js_base64 %>
|
||||
@ -418,11 +418,20 @@ module Msf
|
||||
d['office'] = ie_addons_detect.getMsOfficeVersion();
|
||||
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
|
||||
<%
|
||||
clsid = @requirements[:clsid]
|
||||
method = @requirements[:method]
|
||||
if clsid and method
|
||||
activex = @requirements[:activex]
|
||||
if activex
|
||||
activex.each do \|a\|
|
||||
clsid = a[:clsid]
|
||||
method = a[:method]
|
||||
%>
|
||||
d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
|
||||
var ax = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
|
||||
d['activex'] = "";
|
||||
if (ax == true) {
|
||||
d['activex'] += "<%=clsid%>=><%=method%>=>true;";
|
||||
} else {
|
||||
d['activex'] += "<%=clsid%>=><%=method%>=>false;";
|
||||
}
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@ -438,7 +447,7 @@ module Msf
|
||||
|
||||
%Q|
|
||||
<script>
|
||||
#{js}
|
||||
#{code}
|
||||
</script>
|
||||
<noscript>
|
||||
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
|
||||
@ -462,12 +471,11 @@ module Msf
|
||||
cookie
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Handles exploit stages.
|
||||
#
|
||||
# @param cli [Socket] Socket for the browser
|
||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||
#
|
||||
def on_request_uri(cli, request)
|
||||
case request.uri
|
||||
when '/', get_resource.chomp("/")
|
||||
@ -548,18 +556,17 @@ module Msf
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Overriding method. The module should override this.
|
||||
#
|
||||
# @param cli [Socket] Socket for the browser
|
||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||
# @param browser_info [Hash] The target profile
|
||||
#
|
||||
def on_request_exploit(cli, request, browser_info)
|
||||
raise NoMethodError, "Module must define its own on_request_exploit method"
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Converts an ERB-based exploit template into HTML, and sends to client
|
||||
#
|
||||
# @param cli [Socket] Socket for the browser
|
||||
@ -567,7 +574,6 @@ module Msf
|
||||
# then this is handled as an Array, with the first element
|
||||
# being the HTML, and the second element is the binding object.
|
||||
# @param headers [Hash] The custom HTTP headers to include in the response
|
||||
#
|
||||
def send_exploit_html(cli, template, headers={})
|
||||
html = ''
|
||||
if template.class == Array
|
||||
@ -578,13 +584,12 @@ module Msf
|
||||
send_response(cli, html, headers)
|
||||
end
|
||||
|
||||
#
|
||||
|
||||
# Generates a target-specific payload, should be called by the module
|
||||
#
|
||||
# @param cli [Socket] Socket for the browser
|
||||
# @param browser_info [Hash] The target profile
|
||||
# @return [String] The payload
|
||||
#
|
||||
def get_payload(cli, browser_info)
|
||||
arch = browser_info[:arch]
|
||||
platform = browser_info[:os_name]
|
||||
@ -618,9 +623,8 @@ module Msf
|
||||
|
||||
private
|
||||
|
||||
#
|
||||
|
||||
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
|
||||
#
|
||||
def send_not_found(cli)
|
||||
custom_404_url = get_custom_404_url
|
||||
if custom_404_url.blank?
|
||||
|
@ -66,8 +66,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
||||
:method => "LoadMovie",
|
||||
:activex => [
|
||||
{
|
||||
:clsid => '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
|
||||
:method => 'LoadMovie'
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
:flash => lambda { |ver| ver =~ /^11\./ }
|
||||
|
@ -51,8 +51,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
||||
:method => "LoadMovie",
|
||||
:activex => [
|
||||
{
|
||||
:clsid => '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
|
||||
:method => 'LoadMovie'
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
:flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' }
|
||||
|
@ -46,8 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{#{CLASSID}}",
|
||||
:method => "LoadMovie",
|
||||
:method => "LoadMovie"
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS_7,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
# Ohter versions are vulnerable but .235 is the one that works for me pretty well
|
||||
|
@ -55,8 +55,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
||||
:method => "LoadMovie",
|
||||
:method => "LoadMovie"
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
:flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' }
|
||||
|
@ -43,8 +43,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => /MSIE/i,
|
||||
:ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') },
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
|
||||
:method => "GetColor"
|
||||
}
|
||||
]
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
|
@ -45,8 +45,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
|
||||
:method => "ChooseFilePath",
|
||||
:method => "ChooseFilePath"
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
},
|
||||
'Targets' =>
|
||||
|
@ -73,8 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{19916E01-B44E-4E31-94A4-4696DF46157B}",
|
||||
:method => "requiredClaims",
|
||||
:method => "requiredClaims"
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS_XP
|
||||
},
|
||||
'Targets' =>
|
||||
|
@ -44,7 +44,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:activex => [
|
||||
{
|
||||
:clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}",
|
||||
:method => "SetText"
|
||||
}
|
||||
],
|
||||
:os_name => OperatingSystems::Match::WINDOWS,
|
||||
:ua_name => Msf::HttpClients::IE,
|
||||
:ua_ver => '10.0'
|
||||
|
@ -41,9 +41,9 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
||||
:ua_ver =>'8.0',
|
||||
:arch =>'x86',
|
||||
:office =>'null',
|
||||
:activex =>'true',
|
||||
:proxy =>false,
|
||||
:language =>'en-us',
|
||||
:activex => [ {:clsid=>'{D27CDB6E-AE6D-11cf-96B8-444553540000}', :method => 'LoadMovie'} ],
|
||||
:proxy => false,
|
||||
:language => 'en-us',
|
||||
:tried => true
|
||||
}
|
||||
end
|
||||
@ -65,6 +65,22 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
||||
end
|
||||
end
|
||||
|
||||
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" }
|
||||
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" }
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user