Skip to content

Commit 72a0909

Browse files
committed
Land rapid7#4992, @wchen-r7's support for multiple ActiveX controls on BrowserExploitServerMerge
2 parents 6546d30 + 3c4da5c commit 72a0909

10 files changed

+146
-93
lines changed

lib/msf/core/exploit/remote/browser_exploit_server.rb

Lines changed: 79 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,30 @@ class BESException < RuntimeError; end
4949

5050
# Requirements a browser module can define in either BrowserRequirements or in targets
5151
REQUIREMENT_KEY_SET = Set.new([
52-
:source, # Either 'script' or 'headers'
53-
:ua_name, # Example: MSIE
54-
:ua_ver, # Example: 8.0, 9.0
55-
:os_name, # Example: Windows 7, Linux
56-
:os_device, # Example: iPad, iPhone, etc
57-
:os_vendor, # Example: Microsoft, Ubuntu, Apple, etc
58-
:os_sp, # Example: SP2
59-
:language, # Example: en-us
60-
:arch, # Example: x86
61-
:proxy, # 'true' or 'false'
62-
:silverlight, # 'true' or 'false'
63-
:office, # Example: "2007", "2010"
64-
:java, # Example: 1.6, 1.6.0.0
65-
:clsid, # ActiveX clsid. Also requires the :method key
66-
:method, # ActiveX method. Also requires the :clsid key
67-
:mshtml_build, # mshtml build. Example: "65535"
68-
:flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE)
69-
:vuln_test # Example: "if(window.MyComponentIsInstalled)return true;"
52+
:source, # Return either 'script' or 'headers'
53+
:ua_name, # Example: Returns 'MSIE'
54+
:ua_ver, # Example: Returns '8.0', '9.0'
55+
:os_name, # Example: Returns 'Windows 7', 'Linux'
56+
:os_device, # Example: Returns 'iPad', 'iPhone', etc
57+
:os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
58+
:os_sp, # Example: Returns 'SP2'
59+
:language, # Example: Returns 'en-us'
60+
:arch, # Example: Returns 'x86'
61+
:proxy, # Returns 'true' or 'false'
62+
:silverlight, # Returns 'true' or 'false'
63+
:office, # Example: Returns "2007", "2010"
64+
:java, # Example: Return '1.6', or maybe '1.6.0.0' (depends)
65+
:mshtml_build, # mshtml build. Example: Returns "65535"
66+
:flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
67+
:vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;",
68+
# :activex is a special case.
69+
# When you set this requirement in your module, this is how it should be:
70+
# [{:clsid=>'String', :method=>'String'}]
71+
# Where each Hash is a test case
72+
# But when BES receives this information, the JavaScript will return this format:
73+
# "{CLSID}=>Method=>Boolean;"
74+
# Also see: #has_bad_activex?
75+
:activex
7076
])
7177

7278
def initialize(info={})
@@ -105,68 +111,61 @@ def setup
105111
super
106112
end
107113

108-
#
114+
109115
# Returns the custom 404 URL set by the user
110116
#
111117
# @return [String]
112-
#
113118
def get_custom_404_url
114119
datastore['Custom404'].to_s
115120
end
116121

117-
#
122+
118123
# Allows a block of code to access BES resources in a thread-safe fashion
119124
#
120125
# @param block [Proc] Block of code to sync
121-
#
122126
def sync(&block)
123127
(@mutex ||= Mutex.new).synchronize(&block)
124128
end
125129

126-
#
130+
127131
# Returns the resource (URI) to the module to allow access to on_request_exploit
128132
#
129133
# @return [String] URI to the exploit page
130-
#
131134
def get_module_resource
132135
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
133136
end
134137

135-
#
138+
136139
# Returns the absolute URL to the module's resource that points to on_request_exploit
137140
#
138141
# @return [String] absolute URI to the exploit page
139-
#
140142
def get_module_uri
141143
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
142144
end
143145

144-
#
146+
145147
# Returns the current target
146-
#
147148
def get_target
148149
@target
149150
end
150151

151-
#
152+
152153
# Returns a hash of recognizable requirements
153154
#
154155
# @param reqs [Hash] A hash that contains data for the requirements
155156
# @return [Hash] A hash of requirements
156-
#
157157
def extract_requirements(reqs)
158158
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
159159
# Make sure keys are always symbols
160160
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
161161
end
162162

