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.
Files changed (287) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +159 -0
  3. data/LICENSE.md +126 -196
  4. data/README.md +32 -24
  5. data/arachni.gemspec +7 -7
  6. data/components/checks/active/code_injection_timing.rb +3 -3
  7. data/components/checks/active/csrf.rb +2 -2
  8. data/components/checks/active/file_inclusion.rb +6 -7
  9. data/components/checks/active/os_cmd_injection.rb +3 -3
  10. data/components/checks/active/path_traversal.rb +7 -7
  11. data/components/checks/active/response_splitting.rb +9 -4
  12. data/components/checks/active/session_fixation.rb +7 -3
  13. data/components/checks/active/source_code_disclosure.rb +5 -5
  14. data/components/checks/active/unvalidated_redirect.rb +12 -3
  15. data/components/checks/active/unvalidated_redirect_dom.rb +3 -3
  16. data/components/checks/active/xss.rb +23 -10
  17. data/components/checks/active/xss_dom_inputs.rb +113 -11
  18. data/components/checks/active/xxe.rb +3 -3
  19. data/components/checks/passive/backdoors.rb +6 -5
  20. data/components/checks/passive/backup_directories.rb +6 -6
  21. data/components/checks/passive/backup_files.rb +6 -6
  22. data/components/checks/passive/common_admin_interfaces.rb +58 -0
  23. data/components/checks/passive/common_admin_interfaces/admin-panels.txt +49 -0
  24. data/components/checks/passive/common_directories/directories.txt +0 -16
  25. data/components/checks/passive/common_files.rb +6 -5
  26. data/components/checks/passive/common_files/filenames.txt +0 -2
  27. data/components/checks/passive/directory_listing.rb +6 -6
  28. data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +3 -3
  29. data/components/checks/passive/grep/hsts.rb +6 -3
  30. data/components/checks/passive/grep/http_only_cookies.rb +3 -3
  31. data/components/checks/passive/grep/insecure_cookies.rb +2 -2
  32. data/components/checks/passive/grep/insecure_cors_policy.rb +6 -4
  33. data/components/checks/passive/grep/x_frame_options.rb +6 -4
  34. data/components/checks/passive/htaccess_limit.rb +6 -2
  35. data/components/checks/passive/http_put.rb +8 -4
  36. data/components/checks/passive/interesting_responses.rb +3 -2
  37. data/components/checks/passive/localstart_asp.rb +6 -2
  38. data/components/checks/passive/origin_spoof_access_restriction_bypass.rb +5 -1
  39. data/components/checks/passive/xst.rb +6 -2
  40. data/components/fingerprinters/frameworks/aspx_mvc.rb +43 -0
  41. data/components/fingerprinters/frameworks/cakephp.rb +28 -0
  42. data/components/fingerprinters/frameworks/cherrypy.rb +31 -0
  43. data/components/fingerprinters/frameworks/django.rb +33 -0
  44. data/components/fingerprinters/frameworks/jsf.rb +30 -0
  45. data/components/fingerprinters/frameworks/rack.rb +5 -7
  46. data/components/fingerprinters/frameworks/rails.rb +43 -0
  47. data/components/fingerprinters/languages/aspx.rb +11 -11
  48. data/components/fingerprinters/languages/{jsp.rb → java.rb} +11 -7
  49. data/components/fingerprinters/languages/php.rb +6 -6
  50. data/components/fingerprinters/languages/python.rb +14 -6
  51. data/components/fingerprinters/languages/ruby.rb +3 -5
  52. data/components/fingerprinters/servers/apache.rb +5 -4
  53. data/components/fingerprinters/servers/gunicorn.rb +33 -0
  54. data/components/fingerprinters/servers/jetty.rb +1 -1
  55. data/components/fingerprinters/servers/tomcat.rb +11 -4
  56. data/components/path_extractors/anchors.rb +5 -12
  57. data/components/path_extractors/areas.rb +5 -13
  58. data/components/path_extractors/comments.rb +5 -3
  59. data/components/path_extractors/data_url.rb +21 -0
  60. data/components/path_extractors/forms.rb +5 -13
  61. data/components/path_extractors/frames.rb +6 -13
  62. data/components/path_extractors/generic.rb +3 -12
  63. data/components/path_extractors/links.rb +5 -13
  64. data/components/path_extractors/meta_refresh.rb +5 -13
  65. data/components/path_extractors/scripts.rb +8 -14
  66. data/components/plugins/autologin.rb +17 -5
  67. data/components/plugins/defaults/meta/remedies/discovery.rb +11 -29
  68. data/components/plugins/login_script.rb +40 -10
  69. data/components/plugins/metrics.rb +235 -0
  70. data/components/plugins/proxy.rb +21 -4
  71. data/components/plugins/proxy/panel/page_accordion.html.erb +34 -2
  72. data/components/plugins/restrict_to_dom_state.rb +70 -0
  73. data/components/plugins/vector_feed.rb +38 -9
  74. data/components/reporters/plugin_formatters/html/metrics.rb +290 -0
  75. data/components/reporters/plugin_formatters/stdout/metrics.rb +80 -0
  76. data/components/reporters/plugin_formatters/xml/metrics.rb +29 -0
  77. data/components/reporters/stdout.rb +4 -2
  78. data/components/reporters/xml.rb +4 -4
  79. data/components/reporters/xml/schema.xsd +95 -0
  80. data/lib/arachni.rb +2 -0
  81. data/lib/arachni/browser.rb +132 -77
  82. data/lib/arachni/browser/javascript.rb +173 -45
  83. data/lib/arachni/browser/javascript/scripts/dom_monitor.js +81 -6
  84. data/lib/arachni/browser/javascript/scripts/taint_tracer.js +31 -3
  85. data/lib/arachni/browser_cluster.rb +41 -15
  86. data/lib/arachni/browser_cluster/job.rb +4 -0
  87. data/lib/arachni/browser_cluster/jobs/resource_exploration.rb +0 -9
  88. data/lib/arachni/browser_cluster/worker.rb +8 -5
  89. data/lib/arachni/check/auditor.rb +20 -8
  90. data/lib/arachni/check/base.rb +38 -6
  91. data/lib/arachni/element/base.rb +18 -1
  92. data/lib/arachni/element/capabilities/analyzable/differential.rb +0 -1
  93. data/lib/arachni/element/capabilities/analyzable/taint.rb +40 -10
  94. data/lib/arachni/element/capabilities/analyzable/timeout.rb +27 -23
  95. data/lib/arachni/element/capabilities/auditable/dom.rb +22 -0
  96. data/lib/arachni/element/capabilities/inputtable.rb +6 -2
  97. data/lib/arachni/element/capabilities/submittable.rb +1 -1
  98. data/lib/arachni/element/cookie.rb +37 -23
  99. data/lib/arachni/element/cookie/capabilities/mutable.rb +6 -6
  100. data/lib/arachni/element/cookie/dom.rb +0 -8
  101. data/lib/arachni/element/form.rb +28 -14
  102. data/lib/arachni/element/form/capabilities/auditable.rb +2 -2
  103. data/lib/arachni/element/form/capabilities/mutable.rb +5 -5
  104. data/lib/arachni/element/form/dom.rb +0 -8
  105. data/lib/arachni/element/generic_dom.rb +1 -1
  106. data/lib/arachni/element/json.rb +2 -1
  107. data/lib/arachni/element/json/capabilities/inputtable.rb +6 -6
  108. data/lib/arachni/element/json/capabilities/mutable.rb +1 -1
  109. data/lib/arachni/element/link.rb +13 -16
  110. data/lib/arachni/element/link/dom.rb +1 -14
  111. data/lib/arachni/element/link_template.rb +3 -2
  112. data/lib/arachni/element/link_template/dom.rb +0 -16
  113. data/lib/arachni/element/server.rb +51 -9
  114. data/lib/arachni/element/xml.rb +1 -0
  115. data/lib/arachni/ethon/easy.rb +4 -1
  116. data/lib/arachni/framework/parts/audit.rb +26 -77
  117. data/lib/arachni/framework/parts/browser.rb +50 -55
  118. data/lib/arachni/framework/parts/check.rb +4 -3
  119. data/lib/arachni/framework/parts/data.rb +41 -6
  120. data/lib/arachni/framework/parts/state.rb +16 -7
  121. data/lib/arachni/http/client.rb +66 -38
  122. data/lib/arachni/http/client/dynamic_404_handler.rb +46 -14
  123. data/lib/arachni/http/headers.rb +22 -10
  124. data/lib/arachni/http/proxy_server.rb +67 -22
  125. data/lib/arachni/http/proxy_server/ssl-interceptor-cacert.pem +34 -0
  126. data/lib/arachni/http/proxy_server/ssl-interceptor-cakey.pem +51 -0
  127. data/lib/arachni/http/request.rb +71 -18
  128. data/lib/arachni/issue.rb +17 -3
  129. data/lib/arachni/option_groups/browser_cluster.rb +34 -1
  130. data/lib/arachni/option_groups/http.rb +1 -1
  131. data/lib/arachni/page.rb +26 -13
  132. data/lib/arachni/page/dom/transition.rb +2 -2
  133. data/lib/arachni/parser.rb +28 -11
  134. data/lib/arachni/platform/fingerprinter.rb +5 -0
  135. data/lib/arachni/platform/manager.rb +65 -32
  136. data/lib/arachni/plugin/base.rb +8 -0
  137. data/lib/arachni/processes/instances.rb +25 -11
  138. data/lib/arachni/reporter/manager.rb +2 -2
  139. data/lib/arachni/rpc/client/instance.rb +4 -0
  140. data/lib/arachni/rpc/server/framework/master.rb +3 -3
  141. data/lib/arachni/rpc/server/framework/multi_instance.rb +0 -8
  142. data/lib/arachni/rpc/server/instance.rb +2 -1
  143. data/lib/arachni/ruby/array.rb +5 -0
  144. data/lib/arachni/ruby/hash.rb +5 -0
  145. data/lib/arachni/ruby/string.rb +2 -3
  146. data/lib/arachni/session.rb +32 -6
  147. data/lib/arachni/state/framework.rb +6 -2
  148. data/lib/arachni/support/cache.rb +1 -0
  149. data/lib/arachni/support/cache/base.rb +12 -8
  150. data/lib/arachni/support/cache/least_recently_pushed.rb +29 -0
  151. data/lib/arachni/support/cache/least_recently_used.rb +5 -8
  152. data/lib/arachni/support/cache/preference.rb +1 -1
  153. data/lib/arachni/support/cache/random_replacement.rb +1 -25
  154. data/lib/arachni/support/database/queue.rb +21 -8
  155. data/lib/arachni/support/lookup/base.rb +7 -1
  156. data/lib/arachni/support/mixins/observable.rb +3 -1
  157. data/lib/arachni/support/profiler.rb +51 -10
  158. data/lib/arachni/support/signature.rb +11 -2
  159. data/lib/arachni/trainer.rb +8 -2
  160. data/lib/arachni/uri.rb +28 -25
  161. data/lib/arachni/uri/scope.rb +1 -1
  162. data/lib/arachni/utilities.rb +8 -0
  163. data/lib/arachni/watir/element.rb +1 -1
  164. data/lib/version +1 -1
  165. data/spec/arachni/browser/javascript/dom_monitor_spec.rb +388 -53
  166. data/spec/arachni/browser/javascript/taint_tracer_spec.rb +41 -0
  167. data/spec/arachni/browser/javascript_spec.rb +235 -61
  168. data/spec/arachni/browser_cluster/jobs/resource_exploration_spec.rb +0 -9
  169. data/spec/arachni/browser_cluster_spec.rb +58 -10
  170. data/spec/arachni/browser_spec.rb +170 -26
  171. data/spec/arachni/check/auditor_spec.rb +22 -3
  172. data/spec/arachni/check/base_spec.rb +84 -0
  173. data/spec/arachni/element/body_spec.rb +1 -1
  174. data/spec/arachni/element/capabilities/analyzable/taint_spec.rb +3 -3
  175. data/spec/arachni/element/capabilities/analyzable/timeout_spec.rb +1 -1
  176. data/spec/arachni/element/cookie/dom_spec.rb +0 -9
  177. data/spec/arachni/element/cookie_spec.rb +85 -0
  178. data/spec/arachni/element/form/dom_spec.rb +0 -9
  179. data/spec/arachni/element/form_spec.rb +46 -3
  180. data/spec/arachni/element/json_spec.rb +20 -0
  181. data/spec/arachni/element/link/dom_spec.rb +0 -9
  182. data/spec/arachni/element/link_spec.rb +40 -15
  183. data/spec/arachni/element/link_template/dom_spec.rb +0 -8
  184. data/spec/arachni/element/link_template_spec.rb +2 -6
  185. data/spec/arachni/element/server_spec.rb +94 -8
  186. data/spec/arachni/element/xml_spec.rb +20 -0
  187. data/spec/arachni/framework/parts/audit_spec.rb +12 -14
  188. data/spec/arachni/framework/parts/browser_spec.rb +0 -171
  189. data/spec/arachni/framework/parts/platform_spec.rb +14 -8
  190. data/spec/arachni/framework/parts/report_spec.rb +1 -1
  191. data/spec/arachni/framework/parts/state_spec.rb +0 -9
  192. data/spec/arachni/http/client/dynamic_404_handlers_spec.rb +19 -0
  193. data/spec/arachni/http/client_spec.rb +169 -42
  194. data/spec/arachni/http/headers_spec.rb +18 -0
  195. data/spec/arachni/http/request_spec.rb +23 -0
  196. data/spec/arachni/issue_spec.rb +17 -6
  197. data/spec/arachni/page_spec.rb +22 -2
  198. data/spec/arachni/parser_spec.rb +5 -0
  199. data/spec/arachni/platform/manager_spec.rb +57 -25
  200. data/spec/arachni/reporter/manager_spec.rb +26 -0
  201. data/spec/arachni/rpc/server/active_options_spec.rb +9 -4
  202. data/spec/arachni/state/framework_spec.rb +2 -8
  203. data/spec/arachni/support/cache/least_recently_pushed_spec.rb +90 -0
  204. data/spec/arachni/support/cache/least_recently_used_spec.rb +5 -13
  205. data/spec/arachni/support/database/queue_spec.rb +7 -0
  206. data/spec/arachni/support/mixins/observable_spec.rb +15 -1
  207. data/spec/arachni/trainer_spec.rb +2 -2
  208. data/spec/components/checks/active/code_injection_timing_spec.rb +1 -1
  209. data/spec/components/checks/active/file_inclusion_spec.rb +6 -6
  210. data/spec/components/checks/active/path_traversal_spec.rb +2 -2
  211. data/spec/components/checks/active/source_code_disclosure_spec.rb +2 -2
  212. data/spec/components/checks/active/unvalidated_redirect_spec.rb +6 -6
  213. data/spec/components/checks/active/xss_dom_inputs_spec.rb +3 -5
  214. data/spec/components/checks/active/xss_dom_script_context_spec.rb +1 -1
  215. data/spec/components/checks/active/xss_spec.rb +5 -5
  216. data/spec/components/checks/passive/common_admin_interfaces_spec.rb +15 -0
  217. data/spec/components/checks/passive/interesting_responses_spec.rb +14 -1
  218. data/spec/components/fingerprinters/frameworks/aspx_mvc_spec.rb +31 -0
  219. data/spec/components/fingerprinters/frameworks/cakephp_spec.rb +22 -0
  220. data/spec/components/fingerprinters/frameworks/cherrypy_spec.rb +28 -0
  221. data/spec/components/fingerprinters/frameworks/django_spec.rb +37 -0
  222. data/spec/components/fingerprinters/frameworks/jsf_spec.rb +27 -0
  223. data/spec/components/fingerprinters/frameworks/rack_spec.rb +11 -14
  224. data/spec/components/fingerprinters/frameworks/rails_spec.rb +53 -0
  225. data/spec/components/fingerprinters/languages/asp_spec.rb +7 -9
  226. data/spec/components/fingerprinters/languages/aspx_spec.rb +10 -24
  227. data/spec/components/fingerprinters/languages/java_spec.rb +88 -0
  228. data/spec/components/fingerprinters/languages/php_spec.rb +19 -12
  229. data/spec/components/fingerprinters/languages/python_spec.rb +22 -9
  230. data/spec/components/fingerprinters/languages/ruby.rb +6 -4
  231. data/spec/components/fingerprinters/os/bsd_spec.rb +6 -4
  232. data/spec/components/fingerprinters/os/linux_spec.rb +6 -4
  233. data/spec/components/fingerprinters/os/solaris_spec.rb +6 -4
  234. data/spec/components/fingerprinters/os/unix_spec.rb +6 -4
  235. data/spec/components/fingerprinters/os/windows_spec.rb +6 -4
  236. data/spec/components/fingerprinters/servers/apache_spec.rb +15 -4
  237. data/spec/components/fingerprinters/servers/gunicorn_spec.rb +28 -0
  238. data/spec/components/fingerprinters/servers/iis_spec.rb +6 -6
  239. data/spec/components/fingerprinters/servers/jetty_spec.rb +6 -6
  240. data/spec/components/fingerprinters/servers/nginx_spec.rb +6 -4
  241. data/spec/components/fingerprinters/servers/tomcat_spec.rb +15 -6
  242. data/spec/components/path_extractors/data_url_spec.rb +19 -0
  243. data/spec/components/plugins/autologin_spec.rb +23 -0
  244. data/spec/components/plugins/login_script_spec.rb +112 -24
  245. data/spec/components/plugins/restrict_to_dom_state_spec.rb +16 -0
  246. data/spec/components/plugins/vector_feed_spec.rb +39 -1
  247. data/spec/support/factories/page/dom.rb +9 -4
  248. data/spec/support/factories/page/dom/transition.rb +31 -9
  249. data/spec/support/factories/scan_report.rb +8 -6
  250. data/spec/support/fixtures/empty/placeholder +0 -0
  251. data/spec/support/fixtures/report.afr +0 -0
  252. data/spec/support/fixtures/reporters/manager_spec/error.rb +18 -0
  253. data/spec/support/servers/arachni/browser.rb +117 -11
  254. data/spec/support/servers/arachni/browser/javascript/dom_monitor.rb +148 -4
  255. data/spec/support/servers/arachni/check/auditor.rb +4 -0
  256. data/spec/support/servers/arachni/element/cookie/cookie_dom.rb +1 -1
  257. data/spec/support/servers/arachni/http/client.rb +5 -0
  258. data/spec/support/servers/arachni/http/client/dynamic_404_handler.rb +13 -0
  259. data/spec/support/servers/checks/active/code_injection_timing.rb +1 -1
  260. data/spec/support/servers/checks/active/file_inclusion.rb +2 -2
  261. data/spec/support/servers/checks/active/path_traversal.rb +2 -2
  262. data/spec/support/servers/checks/active/source_code_disclosure.rb +40 -33
  263. data/spec/support/servers/checks/active/trainer_check.rb +9 -10
  264. data/spec/support/servers/checks/active/unvalidated_redirect_dom.rb +7 -4
  265. data/spec/support/servers/checks/active/xss.rb +35 -0
  266. data/spec/support/servers/checks/active/xss_dom.rb +1 -1
  267. data/spec/support/servers/checks/active/xss_dom_inputs.rb +24 -0
  268. data/spec/support/servers/checks/active/xss_dom_script_context.rb +1 -1
  269. data/spec/support/servers/checks/passive/common_admin_interfaces.rb +6 -0
  270. data/spec/support/servers/plugins/autologin.rb +9 -0
  271. data/spec/support/servers/plugins/restrict_to_dom_state.rb +4 -0
  272. data/spec/support/shared/element/base.rb +42 -0
  273. data/spec/support/shared/element/capabilities/auditable.rb +4 -4
  274. data/spec/support/shared/element/capabilities/auditable/dom.rb +26 -0
  275. data/spec/support/shared/element/capabilities/inputtable.rb +16 -11
  276. data/spec/support/shared/element/capabilities/submitable.rb +7 -2
  277. data/spec/support/shared/fingerprinter.rb +8 -0
  278. data/spec/support/shared/path_extractor.rb +1 -1
  279. data/ui/cli/framework.rb +3 -3
  280. data/ui/cli/framework/option_parser.rb +9 -0
  281. data/ui/cli/output.rb +9 -0
  282. data/ui/cli/reporter.rb +5 -2
  283. data/ui/cli/utilities.rb +4 -2
  284. metadata +76 -17
  285. data/lib/arachni/http/proxy_server/ssl-interceptor-cert.pem +0 -34
  286. data/lib/arachni/http/proxy_server/ssl-interceptor-pkey.pem +0 -51
  287. 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 = attributes.my_stringify
