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
@@ -161,16 +161,14 @@ class Dynamic404Handler
161
161
  current_signature = (preliminary_signatures_for( url )[i] ||= {})
162
162
 
163
163
  PRECISION.times do
164
- Client.get( generator.call,
165
- # This is important, helps us reduce waiting callers.
166
- high_priority: true,
167
- performer: self
168
- ) do |c_res|
164
+ request( generator.call ) do |c_res|
169
165
  next if corrupted
170
166
 
167
+ print_debug "#{__method__} [gathering]: #{c_res.request.url} #{c_res.url} #{c_res.code} #{block}"
168
+
171
169
  # Well, bad luck, bail out to avoid FPs.
172
- if c_res.code == 0
173
- print_debug "#{__method__} [corrupted]: #{url} #{block}"
170
+ if corrupted_response?( c_res )
171
+ print_debug "#{__method__} [corrupted]: #{url} #{c_res.code} #{block}"
174
172
  corrupted = true
175
173
  next clear_data_for( url )
176
174
  end
@@ -217,15 +215,13 @@ class Dynamic404Handler
217
215
  current_signature = (advanced_signatures_for( url )[i] ||= {})
218
216
 
219
217
  PRECISION.times do
220
- Client.get( generator.call,
221
- # This is important, helps us reduce waiting callers.
222
- high_priority: true,
223
- performer: self
224
- ) do |c_res|
218
+ request( generator.call ) do |c_res|
225
219
  next if corrupted
226
220
 
221
+ print_debug "#{__method__} [gathering]: #{c_res.request.url} #{c_res.url} #{c_res.code} #{block}"
222
+
227
223
  # Well, bad luck, bail out to avoid FPs.
228
- if c_res.code == 0
224
+ if corrupted_response?( c_res )
229
225
  print_debug "#{__method__} [corrupted]: #{url} #{block}"
230
226
  corrupted = true
231
227
  next clear_data_for( url )
@@ -305,7 +301,20 @@ class Dynamic404Handler
305
301
  def needs_advanced_analysis?( url )
306
302
  uri = uri_parse( url )
307
303
  resource_name = uri.resource_name.to_s.split('.').tap(&:pop).join('.')
308
- !!(!resource_name.empty? || uri.resource_extension)
304
+ !!(
305
+ !resource_name.empty? ||
306
+ uri.resource_extension ||
307
+ uri.resource_name.to_s.include?( '~' )
308
+ )
309
+ end
310
+
311
+ # If this is neither a regular 404 nor a 202 the server probably freaked out
312
+ # -- 500 errors under stress and the like.
313
+ #
314
+ # In that case we should bail out to avoid corrupted signatures which can
315
+ # lead to FPs.
316
+ def corrupted_response?( response )
317
+ response.code != 404 && response.code != 200
309
318
  end
310
319
 
311
320
  def url_for( url )
@@ -387,9 +396,32 @@ class Dynamic404Handler
387
396
  probes << proc { up_to_path + random_string + '.' + resource_extension }
388
397
  end
389
398
 
399
+ if uri.resource_name.include?( '~' )
400
+ probes << proc {
401
+ up_to_path.sub(
402
+ uri.resource_name,
403
+ resource_name.gsub( '~', '~~' )
404
+ )
405
+ }
406
+ end
407
+
390
408
  probes
391
409
  end
392
410
 
411
+ def request( url, &block )
412
+ Client.get( url,
413
+ # This is important, helps us reduce waiting callers.
414
+ high_priority: true,
415
+
416
+ # We're going to be checking for a lot of non-existent resources,
417
+ # don't bother fingerprinting them
418
+ fingerprint: false,
419
+
420
+ performer: self,
421
+ &block
422
+ )
423
+ end
424
+
393
425
  def data_for( url )
394
426
  @signatures[url_for( url )] ||= signature_prototype
395
427
  end
@@ -18,13 +18,28 @@ module HTTP
18
18
  # @author Tasos Laskos <tasos.laskos@arachni-scanner.com>
19
19
  class Headers < Hash
20
20
 