163-
#
163+
164164
# Sets the target automatically based on what requirements are met.
165165
# If there's a possible matching target, it will also merge the requirements.
166166
# You can use the get_target() method to retrieve the most current target.
167167
#
168168
# @param profile [Hash] The profile to check
169-
#
170169
def try_set_target(profile)
171170
match_counts = []
172171
target_requirements = {}
@@ -195,30 +194,36 @@ def try_set_target(profile)
195194
end
196195
end
197196

198-
#
197+
198+
# Returns true if there's a bad ActiveX, otherwise false.
199+
# @param ax [String] The raw activex the JavaScript detection will return in this format:
200+
# "{CLSID}=>Method=>Boolean;"
201+
# @return [Boolean] True if there's a bad ActiveX, otherwise false
202+
def has_bad_activex?(ax)
203+
ax.split(';').each do |a|
204+
bool = a.split('=>')[2]
205+
if bool == 'false'
206+
return true
207+
end
208+
end
209+
210+
false
211+
end
212+
199213
# Returns an array of items that do not meet the requirements
200214
#
201215
# @param profile [Hash] The profile to check
202216
# @return [Array] An array of requirements not met
203-
#
204217
def get_bad_requirements(profile)
205218
bad_reqs = []
206219

207-
# At this point the check is already done.
208-
# If :activex is true, that means the clsid + method had a match,
209-
# if not, then false.
210-
if @requirements[:clsid] and @requirements[:method]
211-
@requirements[:activex] = 'true' # Script passes boolean as string
212-
end
213-
214220
@requirements.each do |k, v|
215-
# Special keys to ignore because the script registers this as [:activex] = true or false
216-
next if k == :clsid or k == :method
217-
218221
expected = k != :vuln_test ? v : 'true'
219222
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
220223

221-
if k == :vuln_test
224+
if k == :activex
225+
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
226+
elsif k == :vuln_test
222227
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
223228
elsif v.is_a? Regexp
224229
bad_reqs << k if profile[k.to_sym] !~ v
@@ -232,7 +237,6 @@ def get_bad_requirements(profile)
232237
bad_reqs
233238
end
234239

235-
#
236240
# Returns the target profile based on the tag. Each profile has the following structure:
237241
# 'cookie_name' =>
238242
# {
@@ -253,53 +257,49 @@ def get_bad_requirements(profile)
253257
#
254258
# If the source is 'script', the profile might have even more information about plugins:
255259
# 'office' : The version of Microsoft Office (IE only)
256-
# 'activex' : Whether a specific method is available from an ActiveX control (IE only)
260+
# 'activex' : Whether a specific set of clsid & method is available from an ActiveX control (IE only)
257261
# 'java' : The Java version
258262
# 'mshtml_build' : The MSHTML build version
259263
# 'flash' : The Flash version
260264
# 'silverlight' : The Silverlight version
261265
#
262266
# @param tag [String] Either a cookie or IP + User-Agent
263267
# @return [Hash] The profile found. If not found, returns nil
264-
#
265268
def get_profile(tag)
266269
sync do
267270
return @target_profiles[tag]
268271
end
269272
end
270273

271-
#
274+
272275
# Updates information for a specific profile
273276
#
274277
# @param target_profile [Hash] The profile to update
275278
# @param key [Symbol] The symbol to use for the hash
276279
# @param value [String] The value to assign
277-
#
278280
def update_profile(target_profile, key, value)
279281
sync do
280282
target_profile[key] = value
281283
end
282284
end
283285

284-
#
286+
285287
# Initializes a profile, if it did not previously exist
286288
#
287289
# @param tag [String] A unique string as a way to ID the profile
288-
#
289290
def init_profile(tag)
290291
sync do
291292
@target_profiles[tag] ||= {}
292293
end
293294
end
294295

295-
#
296+
296297
# Retrieves a tag.
297298
# First it obtains the tag from the browser's "Cookie" header.
298299
# If the header is empty (possible if the browser has cookies disabled),
299300
# then it will return a tag based on IP + the user-agent.
300301
#
301302
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
302-
#
303303
def retrieve_tag(cli, request)
304304
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
305305
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
@@ -317,13 +317,12 @@ def retrieve_tag(cli, request)
317317
tag
318318
end
319319

