arachni 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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