21
+ FORMATTED_NAMES_CACHE = Support::Cache::LeastRecentlyPushed.new( 100 )
22
+
23
+ CONTENT_TYPE = 'content-type'
24
+ SET_COOKIE = 'set-cookie'
25
+ LOCATION = 'location'
26
+
21
27
  # @param [Headers, Hash] headers
22
28
  def initialize( headers = {} )
23
29
  merge!( headers || {} )
24
30
  end
25
31
 
26
32
  def merge!( headers )
27
- headers.each { |k, v| self[k] = v }
33
+ headers.each do |k, v|
34
+ # Handle headers with identical normalized names, like a mixture of
35
+ # Set-Cookie and SET-COOKIE.
36
+ if include? k
37
+ self[k] = [self[k]].flatten
38
+ self[k] << v
39
+ else
40
+ self[k] = v
41
+ end
42
+ end
28
43
  end
29
44
 
30
45
  # @note `field` will be capitalized appropriately before storing.
@@ -77,20 +92,20 @@ class Headers < Hash
77
92
  # @return [String, nil]
78
93
  # Value of the `Content-Type` field.
79
94
  def content_type
80
- self['content-type']
95
+ self[CONTENT_TYPE]
81
96
  end
82
97
 
83
98
  # @return [String, nil]
84
99
  # Value of the `Location` field.
85
100
  def location
86
- self['location']
101
+ self[LOCATION]
87
102
  end
88
103
 
89
104
  # @return [Array<String>]
90
105
  # Set-cookie strings.
91
106
  def set_cookie
92
- return [] if self['set-cookie'].to_s.empty?
93
- [self['set-cookie']].flatten
107
+ return [] if self[SET_COOKIE].to_s.empty?
108
+ [self[SET_COOKIE]].flatten
94
109
  end
95
110
 
96
111
  # @return [Array<Hash>]
@@ -119,15 +134,12 @@ class Headers < Hash
119
134
  end
120
135
 
121
136
  def self.format_field_name( field )
122
- # return field
123
-
124
137
  # If there's a '--' somewhere in there then skip it, it probably is an
125
138
  # audit payload.
126
139
  return field if field.include?( '--' )
127
140
 
128
- @formatted ||= Hash.new
129
- @formatted[field.downcase.hash] ||=
130
- field.to_s.split( '-' ).map( &:capitalize ).join( '-' )
141
+ FORMATTED_NAMES_CACHE[field] ||=
142
+ field.split( '-' ).map( &:capitalize ).join( '-' )
131
143
  end
132
144
 
133
145
  end
@@ -21,11 +21,17 @@ module HTTP
21
21
  # @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
22
22
  class ProxyServer < WEBrick::HTTPProxyServer
23
23
 
24
- INTERCEPTOR_CERTIFICATE =
25
- File.dirname( __FILE__ ) + '/proxy_server/ssl-interceptor-cert.pem'
24
+ CACHE = {
25
+ format_field_name: Support::Cache::LeastRecentlyPushed.new( 100 )
26
+ }
26
27
 
27
- INTERCEPTOR_PRIVATE_KEY =
28
- File.dirname( __FILE__ ) + '/proxy_server/ssl-interceptor-pkey.pem'
28
+ SKIP_HEADERS = Set.new( HopByHop | ['content-encoding'] )
29
+
30
+ INTERCEPTOR_CA_CERTIFICATE =
31
+ File.dirname( __FILE__ ) + '/proxy_server/ssl-interceptor-cacert.pem'
32
+
33
+ INTERCEPTOR_CA_KEY =
34
+ File.dirname( __FILE__ ) + '/proxy_server/ssl-interceptor-cakey.pem'
29
35
 
30
36
  # @param [Hash] options
31
37
  # @option options [String] :address ('0.0.0.0')
@@ -168,8 +174,12 @@ class ProxyServer < WEBrick::HTTPProxyServer
168
174
  # @see #service
169
175
  # @see Webrick::HTTPProxyServer#service
170
176
  def do_CONNECT( req, res )
177
+ host = req.unparsed_uri.split(':').first
178
+
171
179
  req.instance_variable_set( :@unparsed_uri, "127.0.0.1:#{interceptor_port}" )
