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
@@ -15,7 +15,7 @@ require Options.paths.lib + 'issue/severity'
15
15
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
16
16
  class Issue
17
17
 
18
- # Attributes removed from a parent issue (i.e. an issues with variations)
18
+ # Attributes removed from a parent issue (i.e. an issue with variations)
19
19
  # and solely populating variations.
20
20
  VARIATION_ATTRIBUTES = Set.new([
21
21
  :@page, :@referring_page, :@proof, :@signature, :@remarks, :@trusted
@@ -286,12 +286,23 @@ class Issue
286
286
  end
287
287
  end
288
288
 
289
- [:name, :description, :remedy_guidance, :remedy_code, :proof, :signature].each do |m|
289
+ [:name, :description, :remedy_guidance, :remedy_code, :proof].each do |m|
290
290
  define_method "#{m}=" do |s|
291
291
  instance_variable_set( "@#{m}".to_sym, s ? s.to_s.freeze : nil )
292
292
  end
293
293
  end
294
294
 
295
+ def signature=( sig )
296
+ @signature = case sig
297
+ when Regexp
298
+ sig.source
299
+ when String, nil
300
+ sig
301
+ else
302
+ sig.to_s
303
+ end
304
+ end
305
+
295
306
  # @return [Hash]
296
307
  def to_h
297
308
  h = {}
@@ -495,7 +506,10 @@ class Issue
495
506
  data['variations'] = data['variations'].map(&:to_rpc_data)
496
507
  end
497
508
 
498
- data['digest'] = digest
509
+ if !variation?
510
+ data['digest'] = digest
511
+ end
512
+
499
513
  data['severity'] = data['severity'].to_s
500
514
 
501
515
  data
@@ -13,6 +13,11 @@ module Arachni::OptionGroups
13
13
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
14
14
  class BrowserCluster < Arachni::OptionGroup
15
15
 
16
+ # @return [Hash<Regexp,String>]
17
+ # When the page URL matched the key `Regexp`, wait until the `String` CSS
18
+ # selector in the value matches an element.
19
+ attr_accessor :wait_for_elements
20
+
16
21
  # @return [Integer]
17
22
  # Amount of {BrowserCluster::Worker} to keep in the pool and put to work.
18
23
  attr_accessor :pool_size
@@ -38,13 +43,41 @@ class BrowserCluster < Arachni::OptionGroup
38
43
  attr_accessor :screen_height
39
44
 
40
45
  set_defaults(
46
+ wait_for_elements: {},
41
47
  pool_size: 6,
42
- job_timeout: 15,
48
+ job_timeout: 25,
43
49
  worker_time_to_live: 100,
44
50
  ignore_images: false,
45
51
  screen_width: 1600,
46
52
  screen_height: 1200
47
53
  )
48
54
 
55
+ def css_to_wait_for( url )
56
+ wait_for_elements.map do |pattern, css|
57
+ next if !(url =~ pattern)
58
+ css
59
+ end.compact
60
+ end
61
+
62
+ def wait_for_elements=( rules )
63
+ return @wait_for_elements = defaults[:wait_for_elements].dup if !rules
64
+
65
+ @wait_for_elements = rules.inject({}) do |h, (regexp, value)|
66
+ regexp = regexp.is_a?( Regexp ) ? regexp : Regexp.new( regexp.to_s )
67
+ h.merge!( regexp => value )
68
+ h
69
+ end
70
+ end
71
+
72
+ def to_rpc_data
73
+ d = super
74
+
75
+ %w(wait_for_elements).each do |k|
76
+ d[k] = d[k].my_stringify
77
+ end
78
+
79
+ d
80
+ end
81
+
49
82
  end
50
83
  end
@@ -242,7 +242,7 @@ class HTTP < Arachni::OptionGroup
242
242
  request_timeout: 10_000,
243
243
  request_redirect_limit: 5,
244
244
  request_concurrency: 20,
245
- request_queue_size: 500,
245
+ request_queue_size: 100,
246
246
  request_headers: {},
247
247
  response_max_size: 500_000,
248
248
  cookies: {}
@@ -379,31 +379,44 @@ class Page
379
379
  def has_script?
380
380
  return @has_javascript if !@has_javascript.nil?
381
381
 
382
- if !response.headers.content_type.to_s.start_with?( 'text/html' ) ||
383
- !text? || !document
382
+ if !response.headers.content_type.to_s.start_with?( 'text/html' ) || !text?
384
383
  return @has_javascript = false
385
384
  end
386
385
 
386
+ dbody = body.downcase
387
+
387
388
  # First check, quick and simple.
388
- return @has_javascript = true if document.css( 'script' ).any?
389
+ if dbody.include?( '<script' ) || dbody.include?( 'javascript:' )
390
+ return @has_javascript = true
391
+ end
389
392
 
390
393
  # Check for event attributes, if there are any then there's JS to be
391
394
  # executed.
392
395
  Browser::Javascript.events.flatten.each do |event|
393
- return @has_javascript = true if document.xpath( "//*[@#{event}]" ).any?
396
+ return @has_javascript = true if dbody.include?( "#{event}=" )
394
397
  end
395
398
 
396
- # If there's 'javascript:' in 'href' and 'action' attributes then
397
- # there's JS to be executed.
398
- [:action, :href].each do |candidate|
399
- document.xpath( "//*[@#{candidate}]" ).each do |attribute|
400
- if attribute.attributes[candidate.to_s].to_s.start_with?( 'javascript:' )
401
- return @has_javascript = true
402
- end
403
- end
399
+ @has_javascript = false
400
+ end
401
+
402
+ # @param [String, Symbol,Array<String, Symbol>] tags
403
+ # Element tag names.
404
+ #
405
+ # @return [Boolean]
406
+ # `true` if the page contains any of the given elements, `false` otherwise.
407
+ def has_elements?( *tags )
408
+ return if !text?
409
+
410
+ tags.flatten.each do |tag|
411
+ tag = tag.to_s
412
+
413
+ next if !(body =~ /<\s*#{tag}/i)
414
+
415
+ return false if !document
416
+ return true if document.css( tag ).any?
404
417
  end
405
418
 
406
- @has_javascript = false
419
+ false
407
420
  end
408
421
 
409
422
  # @return [Boolean]
@@ -270,13 +270,13 @@ class Transition
270
270
 
271
271
  when 'element'
272
272
  if value.is_a? String
273
- value.to_sym
273
+ data['event'].to_s == 'request' ? value : value.to_sym
274
274
  else
275
275
  Browser::ElementLocator.from_rpc_data( value )
276
276
  end
277
277
 
278
278
  when 'options'
279
- value.my_symbolize_keys
279
+ value.my_symbolize_keys(false)
280
280
 
281
281
  else
282
282
  value
@@ -31,14 +31,26 @@ class Parser
31
31
  # @abstract
32
32
  class Base
33
33
 
34
+ attr_reader :html
35
+ attr_reader :document
36
+ attr_reader :downcased_html
37
+
38
+ def initialize( options = {} )
39
+ @html = options[:html]
40
+ @downcased_html = @html.downcase
41
+ @document = options[:document]
42
+ end
43
+
34
44
  # This method must be implemented by all checks and must return an
35
45
  # array of paths as plain strings
36
46
  #
37
- # @param [Nokogiri] document Nokogiri document
38
- #
39
47
  # @return [Array<String>] paths
40
48
  # @abstract
41
- def run( document )
49
+ def run
50
+ end
51
+
52
+ def includes?( string_or_regexp )
53
+ !!@downcased_html[string_or_regexp]
42
54
  end
43
55
 
44
56
  end
@@ -125,9 +137,8 @@ class Parser
125
137
  @document = Nokogiri::HTML( body ) if text? rescue nil
126
138
  end
127
139
 
128
- # @note It's more of a placeholder method, it doesn't actually analyze anything.
129
- # It's a long shot that any of these will be vulnerable but better be safe
130
- # than sorry.
140
+ # @note It will include common request headers as well headers from the HTTP
141
+ # request.
131
142
  #
132
143
  # @return [Hash]
133
144
  # List of valid auditable HTTP header fields.
@@ -141,14 +152,15 @@ class Parser
141
152
  'User-Agent' => @options.http.user_agent || '',
142
153
  'Referer' => @url,
143
154
  'Pragma' => 'no-cache'
144
- }.map { |k, v| Header.new( url: @url, inputs: { k => v } ) }.freeze
155
+ }.merge( response.request.headers ).
156
+ map { |k, v| Header.new( url: @url, inputs: { k => v } ) }.freeze
145
157
  end
146
158
 
147
159
  # @return [Array<Element::Form>]
148
160
  # Forms from {#document}.
149
161
  def forms
150
162
  return @forms.freeze if @forms
151
- return [] if !text?
163
+ return [] if !text? || !(body =~ /<\s*form/i)
152
164
 
153
165
  f = Form.from_document( @url, document )
154
166
  return f if !@secondary_responses
@@ -199,7 +211,7 @@ class Parser
199
211
  # Links in {#document}.
200
212
  def links
201
213
  return @links.freeze if @links
202
- return @links = [link].compact if !text?
214
+ return @links = [link].compact if !text? || !(body =~ /\?.*=/)
203
215
 
204
216
  @links = [link].compact | Link.from_document( @url, document )
205
217
  end
@@ -227,7 +239,7 @@ class Parser
227
239
  # @return [Hash]
228
240
  # Parameters found in {#url}.
229
241
  def link_vars
230
- return {} if (!parsed = uri_parse( @url ))
242
+ return {} if !(parsed = uri_parse( @url ))
231
243
 
232
244
  @link_vars ||= parsed.rewrite.query_parameters.freeze
233
245
  end
@@ -309,7 +321,12 @@ class Parser
309
321
  def run_extractors
310
322
  begin
311
323
  self.class.extractors.available.map do |name|
312
- exception_jail( false ){ self.class.extractors[name].new.run( document ) }
324
+ exception_jail false do
325
+ self.class.extractors[name].new(
326
+ document: document,
327
+ html: body
328
+ ).run
329
+ end
313
330
  end.flatten.uniq.compact.
314
331
  map { |path| to_absolute( path ) }.compact.uniq.
315
332
  reject { |path| skip?( path ) }
@@ -36,6 +36,11 @@ class Fingerprinter
36
36
  def run
37
37
  end
38
38
 
39
+ def html?
40
+ @is_html ||= page.response.headers['content-type'].to_s.
41
+ downcase.include?( 'text/html' )
42
+ end
43
+
39
44
  # @param [String] string
40
45
  #
41
46
  # @return [Boolean]
@@ -89,12 +89,13 @@ class Manager
89
89
  :nginx,
90
90
  :tomcat,
91
91
  :iis,
92
- :jetty
92
+ :jetty,
93
+ :gunicorn
93
94
  ]