320-
#
320+
321321
# Registers target information to @target_profiles
322322
#
323323
# @param source [Symbol] Either :script, or :headers
324324
# @param cli [Socket] Socket for the browser
325325
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
326-
#
327326
def process_browser_info(source, cli, request)
328327
tag = retrieve_tag(cli, request)
329328
init_profile(tag)
@@ -361,27 +360,28 @@ def process_browser_info(source, cli, request)
361360
})
362361
end
363362

364-
#
363+
365364
# Checks if the target is running a proxy
366365
#
367366
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
368367
# @return [Boolean] True if found, otherwise false
369-
#
370368
def has_proxy?(request)
371369
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
372370
!proxy_header_set.empty?
373371
end
374372

375-
#
373+
376374
# Returns the code for client-side detection
377375
#
378376
# @param user_agent [String] The user-agent of the browser
379377
# @return [String] Returns the HTML for detection
380-
#
381378
def get_detection_html(user_agent)
379+
print_debug(user_agent)
382380
ua_info = fingerprint_user_agent(user_agent)
383381
os = ua_info[:os_name]
384382
client = ua_info[:ua_name]
383+
print_debug(os.inspect)
384+
print_debug(client.inspect)
385385

386386
code = ERB.new(%Q|
387387
<%= js_base64 %>
@@ -418,11 +418,20 @@ def get_detection_html(user_agent)
418418
d['office'] = ie_addons_detect.getMsOfficeVersion();
419419
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
420420
<%
421-
clsid = @requirements[:clsid]
422-
method = @requirements[:method]
423-
if clsid and method
421+
activex = @requirements[:activex]
422+
if activex
423+
activex.each do \|a\|
424+
clsid = a[:clsid]
425+
method = a[:method]
424426
%>
425-
d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
427+
var ax = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
428+
d['activex'] = "";
429+
if (ax == true) {
430+
d['activex'] += "<%=clsid%>=><%=method%>=>true;";
431+
} else {
432+
d['activex'] += "<%=clsid%>=><%=method%>=>false;";
433+
}
434+
<% end %>
426435
<% end %>
427436
<% end %>
428437
@@ -438,7 +447,7 @@ def get_detection_html(user_agent)
438447

439448
%Q|
440449
<script>
441-
#{js}
450+
#{code}
442451
</script>
443452
<noscript>
444453
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
@@ -462,12 +471,11 @@ def cookie_header(tag)
462471
cookie
463472
end
464473

465-
#
474+
466475
# Handles exploit stages.
467476
#
468477
# @param cli [Socket] Socket for the browser
469478
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
470-
#
471479
def on_request_uri(cli, request)
472480
case request.uri
473481
when '/', get_resource.chomp("/")
@@ -548,26 +556,24 @@ def on_request_uri(cli, request)
548556
end
549557
end
550558

551-
#
559+
552560
# Overriding method. The module should override this.
553561
#
554562
# @param cli [Socket] Socket for the browser
555563
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
556564
# @param browser_info [Hash] The target profile
557-
#
558565
def on_request_exploit(cli, request, browser_info)
559566
raise NoMethodError, "Module must define its own on_request_exploit method"
560567
end
561568

562-
#
569+
563570
# Converts an ERB-based exploit template into HTML, and sends to client
564571
#
565572
# @param cli [Socket] Socket for the browser
566573
# @param template [String] The ERB template. If you want to pass the binding object,
567574
# then this is handled as an Array, with the first element
568575
# being the HTML, and the second element is the binding object.
569576
# @param headers [Hash] The custom HTTP headers to include in the response
570-
#
571577
def send_exploit_html(cli, template, headers={})
572578
html = ''
573579
if template.class == Array
@@ -578,13 +584,12 @@ def send_exploit_html(cli, template, headers={})
578584
send_response(cli, html, headers)
579585
end
580586

581-
#
587+
582588
# Generates a target-specific payload, should be called by the module
583589
#
584590
# @param cli [Socket] Socket for the browser
585591
# @param browser_info [Hash] The target profile
586592
# @return [String] The payload
587-
#
588593
def get_payload(cli, browser_info)
589594
arch = browser_info[:arch]
590595
platform = browser_info[:os_name]
@@ -618,9 +623,8 @@ def js_vuln_test
618623

619624
private
620625

621-
#
626+
622627
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
623-
#
624628
def send_not_found(cli)
625629
custom_404_url = get_custom_404_url
626630
if custom_404_url.blank?

0 commit comments

Comments
 (0)