172
- start_ssl_interceptor
180
+
181
+ start_ssl_interceptor( host )
182
+
173
183
  super( req, res )
174
184
  end
175
185
 
@@ -177,8 +187,10 @@ class ProxyServer < WEBrick::HTTPProxyServer
177
187
  # Merges the given HTTP options with some default ones.
178
188
  def http_opts( options = {} )
179
189
  options.merge(
190
+ performer: self,
191
+
180
192
  # Don't follow redirects, the client should handle this.
181
- follow_location: false,
193
+ follow_location: false,
182
194
 
183
195
  # Set the HTTP request timeout.
184
196
  timeout: @options[:timeout],
@@ -198,18 +210,54 @@ class ProxyServer < WEBrick::HTTPProxyServer
198
210
  # Starts the SSL interceptor proxy server.
199
211
  #
200
212
  # The interceptor will listen on {#interceptor_port}.
201
- def start_ssl_interceptor
213
+ def start_ssl_interceptor( host )
202
214
  return @interceptor if @interceptor
203
215
 
216
+ ca = OpenSSL::X509::Certificate.new( File.read( INTERCEPTOR_CA_CERTIFICATE ) )
217
+ ca_key = OpenSSL::PKey::RSA.new( File.read( INTERCEPTOR_CA_KEY ) )
218
+
219
+ keypair = OpenSSL::PKey::RSA.new( 4096 )
220
+
221
+ req = OpenSSL::X509::Request.new
222
+ req.version = 0
223
+ req.subject = OpenSSL::X509::Name.parse(
224
+ "CN=#{host}/subjectAltName=#{host}/O=Arachni/OU=Proxy/L=Athens/ST=Attika/C=GR"
225
+ )
226
+ req.public_key = keypair.public_key
227
+ req.sign( keypair, OpenSSL::Digest::SHA1.new )
228
+
229
+ cert = OpenSSL::X509::Certificate.new
230
+ cert.version = 2
231
+ cert.serial = rand( 999999 )
232
+ cert.not_before = Time.new
233
+ cert.not_after = cert.not_before + (60 * 60 * 24 * 365)
234
+ cert.public_key = req.public_key
235
+ cert.subject = req.subject
236
+ cert.issuer = ca.subject
237
+
238
+ ef = OpenSSL::X509::ExtensionFactory.new
239
+ ef.subject_certificate = cert
240
+ ef.issuer_certificate = ca
241
+
242
+ cert.extensions = [
243
+ ef.create_extension( 'basicConstraints', 'CA:FALSE', true ),
244
+ ef.create_extension( 'extendedKeyUsage', 'serverAuth', false ),
245
+ ef.create_extension( 'subjectKeyIdentifier', 'hash' ),
246
+ ef.create_extension( 'authorityKeyIdentifier', 'keyid:always,issuer:always' ),
247
+ ef.create_extension( 'keyUsage',
248
+ 'nonRepudiation,digitalSignature,keyEncipherment,dataEncipherment',
249
+ true
250
+ )
251
+ ]
252
+ cert.sign( ca_key, OpenSSL::Digest::SHA1.new )
253
+
204
254
  # The interceptor is only used for SSL decryption/encryption, the actual
205
255
  # proxy functionality is forwarded to the plain proxy server.
206
256
  @interceptor = self.class.new(
207
257
  address: '127.0.0.1',
208
258
  port: interceptor_port,
209
- ssl_certificate:
210
- OpenSSL::X509::Certificate.new( File.read( INTERCEPTOR_CERTIFICATE ) ),
211
- ssl_private_key:
212
- OpenSSL::PKey::RSA.new( File.read( INTERCEPTOR_PRIVATE_KEY ) ),
259
+ ssl_certificate: cert,
260
+ ssl_private_key: keypair,
213
261
  service_handler: method( :proxy_service )
214
262
  )
215
263
 
@@ -283,24 +331,21 @@ class ProxyServer < WEBrick::HTTPProxyServer
283
331
  # @param [#[]=] dst
284
332
  # Headers of the forwarded/proxy response.
285
333
  def choose_header( src, dst )
286
- connections = split_field( [src['connection']].flatten.first )
334
+ connections = Set.new( split_field( [src['connection']].flatten.first ) )
287
335
 
288
336
  src.each do |key, value|
289
337
  key = key.downcase
338
+ next if SKIP_HEADERS.include?( key ) || connections.include?( key )
290
339
 
291
- if HopByHop.member?( key ) || # RFC2616: 13.5.1
292
- connections.member?( key ) || # RFC2616: 14.10
293
- key == 'content-encoding'
294
- @logger.debug( "choose_header: `#{key}: #{value}'" )
295
- next
296
- end
297
-
298
- field = key.to_s.split( /_|-/ ).
299
- map { |segment| segment.capitalize }.join( '-' )
300
- dst[field] = value
340
+ dst[self.class.format_field_name( key )] = value
301
341
  end
302
342
  end
303
343
 
344
+ def self.format_field_name( field )
345
+ CACHE[:format_field_name][field] ||=
346
+ field.split( /_|-/ ).map( &:capitalize ).join( '-' )
347
+ end
348
+
304
349
  end
305
350
  end
306
351
  end
@@ -0,0 +1,34 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIF6zCCA9OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBjzELMAkGA1UEBhMCR1Ix
3
+ DzANBgNVBAgMBkF0dGlrYTEPMA0GA1UEBwwGQXRoZW5zMRAwDgYDVQQKDAdBcmFj
4
+ aG5pMQ4wDAYDVQQLDAVQcm94eTEQMA4GA1UEAwwHQXJhY2huaTEqMCgGCSqGSIb3
5
+ DQEJARYbYXJhY2huaUBhcmFjaG5pLXNjYW5uZXIuY29tMB4XDTE1MDYxNDAyMDgz
6
+ MVoXDTI1MDYxMTAyMDgzMVowgY8xCzAJBgNVBAYTAkdSMQ8wDQYDVQQIDAZBdHRp
7
+ a2ExDzANBgNVBAcMBkF0aGVuczEQMA4GA1UECgwHQXJhY2huaTEOMAwGA1UECwwF
8
+ UHJveHkxEDAOBgNVBAMMB0FyYWNobmkxKjAoBgkqhkiG9w0BCQEWG2FyYWNobmlA
9
+ YXJhY2huaS1zY2FubmVyLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
10
+ ggIBAKfH1iQiuG8zYS514F8zZLp8//00gPIML7+wcJn4cw2iN+yEix6RuZEIzqva
11
+ TfC4H6lZTyUhsoGZCeYAzzH9BMri/9uHJF+4worPfVKYIsm3TnMOVYoIC1kP1/Gj
12
+ Y1ih8KI/3baw0pddtJJeQ5/GjDaxx4+ynY4ZxrNcFmbTYXSrPcd62V/D4+edVnLi
13
+ uQsUezWYx7gNFiAuPRtlgJVBwzRoPV+Fh7Es2/SfmNSfGBCCYOpj4Fh0GOv7pSV0
14
+ 9TeF1W/XqDoq/eZ7RzLXoFK0Rz70/22MnFWAIdEUHZqwh3ktndNEK2QHq9FRGUx8
15
+ cUlXVAJgYv8tTErYVBltKIi2qgbnkh0Rb+rT2OkgmSL9lg0PwpXChMeSo6o6riC7
16
+ 5a8PQi6OmIseY742QYmBXApXDHtSzaY8onHUvqgxFrFpP0Bmca3AoF6kWQfXfRwS
17
+ ClMLwfBBDVeb+Tt97MO1G4m2VEW6c7o9H1t4td55LGslUzfJrmFe99vjAtdRTVqG
18
+ t3qDjbYi5VpE9kIyKcPHZkSKelMQ4VO1qB14CdaK/3ufqHTk7Ro2hKgstKDqnTCF
19
+ R7Qb9yXFsb1QyNtW8898T5mQm0HWQxdkaxcodizVjY5inHgqNwPa7A469EYFm5in
20
+ dLSOQtdPOV4q+y5lfhA2MkE3pRdSZPpnTqCEkSVVoKfdVlftAgMBAAGjUDBOMB0G
21
+ A1UdDgQWBBRvmR7gGqIfTQB0GygwgI22Kyr1bDAfBgNVHSMEGDAWgBRvmR7gGqIf
22
+ TQB0GygwgI22Kyr1bDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAx
23
+ g+ZjxJZXW1dYkc9ItXwAZba7oQJapLPu1iWCFy5cU13gck2MwDqfaDApNdr+erHg
24
+ WN7N+smMO+x3+lZZptzTfc6g/hBthBBAnetj8CUehjnWCo3aBGgVLE/mIEyHyFym
25
+ JX6xgcNYpvEzHT2o3Kmu/dAHCqY/3P9NtGJMhf7fy/Zz72tGY+ZTlthFSGWOjIEV
26
+ KXTtYnRUKmIRBLMacZmrJKIZCp/qGVSnFh9yjxHTWPNXXngGMxF9ItsFbdakjefn
27
+ hi2sHqns6/YbMaD2wK42dRQH1wH66DCGbyDPQO2j8iGK1q4Ggps+mGNYNBzMSAO/
28
+ ybdGRLQNq8ag7RXr/tNp/jYHopS/Ga0+3bOnCKf6MXNOolknSZhsOo16BWKDRd+d
29
+ m9ZTlro9AQr9+jdychG41IQNHXySrC5F1jLtzpEE5CJZIXkEFNYRcO9HMByJ3qwG
30
+ 759oYcMklwhU+NSC5qXpD2Z9KGf5rc0HmoO6OyD4T8hnQXkuAqoIN/NBg6YSNisN
31
+ H2C2gbl+taRLt0/RVCiacylo5pl3XSZuQxtGaQl55gRXQDPnlfB2CtIrV44gHZOJ
32
+ 88s+Ld9h44aoT2rWbLld6dU5ElZXWEJOim+aYKJewxX7PwEHn4iCpvMLu+4jXH3j
33
+ OkDTHheVJkxyhTDQ43ebg3/qi4yFaQyAHk3bQItwCw==
34
+ -----END CERTIFICATE-----
@@ -0,0 +1,51 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ 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
51
+ -----END RSA PRIVATE KEY-----
@@ -101,9 +101,6 @@ class Request < Message
101
101
  # Maximum HTTP response size to accept, in bytes.
102
102
  attr_accessor :response_max_size
103
103
 
104
- # @private
105
- attr_accessor :root_redirect_id
106
-
107
104
  # @param [Hash] options
108
105
  # Request options.
109
106
  # @option options [String] :url
@@ -129,6 +126,7 @@ class Request < Message
129
126
  super( options )
130
127
 
131
128
  @train = false if @train.nil?
129
+ @fingerprint = true if @fingerprint.nil?
132
130
  @update_cookies = false if @update_cookies.nil?
133
131
  @follow_location = false if @follow_location.nil?
134
132
  @max_redirects = (Options.http.request_redirect_limit || REDIRECT_LIMIT)
@@ -265,6 +263,13 @@ class Request < Message
265
263
  !!@follow_location
266
264
  end
267
265
 
266
+ # @return [Bool]
267
+ # `true` if the {Response} should be {Platform::Manager.fingerprint fingerprinted}
268
+ # for platforms, `false` otherwise.
269
+ def fingerprint?
270
+ @fingerprint
271
+ end
272
+
268
273
  # @return [Bool]
269
274
  # `true` if the {Response} should be analyzed by the {Trainer}
270
275
  # for new elements, `false` otherwise.
@@ -343,6 +348,10 @@ class Request < Message
343
348
 
344
349
  accept_encoding: 'gzip, deflate',
345
350
  nosignal: true,
351
+
352
+ # If Content-Length is missing this option will have no effect, so
353
+ # we'll also stream the body to make sure that we can at least abort
354
+ # the reading of the response body if it exceeds this limit.
346
355
  maxfilesize: max_size,
347
356
 
348
357
  # Don't keep the socket alive if this is a blocking request because
@@ -384,17 +393,25 @@ class Request < Message
384
393
  end
385
394
  end
386
395
 
387
- curl = parsed_url.query ? url.gsub( "?#{parsed_url.query}", '' ) : url
388
- r = Typhoeus::Request.new( curl, options )
396
+ curl = parsed_url.query ? url.gsub( "?#{parsed_url.query}", '' ) : url
397
+ typhoeus_request = Typhoeus::Request.new( curl, options )
389
398
 
390
399
  if @on_complete.any?
391
- r.on_complete do |typhoeus_response|
400
+ response_body_buffer = ''
401
+ set_body_reader( typhoeus_request, response_body_buffer )
402
+
403
+ typhoeus_request.on_complete do |typhoeus_response|
404
+ if typhoeus_request.options[:maxfilesize]
405
+ typhoeus_response.options[:response_body] =
406
+ response_body_buffer
407
+ end
408
+
392
409
  fill_in_data_from_typhoeus_response typhoeus_response
393
410
  handle_response Response.from_typhoeus( typhoeus_response )
394
411
  end
395
412
  end
396
413
 
397
- r
414
+ typhoeus_request
398
415
  end
399
416
 
400
417
  def to_h
@@ -480,33 +497,69 @@ class Request < Message
480
497
  h
481
498
  end
482
499
  end
500
+
501
+ def encode( string )
502
+ @easy ||= Ethon::Easy.new( url: 'www.example.com' )
503
+ @easy.escape string
504
+ end
483
505
  end
484
506
 
485
507
  def prepare_headers
486
- headers['Cookie'] = effective_cookies.
487
- map { |k, v| "#{Cookie.encode( k )}=#{Cookie.encode( v )}" }.
488
- join( ';' )
489
- headers.delete( 'Cookie' ) if headers['Cookie'].empty?
490
-
491
508
  headers['User-Agent'] ||= Options.http.user_agent
492
509
  headers['Accept'] ||= 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
493
510
  headers['From'] ||= Options.authorized_by if Options.authorized_by
494
511
 
495
512
  headers.each { |k, v| headers[k] = Header.encode( v ) if v }
513
+
514
+ headers['Cookie'] = effective_cookies.
515
+ map { |k, v| "#{Cookie.encode( k )}=#{Cookie.encode( v )}" }.
516
+ join( ';' )
517
+ headers.delete( 'Cookie' ) if headers['Cookie'].empty?
518
+
519
+ headers
496
520
  end
497
521
 
498
522
  private
499
523
 
524
+ def client_run
525
+ typhoeus_request = to_typhoeus
526
+
527
+ response_body_buffer = ''
528
+ set_body_reader( typhoeus_request, response_body_buffer )
529
+
530
+ typhoeus_response = typhoeus_request.run
531
+
532
+ if typhoeus_request.options[:maxfilesize]
533
+ typhoeus_response.options[:response_body] = response_body_buffer
534
+ end
535
+
536
+ fill_in_data_from_typhoeus_response typhoeus_response
537
+
538
+ Response.from_typhoeus( typhoeus_response )
539
+ end
540
+
500
541
  def fill_in_data_from_typhoeus_response( response )
501
- @headers_string = response.debug_info.header_out.first
502
- @effective_body = response.debug_info.data_out.first
542
+ # Only grab the last data.
543
+ # In case of CONNECT calls for HTTPS via proxy the first data will be
544
+ # the proxy-related stuff.
545
+ @headers_string = response.debug_info.header_out.last
546
+ @effective_body = response.debug_info.data_out.last
503
547
  end
504
548
 
505
- def client_run
506
- response = to_typhoeus.run
507
- fill_in_data_from_typhoeus_response response
549
+ def set_body_reader( typhoeus_request, buffer )
550
+ return if !typhoeus_request.options[:maxfilesize]
508
551
 
509
- Response.from_typhoeus( response )
552
+ aborted = nil
553
+ typhoeus_request.on_body do |chunk|
554
+ next aborted if aborted
555
+
556
+ if buffer.size >= typhoeus_request.options[:maxfilesize]
557
+ buffer.clear
558
+ next aborted = :abort
559
+ end
560
+
561
+ buffer << chunk
562
+ end
510
563
  end
511
564
 
512
565
  end