arachni 1.1 → 1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +159 -0
- data/LICENSE.md +126 -196
- data/README.md +32 -24
- data/arachni.gemspec +7 -7
- data/components/checks/active/code_injection_timing.rb +3 -3
- data/components/checks/active/csrf.rb +2 -2
- data/components/checks/active/file_inclusion.rb +6 -7
- data/components/checks/active/os_cmd_injection.rb +3 -3
- data/components/checks/active/path_traversal.rb +7 -7
- data/components/checks/active/response_splitting.rb +9 -4
- data/components/checks/active/session_fixation.rb +7 -3
- data/components/checks/active/source_code_disclosure.rb +5 -5
- data/components/checks/active/unvalidated_redirect.rb +12 -3
- data/components/checks/active/unvalidated_redirect_dom.rb +3 -3
- data/components/checks/active/xss.rb +23 -10
- data/components/checks/active/xss_dom_inputs.rb +113 -11
- data/components/checks/active/xxe.rb +3 -3
- data/components/checks/passive/backdoors.rb +6 -5
- data/components/checks/passive/backup_directories.rb +6 -6
- data/components/checks/passive/backup_files.rb +6 -6
- data/components/checks/passive/common_admin_interfaces.rb +58 -0
- data/components/checks/passive/common_admin_interfaces/admin-panels.txt +49 -0
- data/components/checks/passive/common_directories/directories.txt +0 -16
- data/components/checks/passive/common_files.rb +6 -5
- data/components/checks/passive/common_files/filenames.txt +0 -2
- data/components/checks/passive/directory_listing.rb +6 -6
- data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +3 -3
- data/components/checks/passive/grep/hsts.rb +6 -3
- data/components/checks/passive/grep/http_only_cookies.rb +3 -3
- data/components/checks/passive/grep/insecure_cookies.rb +2 -2
- data/components/checks/passive/grep/insecure_cors_policy.rb +6 -4
- data/components/checks/passive/grep/x_frame_options.rb +6 -4
- data/components/checks/passive/htaccess_limit.rb +6 -2
- data/components/checks/passive/http_put.rb +8 -4
- data/components/checks/passive/interesting_responses.rb +3 -2
- data/components/checks/passive/localstart_asp.rb +6 -2
- data/components/checks/passive/origin_spoof_access_restriction_bypass.rb +5 -1
- data/components/checks/passive/xst.rb +6 -2
- data/components/fingerprinters/frameworks/aspx_mvc.rb +43 -0
- data/components/fingerprinters/frameworks/cakephp.rb +28 -0
- data/components/fingerprinters/frameworks/cherrypy.rb +31 -0
- data/components/fingerprinters/frameworks/django.rb +33 -0
- data/components/fingerprinters/frameworks/jsf.rb +30 -0
- data/components/fingerprinters/frameworks/rack.rb +5 -7
- data/components/fingerprinters/frameworks/rails.rb +43 -0
- data/components/fingerprinters/languages/aspx.rb +11 -11
- data/components/fingerprinters/languages/{jsp.rb → java.rb} +11 -7
- data/components/fingerprinters/languages/php.rb +6 -6
- data/components/fingerprinters/languages/python.rb +14 -6
- data/components/fingerprinters/languages/ruby.rb +3 -5
- data/components/fingerprinters/servers/apache.rb +5 -4
- data/components/fingerprinters/servers/gunicorn.rb +33 -0
- data/components/fingerprinters/servers/jetty.rb +1 -1
- data/components/fingerprinters/servers/tomcat.rb +11 -4
- data/components/path_extractors/anchors.rb +5 -12
- data/components/path_extractors/areas.rb +5 -13
- data/components/path_extractors/comments.rb +5 -3
- data/components/path_extractors/data_url.rb +21 -0
- data/components/path_extractors/forms.rb +5 -13
- data/components/path_extractors/frames.rb +6 -13
- data/components/path_extractors/generic.rb +3 -12
- data/components/path_extractors/links.rb +5 -13
- data/components/path_extractors/meta_refresh.rb +5 -13
- data/components/path_extractors/scripts.rb +8 -14
- data/components/plugins/autologin.rb +17 -5
- data/components/plugins/defaults/meta/remedies/discovery.rb +11 -29
- data/components/plugins/login_script.rb +40 -10
- data/components/plugins/metrics.rb +235 -0
- data/components/plugins/proxy.rb +21 -4
- data/components/plugins/proxy/panel/page_accordion.html.erb +34 -2
- data/components/plugins/restrict_to_dom_state.rb +70 -0
- data/components/plugins/vector_feed.rb +38 -9
- data/components/reporters/plugin_formatters/html/metrics.rb +290 -0
- data/components/reporters/plugin_formatters/stdout/metrics.rb +80 -0
- data/components/reporters/plugin_formatters/xml/metrics.rb +29 -0
- data/components/reporters/stdout.rb +4 -2
- data/components/reporters/xml.rb +4 -4
- data/components/reporters/xml/schema.xsd +95 -0
- data/lib/arachni.rb +2 -0
- data/lib/arachni/browser.rb +132 -77
- data/lib/arachni/browser/javascript.rb +173 -45
- data/lib/arachni/browser/javascript/scripts/dom_monitor.js +81 -6
- data/lib/arachni/browser/javascript/scripts/taint_tracer.js +31 -3
- data/lib/arachni/browser_cluster.rb +41 -15
- data/lib/arachni/browser_cluster/job.rb +4 -0
- data/lib/arachni/browser_cluster/jobs/resource_exploration.rb +0 -9
- data/lib/arachni/browser_cluster/worker.rb +8 -5
- data/lib/arachni/check/auditor.rb +20 -8
- data/lib/arachni/check/base.rb +38 -6
- data/lib/arachni/element/base.rb +18 -1
- data/lib/arachni/element/capabilities/analyzable/differential.rb +0 -1
- data/lib/arachni/element/capabilities/analyzable/taint.rb +40 -10
- data/lib/arachni/element/capabilities/analyzable/timeout.rb +27 -23
- data/lib/arachni/element/capabilities/auditable/dom.rb +22 -0
- data/lib/arachni/element/capabilities/inputtable.rb +6 -2
- data/lib/arachni/element/capabilities/submittable.rb +1 -1
- data/lib/arachni/element/cookie.rb +37 -23
- data/lib/arachni/element/cookie/capabilities/mutable.rb +6 -6
- data/lib/arachni/element/cookie/dom.rb +0 -8
- data/lib/arachni/element/form.rb +28 -14
- data/lib/arachni/element/form/capabilities/auditable.rb +2 -2
- data/lib/arachni/element/form/capabilities/mutable.rb +5 -5
- data/lib/arachni/element/form/dom.rb +0 -8
- data/lib/arachni/element/generic_dom.rb +1 -1
- data/lib/arachni/element/json.rb +2 -1
- data/lib/arachni/element/json/capabilities/inputtable.rb +6 -6
- data/lib/arachni/element/json/capabilities/mutable.rb +1 -1
- data/lib/arachni/element/link.rb +13 -16
- data/lib/arachni/element/link/dom.rb +1 -14
- data/lib/arachni/element/link_template.rb +3 -2
- data/lib/arachni/element/link_template/dom.rb +0 -16
- data/lib/arachni/element/server.rb +51 -9
- data/lib/arachni/element/xml.rb +1 -0
- data/lib/arachni/ethon/easy.rb +4 -1
- data/lib/arachni/framework/parts/audit.rb +26 -77
- data/lib/arachni/framework/parts/browser.rb +50 -55
- data/lib/arachni/framework/parts/check.rb +4 -3
- data/lib/arachni/framework/parts/data.rb +41 -6
- data/lib/arachni/framework/parts/state.rb +16 -7
- data/lib/arachni/http/client.rb +66 -38
- data/lib/arachni/http/client/dynamic_404_handler.rb +46 -14
- data/lib/arachni/http/headers.rb +22 -10
- data/lib/arachni/http/proxy_server.rb +67 -22
- data/lib/arachni/http/proxy_server/ssl-interceptor-cacert.pem +34 -0
- data/lib/arachni/http/proxy_server/ssl-interceptor-cakey.pem +51 -0
- data/lib/arachni/http/request.rb +71 -18
- data/lib/arachni/issue.rb +17 -3
- data/lib/arachni/option_groups/browser_cluster.rb +34 -1
- data/lib/arachni/option_groups/http.rb +1 -1
- data/lib/arachni/page.rb +26 -13
- data/lib/arachni/page/dom/transition.rb +2 -2
- data/lib/arachni/parser.rb +28 -11
- data/lib/arachni/platform/fingerprinter.rb +5 -0
- data/lib/arachni/platform/manager.rb +65 -32
- data/lib/arachni/plugin/base.rb +8 -0
- data/lib/arachni/processes/instances.rb +25 -11
- data/lib/arachni/reporter/manager.rb +2 -2
- data/lib/arachni/rpc/client/instance.rb +4 -0
- data/lib/arachni/rpc/server/framework/master.rb +3 -3
- data/lib/arachni/rpc/server/framework/multi_instance.rb +0 -8
- data/lib/arachni/rpc/server/instance.rb +2 -1
- data/lib/arachni/ruby/array.rb +5 -0
- data/lib/arachni/ruby/hash.rb +5 -0
- data/lib/arachni/ruby/string.rb +2 -3
- data/lib/arachni/session.rb +32 -6
- data/lib/arachni/state/framework.rb +6 -2
- data/lib/arachni/support/cache.rb +1 -0
- data/lib/arachni/support/cache/base.rb +12 -8
- data/lib/arachni/support/cache/least_recently_pushed.rb +29 -0
- data/lib/arachni/support/cache/least_recently_used.rb +5 -8
- data/lib/arachni/support/cache/preference.rb +1 -1
- data/lib/arachni/support/cache/random_replacement.rb +1 -25
- data/lib/arachni/support/database/queue.rb +21 -8
- data/lib/arachni/support/lookup/base.rb +7 -1
- data/lib/arachni/support/mixins/observable.rb +3 -1
- data/lib/arachni/support/profiler.rb +51 -10
- data/lib/arachni/support/signature.rb +11 -2
- data/lib/arachni/trainer.rb +8 -2
- data/lib/arachni/uri.rb +28 -25
- data/lib/arachni/uri/scope.rb +1 -1
- data/lib/arachni/utilities.rb +8 -0
- data/lib/arachni/watir/element.rb +1 -1
- data/lib/version +1 -1
- data/spec/arachni/browser/javascript/dom_monitor_spec.rb +388 -53
- data/spec/arachni/browser/javascript/taint_tracer_spec.rb +41 -0
- data/spec/arachni/browser/javascript_spec.rb +235 -61
- data/spec/arachni/browser_cluster/jobs/resource_exploration_spec.rb +0 -9
- data/spec/arachni/browser_cluster_spec.rb +58 -10
- data/spec/arachni/browser_spec.rb +170 -26
- data/spec/arachni/check/auditor_spec.rb +22 -3
- data/spec/arachni/check/base_spec.rb +84 -0
- data/spec/arachni/element/body_spec.rb +1 -1
- data/spec/arachni/element/capabilities/analyzable/taint_spec.rb +3 -3
- data/spec/arachni/element/capabilities/analyzable/timeout_spec.rb +1 -1
- data/spec/arachni/element/cookie/dom_spec.rb +0 -9
- data/spec/arachni/element/cookie_spec.rb +85 -0
- data/spec/arachni/element/form/dom_spec.rb +0 -9
- data/spec/arachni/element/form_spec.rb +46 -3
- data/spec/arachni/element/json_spec.rb +20 -0
- data/spec/arachni/element/link/dom_spec.rb +0 -9
- data/spec/arachni/element/link_spec.rb +40 -15
- data/spec/arachni/element/link_template/dom_spec.rb +0 -8
- data/spec/arachni/element/link_template_spec.rb +2 -6
- data/spec/arachni/element/server_spec.rb +94 -8
- data/spec/arachni/element/xml_spec.rb +20 -0
- data/spec/arachni/framework/parts/audit_spec.rb +12 -14
- data/spec/arachni/framework/parts/browser_spec.rb +0 -171
- data/spec/arachni/framework/parts/platform_spec.rb +14 -8
- data/spec/arachni/framework/parts/report_spec.rb +1 -1
- data/spec/arachni/framework/parts/state_spec.rb +0 -9
- data/spec/arachni/http/client/dynamic_404_handlers_spec.rb +19 -0
- data/spec/arachni/http/client_spec.rb +169 -42
- data/spec/arachni/http/headers_spec.rb +18 -0
- data/spec/arachni/http/request_spec.rb +23 -0
- data/spec/arachni/issue_spec.rb +17 -6
- data/spec/arachni/page_spec.rb +22 -2
- data/spec/arachni/parser_spec.rb +5 -0
- data/spec/arachni/platform/manager_spec.rb +57 -25
- data/spec/arachni/reporter/manager_spec.rb +26 -0
- data/spec/arachni/rpc/server/active_options_spec.rb +9 -4
- data/spec/arachni/state/framework_spec.rb +2 -8
- data/spec/arachni/support/cache/least_recently_pushed_spec.rb +90 -0
- data/spec/arachni/support/cache/least_recently_used_spec.rb +5 -13
- data/spec/arachni/support/database/queue_spec.rb +7 -0
- data/spec/arachni/support/mixins/observable_spec.rb +15 -1
- data/spec/arachni/trainer_spec.rb +2 -2
- data/spec/components/checks/active/code_injection_timing_spec.rb +1 -1
- data/spec/components/checks/active/file_inclusion_spec.rb +6 -6
- data/spec/components/checks/active/path_traversal_spec.rb +2 -2
- data/spec/components/checks/active/source_code_disclosure_spec.rb +2 -2
- data/spec/components/checks/active/unvalidated_redirect_spec.rb +6 -6
- data/spec/components/checks/active/xss_dom_inputs_spec.rb +3 -5
- data/spec/components/checks/active/xss_dom_script_context_spec.rb +1 -1
- data/spec/components/checks/active/xss_spec.rb +5 -5
- data/spec/components/checks/passive/common_admin_interfaces_spec.rb +15 -0
- data/spec/components/checks/passive/interesting_responses_spec.rb +14 -1
- data/spec/components/fingerprinters/frameworks/aspx_mvc_spec.rb +31 -0
- data/spec/components/fingerprinters/frameworks/cakephp_spec.rb +22 -0
- data/spec/components/fingerprinters/frameworks/cherrypy_spec.rb +28 -0
- data/spec/components/fingerprinters/frameworks/django_spec.rb +37 -0
- data/spec/components/fingerprinters/frameworks/jsf_spec.rb +27 -0
- data/spec/components/fingerprinters/frameworks/rack_spec.rb +11 -14
- data/spec/components/fingerprinters/frameworks/rails_spec.rb +53 -0
- data/spec/components/fingerprinters/languages/asp_spec.rb +7 -9
- data/spec/components/fingerprinters/languages/aspx_spec.rb +10 -24
- data/spec/components/fingerprinters/languages/java_spec.rb +88 -0
- data/spec/components/fingerprinters/languages/php_spec.rb +19 -12
- data/spec/components/fingerprinters/languages/python_spec.rb +22 -9
- data/spec/components/fingerprinters/languages/ruby.rb +6 -4
- data/spec/components/fingerprinters/os/bsd_spec.rb +6 -4
- data/spec/components/fingerprinters/os/linux_spec.rb +6 -4
- data/spec/components/fingerprinters/os/solaris_spec.rb +6 -4
- data/spec/components/fingerprinters/os/unix_spec.rb +6 -4
- data/spec/components/fingerprinters/os/windows_spec.rb +6 -4
- data/spec/components/fingerprinters/servers/apache_spec.rb +15 -4
- data/spec/components/fingerprinters/servers/gunicorn_spec.rb +28 -0
- data/spec/components/fingerprinters/servers/iis_spec.rb +6 -6
- data/spec/components/fingerprinters/servers/jetty_spec.rb +6 -6
- data/spec/components/fingerprinters/servers/nginx_spec.rb +6 -4
- data/spec/components/fingerprinters/servers/tomcat_spec.rb +15 -6
- data/spec/components/path_extractors/data_url_spec.rb +19 -0
- data/spec/components/plugins/autologin_spec.rb +23 -0
- data/spec/components/plugins/login_script_spec.rb +112 -24
- data/spec/components/plugins/restrict_to_dom_state_spec.rb +16 -0
- data/spec/components/plugins/vector_feed_spec.rb +39 -1
- data/spec/support/factories/page/dom.rb +9 -4
- data/spec/support/factories/page/dom/transition.rb +31 -9
- data/spec/support/factories/scan_report.rb +8 -6
- data/spec/support/fixtures/empty/placeholder +0 -0
- data/spec/support/fixtures/report.afr +0 -0
- data/spec/support/fixtures/reporters/manager_spec/error.rb +18 -0
- data/spec/support/servers/arachni/browser.rb +117 -11
- data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +148 -4
- data/spec/support/servers/arachni/check/auditor.rb +4 -0
- data/spec/support/servers/arachni/element/cookie/cookie_dom.rb +1 -1
- data/spec/support/servers/arachni/http/client.rb +5 -0
- data/spec/support/servers/arachni/http/client/dynamic_404_handler.rb +13 -0
- data/spec/support/servers/checks/active/code_injection_timing.rb +1 -1
- data/spec/support/servers/checks/active/file_inclusion.rb +2 -2
- data/spec/support/servers/checks/active/path_traversal.rb +2 -2
- data/spec/support/servers/checks/active/source_code_disclosure.rb +40 -33
- data/spec/support/servers/checks/active/trainer_check.rb +9 -10
- data/spec/support/servers/checks/active/unvalidated_redirect_dom.rb +7 -4
- data/spec/support/servers/checks/active/xss.rb +35 -0
- data/spec/support/servers/checks/active/xss_dom.rb +1 -1
- data/spec/support/servers/checks/active/xss_dom_inputs.rb +24 -0
- data/spec/support/servers/checks/active/xss_dom_script_context.rb +1 -1
- data/spec/support/servers/checks/passive/common_admin_interfaces.rb +6 -0
- data/spec/support/servers/plugins/autologin.rb +9 -0
- data/spec/support/servers/plugins/restrict_to_dom_state.rb +4 -0
- data/spec/support/shared/element/base.rb +42 -0
- data/spec/support/shared/element/capabilities/auditable.rb +4 -4
- data/spec/support/shared/element/capabilities/auditable/dom.rb +26 -0
- data/spec/support/shared/element/capabilities/inputtable.rb +16 -11
- data/spec/support/shared/element/capabilities/submitable.rb +7 -2
- data/spec/support/shared/fingerprinter.rb +8 -0
- data/spec/support/shared/path_extractor.rb +1 -1
- data/ui/cli/framework.rb +3 -3
- data/ui/cli/framework/option_parser.rb +9 -0
- data/ui/cli/output.rb +9 -0
- data/ui/cli/reporter.rb +5 -2
- data/ui/cli/utilities.rb +4 -2
- metadata +76 -17
- data/lib/arachni/http/proxy_server/ssl-interceptor-cert.pem +0 -34
- data/lib/arachni/http/proxy_server/ssl-interceptor-pkey.pem +0 -51
- data/spec/components/fingerprinters/languages/jsp_spec.rb +0 -56
|
@@ -21,6 +21,12 @@ class Javascript
|
|
|
21
21
|
require_relative 'javascript/taint_tracer'
|
|
22
22
|
require_relative 'javascript/dom_monitor'
|
|
23
23
|
|
|
24
|
+
CACHE = {
|
|
25
|
+
select_event_attributes: Support::Cache::LeastRecentlyPushed.new( 1_000 )
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
TOKEN = 'arachni_js_namespace'
|
|
29
|
+
|
|
24
30
|
# @return [String]
|
|
25
31
|
# URL to use when requesting our custom JS scripts.
|
|
26
32
|
SCRIPT_BASE_URL = 'http://javascript.browser.arachni/'
|
|
@@ -33,6 +39,8 @@ class Javascript
|
|
|
33
39
|
h.merge!( path => IO.read(path) )
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
HTML_IDENTIFIERS = ['<!doctype html', '<html', '<head', '<body', '<title', '<script']
|
|
43
|
+
|
|
36
44
|
NO_EVENTS_FOR_ELEMENTS = Set.new([
|
|
37
45
|
:base, :bdo, :br, :head, :html, :iframe, :meta, :param, :script, :style,
|
|
38
46
|
:title, :link
|
|
@@ -126,15 +134,29 @@ class Javascript
|
|
|
126
134
|
GLOBAL_EVENTS | EVENTS_PER_ELEMENT.values.flatten.uniq
|
|
127
135
|
end
|
|
128
136
|
|
|
137
|
+
def self.event_whitelist
|
|
138
|
+
@event_whitelist ||= Set.new( events.flatten.map(&:to_s) )
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# @param [Symbol] element
|
|
142
|
+
#
|
|
143
|
+
# @return [Array<Symbol>]
|
|
144
|
+
# Events for `element`.
|
|
145
|
+
def self.events_for( element )
|
|
146
|
+
GLOBAL_EVENTS | EVENTS_PER_ELEMENT[element.to_sym]
|
|
147
|
+
end
|
|
148
|
+
|
|
129
149
|
# @param [Hash] attributes
|
|
130
150
|
# Element attributes.
|
|
131
151
|
#
|
|
132
152
|
# @return [Hash]
|
|
133
153
|
# `attributes` that include {.events}.
|
|
134
154
|
def self.select_event_attributes( attributes = {} )
|
|
135
|
-
attributes
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
CACHE[:select_event_attributes][attributes] ||=
|
|
156
|
+
attributes.inject({}) do |h, (event, handler)|
|
|
157
|
+
next h if !event_whitelist.include?( event.to_s )
|
|
158
|
+
h.merge!( event.to_sym => handler )
|
|
159
|
+
end
|
|
138
160
|
end
|
|
139
161
|
|
|
140
162
|
# @param [Browser] browser
|
|
@@ -169,7 +191,7 @@ class Javascript
|
|
|
169
191
|
# @return [String]
|
|
170
192
|
# Token used to namespace the injected JS code and avoid clashes.
|
|
171
193
|
def token
|
|
172
|
-
@token ||=
|
|
194
|
+
@token ||= TOKEN
|
|
173
195
|
end
|
|
174
196
|
|
|
175
197
|
# @return [String]
|
|
@@ -268,6 +290,8 @@ class Javascript
|
|
|
268
290
|
dom_monitor.digest
|
|
269
291
|
end
|
|
270
292
|
|
|
293
|
+
# @note Will not include custom events.
|
|
294
|
+
#
|
|
271
295
|
# @return [Array<Hash>]
|
|
272
296
|
# Information about all DOM elements, including any registered event listeners.
|
|
273
297
|
def dom_elements_with_events
|
|
@@ -277,10 +301,15 @@ class Javascript
|
|
|
277
301
|
next if NO_EVENTS_FOR_ELEMENTS.include? element['tag_name'].to_sym
|
|
278
302
|
|
|
279
303
|
attributes = element['attributes']
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
304
|
+
|
|
305
|
+
element['events'] = (element['events'].map do |event, fn|
|
|
306
|
+
next if !(self.class.event_whitelist.include?( event ) ||
|
|
307
|
+
self.class.event_whitelist.include?( "on#{event}" ))
|
|
308
|
+
|
|
309
|
+
[event.to_sym, fn]
|
|
310
|
+
end.compact)
|
|
311
|
+
|
|
312
|
+
element['events'] |= self.class.select_event_attributes( attributes ).to_a
|
|
284
313
|
|
|
285
314
|
element
|
|
286
315
|
end.compact
|
|
@@ -327,63 +356,162 @@ class Javascript
|
|
|
327
356
|
# @param [HTTP::Response] response
|
|
328
357
|
# Installs our custom JS interfaces in the given `response`.
|
|
329
358
|
#
|
|
330
|
-
# @return [Bool]
|
|
331
|
-
# `true` if injection was performed, `false` otherwise (in case our code
|
|
332
|
-
# is already present).
|
|
333
|
-
#
|
|
334
359
|
# @see SCRIPT_BASE_URL
|
|
335
360
|
# @see SCRIPT_LIBRARY
|
|
336
361
|
def inject( response )
|
|
337
|
-
|
|
362
|
+
# Don't intercept our own stuff!
|
|
363
|
+
return if response.url.start_with?( SCRIPT_BASE_URL )
|
|
364
|
+
|
|
365
|
+
# If it's a JS file, update our JS interfaces in case it has stuff that
|
|
366
|
+
# can be tracked.
|
|
367
|
+
#
|
|
368
|
+
# This is necessary because new files can be required dynamically.
|
|
369
|
+
if javascript?( response )
|
|
370
|
+
|
|
371
|
+
response.body = <<-EOCODE
|
|
372
|
+
#{js_comment}
|
|
373
|
+
#{taint_tracer.stub.function( :update_tracers )};
|
|
374
|
+
#{dom_monitor.stub.function( :update_trackers )};
|
|
375
|
+
|
|
376
|
+
#{response.body};
|
|
377
|
+
EOCODE
|
|
378
|
+
|
|
379
|
+
# Already has the JS initializer, so it's an HTML response; just update
|
|
380
|
+
# taints and custom code.
|
|
381
|
+
elsif has_js_initializer?( response )
|
|
382
|
+
|
|
383
|
+
body = response.body.dup
|
|
384
|
+
|
|
385
|
+
update_taints( body )
|
|
386
|
+
update_custom_code( body )
|
|
387
|
+
|
|
388
|
+
response.body = body
|
|
389
|
+
|
|
390
|
+
elsif html?( response )
|
|
391
|
+
body = response.body.dup
|
|
392
|
+
|
|
393
|
+
# Perform an update before each script.
|
|
394
|
+
body.gsub!(
|
|
395
|
+
/<script.*?>/i,
|
|
396
|
+
"\\0\n
|
|
397
|
+
#{js_comment}
|
|
398
|
+
#{@taint_tracer.stub.function( :update_tracers )};
|
|
399
|
+
#{@dom_monitor.stub.function( :update_trackers )};\n\n"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Perform an update after each script.
|
|
403
|
+
body.gsub!(
|
|
404
|
+
/<\/script>/i,
|
|
405
|
+
"\\0\n<script type=\"text/javascript\">" <<
|
|
406
|
+
"#{@taint_tracer.stub.function( :update_tracers )};" <<
|
|
407
|
+
"#{@dom_monitor.stub.function( :update_trackers )};" <<
|
|
408
|
+
"</script> #{html_comment}\n"
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# Include and initialize our JS interfaces.
|
|
412
|
+
response.body = <<-EOHTML
|
|
413
|
+
<script src="#{script_url_for( :taint_tracer )}"></script> #{html_comment}
|
|
414
|
+
<script src="#{script_url_for( :dom_monitor )}"></script> #{html_comment}
|
|
415
|
+
<script>
|
|
416
|
+
#{wrapped_taint_tracer_initializer}
|
|
417
|
+
#{js_initialization_signal};
|
|
418
|
+
|
|
419
|
+
#{wrapped_custom_code}
|
|
420
|
+
</script> #{html_comment}
|
|
421
|
+
|
|
422
|
+
#{body}
|
|
423
|
+
EOHTML
|
|
424
|
+
end
|
|
338
425
|
|
|
339
|
-
|
|
426
|
+
response.headers['content-length'] = response.body.size
|
|
427
|
+
|
|
428
|
+
true
|
|
429
|
+
end
|
|
340
430
|
|
|
341
|
-
|
|
342
|
-
|
|
431
|
+
def javascript?( response )
|
|
432
|
+
response.headers.content_type.to_s.downcase.include?( 'javascript' )
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def html?( response )
|
|
436
|
+
return false if response.body.empty?
|
|
437
|
+
|
|
438
|
+
# We only care about HTML.
|
|
439
|
+
return false if !response.headers.content_type.to_s.downcase.start_with?( 'text/html' )
|
|
440
|
+
|
|
441
|
+
# Let's check that the response at least looks like it contains HTML
|
|
442
|
+
# code of interest.
|
|
443
|
+
body = response.body.downcase
|
|
444
|
+
return false if !HTML_IDENTIFIERS.find { |tag| body.include? tag.downcase }
|
|
445
|
+
|
|
446
|
+
# The last check isn't fool-proof, so don't do it when loading the page
|
|
447
|
+
# for the first time, but only when the page loads stuff via AJAX and whatnot.
|
|
343
448
|
#
|
|
344
|
-
#
|
|
345
|
-
|
|
346
|
-
body.gsub!(
|
|
347
|
-
/<script(.*?)>/i,
|
|
348
|
-
"\\0\n#{@taint_tracer.stub.function( :update_tracers )}; // Injected by #{self.class}\n"
|
|
349
|
-
)
|
|
449
|
+
# Well, we can be pretty sure that the root page will be HTML anyways.
|
|
450
|
+
return true if @browser.last_url == response.url
|
|
350
451
|
|
|
351
|
-
#
|
|
352
|
-
#
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
452
|
+
# Finally, verify that we're really working with markup (hopefully HTML)
|
|
453
|
+
# and that the previous checks weren't just flukes matching some other
|
|
454
|
+
# kind of document.
|
|
455
|
+
#
|
|
456
|
+
# For example, it may have been JSON with the wrong content-type that
|
|
457
|
+
# includes HTML -- it happens.
|
|
458
|
+
begin
|
|
459
|
+
return false if Nokogiri::XML( response.body ).children.empty?
|
|
460
|
+
rescue => e
|
|
461
|
+
print_debug "Does not look like HTML: #{response.url}"
|
|
462
|
+
print_debug "\n#{response.body}"
|
|
463
|
+
print_debug_exception e
|
|
464
|
+
return false
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
true
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private
|
|
471
|
+
|
|
472
|
+
def js_comment
|
|
473
|
+
"// Injected by #{self.class}"
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def html_comment
|
|
477
|
+
"<!-- Injected by #{self.class} -->"
|
|
478
|
+
end
|
|
358
479
|
|
|
480
|
+
def taints
|
|
359
481
|
taints = [@taint]
|
|
482
|
+
|
|
360
483
|
# Include cookie names and values in the trace so that the browser will
|
|
361
484
|
# be able to infer if they're being used, to avoid unnecessary audits.
|
|
362
485
|
if Options.audit.cookie_doms?
|
|
363
486
|
taints |= HTTP::Client.cookies.map { |c| c.inputs.to_a }.flatten
|
|
364
487
|
end
|
|
365
|
-
taints = taints.flatten.reject { |v| v.to_s.empty? }
|
|
366
488
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
<script> #{@taint_tracer.stub.function( :initialize, taints )} </script> <!-- Script injected by #{self.class} -->
|
|
370
|
-
|
|
371
|
-
<script src="#{script_url_for( :dom_monitor )}"></script> <!-- Script injected by #{self.class} -->
|
|
372
|
-
<script>
|
|
373
|
-
#{@dom_monitor.stub.function( :initialize )};
|
|
374
|
-
#{js_initialization_signal};
|
|
489
|
+
taints.flatten.reject { |v| v.to_s.empty? }
|
|
490
|
+
end
|
|
375
491
|
|
|
376
|
-
|
|
377
|
-
|
|
492
|
+
def update_taints( body )
|
|
493
|
+
body.gsub!(
|
|
494
|
+
/\/\* #{token}_initialize_start \*\/(.*)\/\* #{token}_initialize_stop \*\//,
|
|
495
|
+
wrapped_taint_tracer_initializer
|
|
496
|
+
)
|
|
497
|
+
end
|
|
378
498
|
|
|
379
|
-
|
|
380
|
-
|
|
499
|
+
def update_custom_code( body )
|
|
500
|
+
body.gsub!(
|
|
501
|
+
/\/\* #{token}_code_start \*\/(.*)\/\* #{token}_code_stop \*\//,
|
|
502
|
+
wrapped_custom_code
|
|
503
|
+
)
|
|
504
|
+
end
|
|
381
505
|
|
|
382
|
-
|
|
383
|
-
|
|
506
|
+
def wrapped_taint_tracer_initializer
|
|
507
|
+
"/* #{token}_initialize_start */" <<
|
|
508
|
+
"#{@taint_tracer.stub.function( :initialize, taints )}" <<
|
|
509
|
+
"/* #{token}_initialize_stop */"
|
|
384
510
|
end
|
|
385
511
|
|
|
386
|
-
|
|
512
|
+
def wrapped_custom_code
|
|
513
|
+
"/* #{token}_code_start */#{custom_code}/* #{token}_code_stop */"
|
|
514
|
+
end
|
|
387
515
|
|
|
388
516
|
def js_initialization_signal
|
|
389
517
|
"window._#{token} = true"
|
|
@@ -37,6 +37,10 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
37
37
|
_tokenDOMMonitor.initialized = true
|
|
38
38
|
},
|
|
39
39
|
|
|
40
|
+
update_trackers: function () {
|
|
41
|
+
_tokenDOMMonitor.track_jQuery_delegated_events();
|
|
42
|
+
},
|
|
43
|
+
|
|
40
44
|
// Returns information about all DOM elements, their attributes and registered
|
|
41
45
|
// events.
|
|
42
46
|
elements_with_events: function () {
|
|
@@ -50,9 +54,11 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
50
54
|
// Skip invisible elements.
|
|
51
55
|
if( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) continue;
|
|
52
56
|
|
|
57
|
+
_tokenDOMMonitor.apply_jQuery_delegated_events( element );
|
|
58
|
+
|
|
53
59
|
var e = {
|
|
54
60
|
tag_name: element.tagName.toLowerCase(),
|
|
55
|
-
events: element.
|
|
61
|
+
events: element._arachni_events || [],
|
|
56
62
|
attributes: {}
|
|
57
63
|
};
|
|
58
64
|
|
|
@@ -122,6 +128,54 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
122
128
|
};
|
|
123
129
|
},
|
|
124
130
|
|
|
131
|
+
track_jQuery_delegated_events: function () {
|
|
132
|
+
if( _tokenDOMMonitor.tracked_jQuery_delegated_events || !window.jQuery ) return;
|
|
133
|
+
_tokenDOMMonitor.tracked_jQuery_delegated_events = true;
|
|
134
|
+
|
|
135
|
+
var original = window.jQuery.fn.on;
|
|
136
|
+
|
|
137
|
+
// We only care for calls with selectors, as any other will attach the
|
|
138
|
+
// events to the DOM element immediately and thus be captured by the
|
|
139
|
+
// addEventListener tracker.
|
|
140
|
+
window.jQuery.fn.on = function ( types, selector, data, fn, one ) {
|
|
141
|
+
|
|
142
|
+
// Types can be a map of types/handlers, in that case just run
|
|
143
|
+
// the original as it'll act recursively and pass itself (which is
|
|
144
|
+
// this override, really) each type.
|
|
145
|
+
if ( typeof types === "object" ) {
|
|
146
|
+
return original.apply( this, [].slice.call( arguments ) );
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if ( data == null && fn == null ) {
|
|
150
|
+
// ( types, fn ) -- no selector, bail out.
|
|
151
|
+
return original.apply( this, [].slice.call( arguments ) );
|
|
152
|
+
} else if ( fn == null ) {
|
|
153
|
+
if ( typeof selector === "string" ) {
|
|
154
|
+
// ( types, selector, fn ) -- with selector, proceed.
|
|
155
|
+
fn = data;
|
|
156
|
+
} else {
|
|
157
|
+
// ( types, data, fn ) -- no selector, bail out.
|
|
158
|
+
return original.apply( this, [].slice.call( arguments ) );
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if( selector ) {
|
|
163
|
+
this.each( function( i, e ){
|
|
164
|
+
e['_arachni_jquery_delegated_event'] =
|
|
165
|
+
e['_arachni_jquery_delegated_event'] || [];
|
|
166
|
+
|
|
167
|
+
e['_arachni_jquery_delegated_event'].push({
|
|
168
|
+
selector: selector,
|
|
169
|
+
event: types,
|
|
170
|
+
handler: fn
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return original.apply( this, [].slice.call( arguments ) );
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
|
|
125
179
|
// Overrides window.addEventListener and Node.prototype.addEventListener
|
|
126
180
|
// to intercept event binds so that we can keep track of them in order to
|
|
127
181
|
// optimize DOM analysis.
|
|
@@ -129,7 +183,7 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
129
183
|
// Override window.addEventListener
|
|
130
184
|
var original_Window_addEventListener = window.addEventListener;
|
|
131
185
|
|
|
132
|
-
window.addEventListener = function
|
|
186
|
+
window.addEventListener = function ( event, listener, useCapture ) {
|
|
133
187
|
_tokenDOMMonitor.registerEvent( window, event, listener );
|
|
134
188
|
original_Window_addEventListener.apply( window, [].slice.call( arguments ) );
|
|
135
189
|
};
|
|
@@ -137,16 +191,35 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
137
191
|
// Override Node.prototype.addEventListener
|
|
138
192
|
var original_Node_addEventListener = Node.prototype.addEventListener;
|
|
139
193
|
|
|
140
|
-
Node.prototype.addEventListener = function
|
|
194
|
+
Node.prototype.addEventListener = function ( event, listener, useCapture ) {
|
|
141
195
|
_tokenDOMMonitor.registerEvent( this, event, listener );
|
|
142
196
|
original_Node_addEventListener.apply( this, [].slice.call( arguments ) );
|
|
143
197
|
};
|
|
144
198
|
},
|
|
145
199
|
|
|
200
|
+
apply_jQuery_delegated_events: function ( element ){
|
|
201
|
+
if( !element['_arachni_jquery_delegated_event'] ) return;
|
|
202
|
+
|
|
203
|
+
var event_data = element['_arachni_jquery_delegated_event'];
|
|
204
|
+
var jquery_element = jQuery( element );
|
|
205
|
+
|
|
206
|
+
for( var i = 0; i < event_data.length; i++ ) {
|
|
207
|
+
var data = event_data[i];
|
|
208
|
+
|
|
209
|
+
jquery_element.find( data.selector ).each( function ( j, child ){
|
|
210
|
+
_tokenDOMMonitor.registerEvent( child, data.event, data.handler );
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
element['_arachni_jquery_delegated_event'] = undefined;
|
|
215
|
+
},
|
|
216
|
+
|
|
146
217
|
// Registers an event and its handler for the given element.
|
|
147
218
|
registerEvent: function ( element, event, handler ) {
|
|
148
|
-
if( !('
|
|
149
|
-
|
|
219
|
+
if( !('_arachni_events' in element) ) element['_arachni_events'] = [];
|
|
220
|
+
|
|
221
|
+
// Custom events are usually in the form of "click.delegateEventsview13".
|
|
222
|
+
element['_arachni_events'].push( [event.split( '.' )[0], handler] );
|
|
150
223
|
},
|
|
151
224
|
|
|
152
225
|
// Sets a unique enough custom ID attribute to elements that lack proper IDs.
|
|
@@ -169,7 +242,7 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
169
242
|
if( element.offsetWidth <= 0 && element.offsetHeight <= 0 ) continue;
|
|
170
243
|
|
|
171
244
|
// We don't care about elements without events.
|
|
172
|
-
if( !element.
|
|
245
|
+
if( !element._arachni_events || element._arachni_events.length == 0 ) continue;
|
|
173
246
|
|
|
174
247
|
element.setAttribute( 'data-arachni-id', _tokenDOMMonitor.hashCode( element.innerHTML ) );
|
|
175
248
|
}
|
|
@@ -188,3 +261,5 @@ var _tokenDOMMonitor = _tokenDOMMonitor || {
|
|
|
188
261
|
return hash;
|
|
189
262
|
}
|
|
190
263
|
};
|
|
264
|
+
|
|
265
|
+
_tokenDOMMonitor.initialize();
|
|
@@ -23,6 +23,8 @@ var _tokenTaintTracer = _tokenTaintTracer || {
|
|
|
23
23
|
// Allows the 'debug' function to operate.
|
|
24
24
|
enable_debugging: true,
|
|
25
25
|
|
|
26
|
+
max_sinks: 50,
|
|
27
|
+
|
|
26
28
|
// Hold debugging information, usually pushed by the 'debug' function.
|
|
27
29
|
debugging_data: [],
|
|
28
30
|
|
|
@@ -33,8 +35,14 @@ var _tokenTaintTracer = _tokenTaintTracer || {
|
|
|
33
35
|
// taints as keys and traces as values.
|
|
34
36
|
data_flow_sinks: {},
|
|
35
37
|
|
|
38
|
+
ignore: {
|
|
39
|
+
'': true,
|
|
40
|
+
'lodash': true
|
|
41
|
+
},
|
|
42
|
+
|
|
36
43
|
// Keeps track of which functions have had tracers installed.
|
|
37
|
-
traced: {
|
|
44
|
+
traced: {
|
|
45
|
+
},
|
|
38
46
|
|
|
39
47
|
// Original functions, without tracers. We don't want to trigger traced
|
|
40
48
|
// functions to provide functionality to this object.
|
|
@@ -198,14 +206,32 @@ var _tokenTaintTracer = _tokenTaintTracer || {
|
|
|
198
206
|
_tokenTaintTracer.data_flow_sinks[taint].push({
|
|
199
207
|
data: frame_data,
|
|
200
208
|
trace: _tokenTaintTracer.trace()
|
|
201
|
-
})
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if( _tokenTaintTracer.data_flow_sinks[taint].length > _tokenTaintTracer.max_sinks ) {
|
|
212
|
+
_tokenTaintTracer.data_flow_sinks[taint] =
|
|
213
|
+
_tokenTaintTracer.data_flow_sinks[taint].slice(
|
|
214
|
+
_tokenTaintTracer.data_flow_sinks[taint].length -
|
|
215
|
+
_tokenTaintTracer.max_sinks,
|
|
216
|
+
_tokenTaintTracer.data_flow_sinks[taint].length
|
|
217
|
+
)
|
|
218
|
+
}
|
|
202
219
|
},
|
|
203
220
|
|
|
204
221
|
log_execution_flow_sink: function (){
|
|
205
222
|
_tokenTaintTracer.execution_flow_sinks.push({
|
|
206
223
|
data: arguments,
|
|
207
224
|
trace: _tokenTaintTracer.trace()
|
|
208
|
-
})
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if( _tokenTaintTracer.execution_flow_sinks.length > _tokenTaintTracer.max_sinks ) {
|
|
228
|
+
_tokenTaintTracer.execution_flow_sinks =
|
|
229
|
+
_tokenTaintTracer.execution_flow_sinks.slice(
|
|
230
|
+
_tokenTaintTracer.execution_flow_sinks.length -
|
|
231
|
+
_tokenTaintTracer.max_sinks,
|
|
232
|
+
_tokenTaintTracer.execution_flow_sinks.length
|
|
233
|
+
)
|
|
234
|
+
}
|
|
209
235
|
},
|
|
210
236
|
|
|
211
237
|
flush_execution_flow_sinks: function (){
|
|
@@ -435,6 +461,8 @@ var _tokenTaintTracer = _tokenTaintTracer || {
|
|
|
435
461
|
if( Object.prototype.toString.call(potentialFunction) !== '[object Function]' )
|
|
436
462
|
continue;
|
|
437
463
|
|
|
464
|
+
if( _tokenTaintTracer.ignore[potentialFunction.name] ) continue;
|
|
465
|
+
|
|
438
466
|
var namespace_function_name = Object.prototype.toString.call(namespace) +
|
|
439
467
|
'-' + potentialFunction.name;
|
|
440
468
|
if( _tokenTaintTracer.traced[namespace_function_name] ) continue;
|