136
- Hash[(self.events.flatten.map(&:to_s) & attributes.keys).
137
- map { |event| [event.to_sym, attributes[event]] }]
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 ||= generate_token.to_s
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
- element['events'] =
281
- element['events'].map { |event, fn| [event.to_sym, fn] } |
282
- (self.class.events.flatten.map(&:to_s) & attributes.keys).
283
- map { |event| [event.to_sym, attributes[event]] }
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
- return false if has_js_initializer?( response )
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
- body = response.body.dup
426
+ response.headers['content-length'] = response.body.size
427
+
428
+ true
429
+ end
340
430
 
341
- # Schedule a tracer update at the beginning of each script block in order
342
- # to put our hooks into any newly introduced functions.
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
- # The fact that our update call seems to be taking place before any
345
- # functions get the chance to be defined doesn't seem to matter.
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
- # Also perform an update after each script block, this is for external
352
- # scripts.
353
- body.gsub!(
354
- /<\/script>/i,
355
- "\\0\n<script type=\"text/javascript\">#{@taint_tracer.stub.function( :update_tracers )}" <<
356
- "</script> <!-- Script injected by #{self.class} -->\n"
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
- response.body = <<-EOHTML
368
- <script src="#{script_url_for( :taint_tracer )}"></script> <!-- Script injected by #{self.class} -->
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
- #{custom_code}
377
- </script> <!-- Script injected by #{self.class} -->
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
- #{body}
380
- EOHTML
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
- response.headers['content-length'] = response.body.bytesize
383
- true
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
- private
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.events || [],
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 _window_addEventListener( event, listener, useCapture ) {
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 _Node_addEventListener( event, listener, useCapture ) {
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( !('events' in element) ) element['events'] = [];
149
- element['events'].push( [event, handler] );
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.events || element.events.length == 0 ) continue;
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;