1
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:
jvazquez-r7 2015-03-25 13:30:36 -05:00
commit 72a0909e9b
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
10 changed files with 146 additions and 93 deletions

View File

@ -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?

View File

@ -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\./ }

View File

@ -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' }

View File

@ -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

View File

@ -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' }

View File

@ -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' =>
{

View File

@ -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' =>

View File

@ -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' =>

View File

@ -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'

View File

@ -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)