94
95
 
95
96
  LANGUAGES = [
96
97
  :php,
97
- :jsp,
98
+ :java,
98
99
  :python,
99
100
  :ruby,
100
101
  :asp,
@@ -104,7 +105,13 @@ class Manager
104
105
 
105
106
  # WebApp frameworks.
106
107
  FRAMEWORKS = [
107
- :rack
108
+ :rack,
109
+ :rails,
110
+ :cakephp,
111
+ :django,
112
+ :aspx_mvc,
113
+ :jsf,
114
+ :cherrypy
108
115
  ]
109
116
 
110
117
  PLATFORM_NAMES = {
@@ -143,10 +150,11 @@ class Manager
143
150
  tomcat: 'TomCat',
144
151
  iis: 'IIS',
145
152
  jetty: 'Jetty',
153
+ gunicorn: 'Gunicorn',
146
154
 
147
155
  # Programming languages
148
156
  php: 'PHP',
149
- jsp: 'JSP',
157
+ java: 'Java',
150
158
  python: 'Python',
151
159
  ruby: 'Ruby',
152
160
  asp: 'ASP',
@@ -154,7 +162,13 @@ class Manager
154
162
  perl: 'Perl',
155
163
 
156
164
  # Web frameworks
157
- rack: 'Rack'
165
+ rack: 'Rack',
166
+ django: 'Django',
167
+ cakephp: 'CakePHP',
168
+ rails: 'Ruby on Rails',
169
+ aspx_mvc: 'ASP.NET MVC',
170
+ jsf: 'JavaServer Faces',
171
+ cherrypy: 'CherryPy'
158
172
  }
159
173
 
160
174
  # Amount of
@@ -194,7 +208,7 @@ class Manager
194
208
  # Sets global platforms fingerprints
195
209
  # @private
196
210
  def self.set( platforms )
197
- @platforms = Support::Cache::RandomReplacement.new( PLATFORM_CACHE_SIZE )
211
+ @platforms = Support::Cache::LeastRecentlyPushed.new( PLATFORM_CACHE_SIZE )
198
212
  platforms.each { |k, v| @platforms[k] = v }
199
213
  @platforms
200
214
  end
@@ -228,8 +242,8 @@ class Manager
228
242
  # @return [Bool]
229
243
  # `true` if the resource should be fingerprinted, `false` otherwise.
230
244
  def self.fingerprint?( resource )
231
- !(!Options.fingerprint? || !resource.text? ||
232
- include?( resource.url ) || resource.scope.out?)
245
+ !(!Options.fingerprint? || resource.code != 200 || !resource.text? ||
246
+ include?( resource.url ) || resource.scope.out?)
233
247
  end
234
248
 
235
249
  # Runs all fingerprinters against the given `page`.
@@ -240,14 +254,40 @@ class Manager
240
254
  # @return [Manager]
241
255
  # Updated `self`.
242
256
  def self.fingerprint( page )
243
- return page if !fingerprint? page
257
+ synchronize do
258
+ return page if !fingerprint? page
244
259
 
245
- fingerprinters.available.each do |name|
246
- exception_jail( false ) do
247
- fingerprinters[name].new( page ).run
260
+ fingerprinters.available.each do |name|
261
+ exception_jail( false ) do
262
+ fingerprinters[name].new( page ).run
263
+ end
248
264
  end
265
+
266
+ # We do this to flag the resource as checked even if no platforms
267
+ # were identified. We don't want to keep checking a resource that
268
+ # yields nothing over and over.
269
+ update( page.url, [] )
249
270
  end
250
- page
271
+
272
+ # Fingerprinting will have resulted in element parsing, clear the element
273
+ # caches to keep RAM consumption down.
274
+ page.clear_cache
275
+ end
276
+
277
+ # @param [String, URI] uri
278
+ #
279
+ # @return [Manager]
280
+ # Platform for the given `uri`
281
+ def self.[]( uri )
282
+ # If fingerprinting is disabled there's no point in filling the cache
283
+ # with the same object over and over, create an identical one for all
284
+ # URLs and return that always.
285
+ if !Options.fingerprint?
286
+ return @default ||= new_from_options
287
+ end
288
+
289
+ return new_from_options if !(key = make_key( uri ))
290
+ synchronize { @platforms[key] ||= new_from_options }
251
291
  end
252
292
 
253
293
  # Sets platform manager for the given `uri`.
@@ -260,11 +300,16 @@ class Manager
260
300
  # @raise [Error::Invalid]
261
301
  # On {#invalid?} platforms.
262
302
  def self.[]=( uri, platforms )
263
- return new( platforms ) if !(key = make_key( uri ))
303
+ # For some reason we failed to make a key, try to salvage the situation.
304
+ if !(key = make_key( uri ))
305
+ return new_from_options( platforms )
306
+ end
264
307
 
265
308
  synchronize do
266
309
  @platforms[key] =
267
- platforms.is_a?( self ) ? platforms : new( platforms )
310
+ platforms.is_a?( self ) ?
311
+ platforms :
312
+ new_from_options( platforms )
268
313
  end
269
314
  end
270
315
 
@@ -293,22 +338,6 @@ class Manager
293
338
  end
294
339
  end
295
340
 
296
- # @param [String, URI] uri
297
- #
298
- # @return [Manager]
299
- # Platform for the given `uri`
300
- def self.[]( uri )
301
- # If fingerprinting is disabled there's no point in filling the cache
302
- # with the same object over and over, create an identical one for all
303
- # URLs and return that always.
304
- if !Options.fingerprint?
305
- return @default ||= new( Options.platforms )
306
- end
307
-
308
- return new if !(key = make_key( uri ))
309
- synchronize { @platforms[key] ||= new }
310
- end
311
-
312
341
  # @return [Boolean]
313
342
  # `true` if there are no platforms fingerprints, `false` otherwise.
314
343
  def self.empty?
@@ -326,6 +355,10 @@ class Manager
326
355
  parsed.without_query
327
356
  end
328
357
 
358
+ def self.new_from_options( platforms = [] )
359
+ new( platforms | Options.platforms )
360
+ end
361
+
329
362
  # @param [Array<String, Symbol>] platforms
330
363
  # Platforms with which to initialize the lists.
331
364
  def initialize( platforms = [] )
@@ -335,7 +368,7 @@ class Manager
335
368
  List.new( self.class.const_get( type.to_s.upcase.to_sym ) )
336
369
  end
337
370
 
338
- update [platforms | Options.platforms].flatten.compact
371
+ update platforms
339
372
  end
340
373
 
341
374
  # @!method os