arachni 1.0 → 1.0.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/README.md +2 -2
  4. data/arachni.gemspec +1 -1
  5. data/components/checks/active/code_injection_php_input_wrapper.rb +8 -3
  6. data/components/checks/active/file_inclusion.rb +7 -3
  7. data/components/checks/active/path_traversal.rb +7 -3
  8. data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +5 -3
  9. data/components/plugins/cookie_collector.rb +1 -1
  10. data/components/plugins/proxy.rb +4 -3
  11. data/components/plugins/vector_feed.rb +1 -1
  12. data/components/reporters/html/default/issue.erb +5 -0
  13. data/components/reporters/stdout.rb +4 -0
  14. data/lib/arachni/browser.rb +25 -6
  15. data/lib/arachni/browser/element_locator.rb +1 -1
  16. data/lib/arachni/browser/javascript/taint_tracer.rb +3 -3
  17. data/lib/arachni/browser/javascript/taint_tracer/frame.rb +1 -1
  18. data/lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb +1 -1
  19. data/lib/arachni/browser/javascript/taint_tracer/sink/base.rb +1 -1
  20. data/lib/arachni/check/auditor.rb +2 -0
  21. data/lib/arachni/component/manager.rb +2 -2
  22. data/lib/arachni/component/options/base.rb +2 -2
  23. data/lib/arachni/element/base.rb +2 -2
  24. data/lib/arachni/element/cookie.rb +4 -4
  25. data/lib/arachni/element/form.rb +1 -1
  26. data/lib/arachni/element/generic_dom.rb +1 -1
  27. data/lib/arachni/framework.rb +9 -1
  28. data/lib/arachni/http/client.rb +2 -0
  29. data/lib/arachni/http/request.rb +2 -2
  30. data/lib/arachni/http/response.rb +1 -1
  31. data/lib/arachni/issue.rb +2 -2
  32. data/lib/arachni/option_group.rb +1 -1
  33. data/lib/arachni/option_groups/input.rb +1 -1
  34. data/lib/arachni/option_groups/scope.rb +1 -1
  35. data/lib/arachni/page.rb +1 -1
  36. data/lib/arachni/page/dom/transition.rb +3 -3
  37. data/lib/arachni/parser.rb +3 -1
  38. data/lib/arachni/platform/list.rb +1 -1
  39. data/lib/arachni/report.rb +1 -1
  40. data/lib/arachni/rpc/client/instance/framework.rb +6 -6
  41. data/lib/arachni/rpc/client/instance/service.rb +7 -7
  42. data/lib/arachni/rpc/server/dispatcher.rb +18 -5
  43. data/lib/arachni/rpc/server/dispatcher/node.rb +13 -6
  44. data/lib/arachni/rpc/server/framework/distributor.rb +1 -1
  45. data/lib/arachni/rpc/server/framework/master.rb +1 -1
  46. data/lib/arachni/rpc/server/framework/multi_instance.rb +2 -2
  47. data/lib/arachni/rpc/server/instance.rb +11 -3
  48. data/lib/arachni/ruby/hash.rb +7 -6
  49. data/lib/arachni/state/framework.rb +1 -0
  50. data/lib/version +1 -1
  51. data/spec/arachni/browser_spec.rb +25 -0
  52. data/spec/arachni/component/manager_spec.rb +1 -1
  53. data/spec/arachni/element/cookie_spec.rb +3 -3
  54. data/spec/arachni/http/request_spec.rb +3 -3
  55. data/spec/arachni/option_groups/scope_spec.rb +2 -2
  56. data/spec/arachni/parser_spec.rb +7 -0
  57. data/spec/arachni/reporter/manager_spec.rb +1 -1
  58. data/spec/arachni/rpc/server/dispatcher/node_spec.rb +2 -0
  59. data/spec/arachni/rpc/server/framework_spec.rb +1 -1
  60. data/spec/arachni/ruby/hash_spec.rb +8 -8
  61. data/spec/support/servers/checks/passive/grep/cookie_set_for_parent_domain.rb +1 -1
  62. data/spec/support/shared/element/capabilities/inputtable.rb +2 -2
  63. data/ui/cli/utilities.rb +3 -0
  64. metadata +4 -4
@@ -48,7 +48,7 @@ class Base
48
48
  attr_reader :initialization_options
49
49
 
50
50
  def initialize( options )
51
- options = options.symbolize_keys( false )
51
+ options = options.my_symbolize_keys( false )
52
52
 
53
53
  if !(options[:url] || options[:action])
54
54
  fail 'Needs :url or :action option.'
@@ -167,7 +167,7 @@ class Base
167
167
 
168
168
  when 'initialization_options'
169
169
  value.is_a?( Hash ) ?
170
- value.symbolize_keys( false ) : value
170
+ value.my_symbolize_keys( false ) : value
171
171
 
172
172
  when 'method'
173
173
  value.to_sym
@@ -77,7 +77,7 @@ class Cookie < Base
77
77
  @data[:expires] = Time.parse( @data[:expires] ) rescue nil
78
78
  end
79
79
 
80
- @data[:domain] ||= ".#{parsed_uri.host}"
80
+ @data[:domain] ||= parsed_uri.host
81
81
 
82
82
  @default_inputs = self.inputs.dup.freeze
83
83
  end
@@ -321,7 +321,7 @@ class Cookie < Base
321
321
  c['expires'] = nil
322
322
  end
323
323
  c['secure'] = (c['secure'] == 'TRUE') ? true : false
324
- new( { url: url }.merge( c.symbolize_keys ) )
324
+ new( { url: url }.merge( c.my_symbolize_keys ) )
325
325
  end.flatten.compact
326
326
  end
327
327
 
@@ -428,7 +428,7 @@ class Cookie < Base
428
428
  cookie_hash['name'] = decode( cookie.name )
429
429
  cookie_hash['value'] = decode( cookie.value )
430
430
 
431
- new( { url: url }.merge( cookie_hash.symbolize_keys ) )
431
+ new( { url: url }.merge( cookie_hash.my_symbolize_keys ) )
432
432
  end.flatten.compact
433
433
  end
434
434
  alias :parse_set_cookie :from_set_cookie
@@ -461,7 +461,7 @@ class Cookie < Base
461
461
  #
462
462
  # @return [String]
463
463
  def encode( str, type = :value )
464
- reserved = "+;%\0"
464
+ reserved = "+;%\0\'\""
465
465
  reserved << '=' if type == :name
466
466
 
467
467
  URI.encode( str, reserved ).recode.gsub( ' ', '+' )
@@ -78,7 +78,7 @@ class Form < Base
78
78
 
79
79
  cinputs = (options[:inputs] || {}).inject({}) do |h, (name, value_or_info)|
80
80
  if value_or_info.is_a? Hash
81
- value_or_info = value_or_info.symbolize_keys
81
+ value_or_info = value_or_info.my_symbolize_keys
82
82
  h[name] = value_or_info[:value]
83
83
  @input_details[name.to_s] = value_or_info
84
84
  else
@@ -113,7 +113,7 @@ class GenericDOM < Base
113
113
  Arachni::Page::DOM::Transition.from_rpc_data( value )
114
114
 
115
115
  when 'initialization_options'
116
- value = value.is_a?( Hash ) ? value.symbolize_keys(false) : value
116
+ value = value.is_a?( Hash ) ? value.my_symbolize_keys(false) : value
117
117
  value[:transition] =
118
118
  Arachni::Page::DOM::Transition.from_rpc_data( value[:transition] )
119
119
  value
@@ -198,7 +198,15 @@ class Framework
198
198
 
199
199
  # Initialization may take a while so since we lazy load this make sure
200
200
  # that only one thread gets to this code at a time.
201
- synchronize { @browser_cluster ||= BrowserCluster.new }
201
+ synchronize do
202
+ if !@browser_cluster
203
+ state.set_status_message :browser_cluster_startup
204
+ end
205
+
206
+ @browser_cluster ||= BrowserCluster.new
207
+ state.clear_status_messages
208
+ @browser_cluster
209
+ end
202
210
  end
203
211
 
204
212
  # Starts the scan.
@@ -719,6 +719,7 @@ class Client
719
719
  print_debug_level_3 '------------'
720
720
  print_debug_level_3 'Queued request.'
721
721
  print_debug_level_3 "ID#: #{request.id}"
722
+ print_debug_level_3 "Performer: #{request.performer}"
722
723
  print_debug_level_3 "URL: #{request.url}"
723
724
  print_debug_level_3 "Method: #{request.method}"
724
725
  print_debug_level_3 "Params: #{request.parameters}"
@@ -747,6 +748,7 @@ class Client
747
748
  if debug_level_3?
748
749
  print_debug_level_3 '------------'
749
750
  print_debug_level_3 "Got response for request ID#: #{response.request.id}"
751
+ print_debug_level_3 "Performer: #{response.request.performer}"
750
752
  print_debug_level_3 "Status: #{response.code}"
751
753
  print_debug_level_3 "Code: #{response.return_code}"
752
754
  print_debug_level_3 "Message: #{response.return_message}"
@@ -343,7 +343,7 @@ class Request < Message
343
343
  if proxy
344
344
  options.merge!(
345
345
  proxy: proxy,
346
- proxytype: proxy_type
346
+ proxytype: (proxy_type || :http).to_sym
347
347
  )
348
348
 
349
349
  if proxy_user_password
@@ -353,7 +353,7 @@ class Request < Message
353
353
  elsif Arachni::Options.http.proxy_host && Arachni::Options.http.proxy_port
354
354
  options.merge!(
355
355
  proxy: "#{Arachni::Options.http.proxy_host}:#{Arachni::Options.http.proxy_port}",
356
- proxytype: Arachni::Options.http.proxy_type
356
+ proxytype: (Arachni::Options.http.proxy_type || :http).to_sym
357
357
  )
358
358
 
359
359
  if Arachni::Options.http.proxy_username && Arachni::Options.http.proxy_password
@@ -176,7 +176,7 @@ class Response < Message
176
176
  def to_rpc_data
177
177
  data = to_h
178
178
  data[:request] = request.to_rpc_data
179
- data.stringify_keys(false)
179
+ data.my_stringify_keys(false)
180
180
  end
181
181
 
182
182
  # @param [Hash] data {#to_rpc_data}
@@ -462,13 +462,13 @@ class Issue
462
462
  end)
463
463
  end
464
464
 
465
- value.symbolize_keys(false)
465
+ value.my_symbolize_keys(false)
466
466
 
467
467
  when 'variations'
468
468
  value.map { |i| from_rpc_data i }
469
469
 
470
470
  when 'remarks'
471
- value.symbolize_keys
471
+ value.my_symbolize_keys
472
472
 
473
473
  when 'platform_name', 'platform_type'
474
474
  next if !value
@@ -54,7 +54,7 @@ class OptionGroup
54
54
  end
55
55
 
56
56
  def to_rpc_data
57
- to_h.stringify_keys(false)
57
+ to_h.my_stringify_keys(false)
58
58
  end
59
59
 
60
60
  # @return [Hash]
@@ -147,7 +147,7 @@ class Input < Arachni::OptionGroup
147
147
  h = super
148
148
  [:values, :default_values].each do |k|
149
149
  # We can't have blocks in there...
150
- h[k] = h[k].select{ |_, v| v.is_a? String }.stringify
150
+ h[k] = h[k].select{ |_, v| v.is_a? String }.my_stringify
151
151
  end
152
152
  h
153
153
  end
@@ -219,7 +219,7 @@ class Scope < Arachni::OptionGroup
219
219
  d = super
220
220
 
221
221
  %w(redundant_path_patterns url_rewrites).each do |k|
222
- d[k] = d[k].stringify
222
+ d[k] = d[k].my_stringify
223
223
  end
224
224
 
225
225
  %w(exclude_path_patterns exclude_content_patterns include_path_patterns).each do |k|
@@ -478,7 +478,7 @@ class Page
478
478
  # @return [Hash]
479
479
  # Data representing this instance that are suitable the RPC transmission.
480
480
  def to_rpc_data
481
- data = to_initialization_options.stringify_keys(false)
481
+ data = to_initialization_options.my_stringify_keys(false)
482
482
  data['dom'] = dom.to_rpc_data
483
483
  data['element_audit_whitelist'] = element_audit_whitelist.to_a
484
484
  data['response'] = data['response'].to_rpc_data
@@ -143,7 +143,7 @@ class Transition
143
143
  self.event = event
144
144
  @element = element
145
145
 
146
- @options = options.symbolize_keys(false)
146
+ @options = options.my_symbolize_keys(false)
147
147
  @clock = Time.now
148
148
 
149
149
  return self if !block_given?
@@ -253,7 +253,7 @@ class Transition
253
253
  # @return [Hash]
254
254
  # Data representing this instance that are suitable the RPC transmission.
255
255
  def to_rpc_data
256
- h = to_hash.stringify_keys(false)
256
+ h = to_hash.my_stringify_keys(false)
257
257
  h['element'] = element.to_rpc_data_or_self
258
258
  h
259
259
  end
@@ -276,7 +276,7 @@ class Transition
276
276
  end
277
277
 
278
278
  when 'options'
279
- value.symbolize_keys
279
+ value.my_symbolize_keys
280
280
 
281
281
  else
282
282
  value
@@ -217,7 +217,9 @@ class Parser
217
217
  # @return [Hash]
218
218
  # Parameters found in {#url}.
219
219
  def link_vars
220
- @link_vars ||= uri_parse( @url ).rewrite.query_parameters.freeze
220
+ return {} if (!parsed = uri_parse( @url ))
221
+
222
+ @link_vars ||= parsed.rewrite.query_parameters.freeze
221
223
  end
222
224
 
223
225
  # @return [Array<Element::Cookie>]
@@ -221,7 +221,7 @@ class List
221
221
  when String
222
222
  platforms.to_sym
223
223
  when Hash
224
- platforms.symbolize_keys
224
+ platforms.my_symbolize_keys
225
225
  when Enumerable, Array
226
226
  platforms.to_a.flatten.map( &:to_sym ).uniq.sort
227
227
  end
@@ -245,7 +245,7 @@ class Report
245
245
 
246
246
  data['plugins'] = data['plugins'].inject({}) do |h, (k, v)|
247
247
  k = k.to_sym
248
- h[k] = v.symbolize_keys(false)
248
+ h[k] = v.my_symbolize_keys(false)
249
249
  next h if !h[k][:options]
250
250
 
251
251
  h[k][:options] = v['options'].map do |option|
@@ -26,9 +26,9 @@ class Framework < Proxy
26
26
  %w(list_reporters list_plugins).each do |m|
27
27
  translate m do |data|
28
28
  data.map do |c|
29
- c = c.symbolize_keys
29
+ c = c.my_symbolize_keys
30
30
  c[:options] = c[:options].map do |o|
31
- o = o.symbolize_keys
31
+ o = o.my_symbolize_keys
32
32
  o[:name] = o[:name].to_sym
33
33
  o[:type] = o[:type].to_sym
34
34
  o
@@ -39,18 +39,18 @@ class Framework < Proxy
39
39
  end
40
40
 
41
41
  translate :list_platforms do |platforms|
42
- platforms.inject({}) { |h, (k, v)| h[k] = v.symbolize_keys; h }
42
+ platforms.inject({}) { |h, (k, v)| h[k] = v.my_symbolize_keys; h }
43
43
  end
44
44
 
45
45
  translate :statistics do |stats|
46
- stats.symbolize_keys
46
+ stats.my_symbolize_keys
47
47
  end
48
48
 
49
49
  translate :progress do |data, options = {}|
50
50
  sitemap = data.delete('sitemap')
51
51
  issues = data.delete('issues')
52
52
 
53
- data = data.symbolize_keys
53
+ data = data.my_symbolize_keys
54
54
  data[:status] = data[:status].to_sym
55
55
 
56
56
  if issues
@@ -62,7 +62,7 @@ class Framework < Proxy
62
62
  end
63
63
 
64
64
  if data[:instances]
65
- data[:instances] = data[:instances].map(&:symbolize_keys)
65
+ data[:instances] = data[:instances].map(&:my_symbolize_keys)
66
66
  end
67
67
 
68
68
  if sitemap
@@ -26,9 +26,9 @@ class Service < Proxy
26
26
  %w(list_reporters list_plugins).each do |m|
27
27
  translate m do |data|
28
28
  data.map do |c|
29
- c = c.symbolize_keys
29
+ c = c.my_symbolize_keys
30
30
  c[:options] = c[:options].map do |o|
31
- o = o.symbolize_keys
31
+ o = o.my_symbolize_keys
32
32
  o[:name] = o[:name].to_sym
33
33
  o[:type] = o[:type].to_sym
34
34
  o
@@ -39,18 +39,18 @@ class Service < Proxy
39
39
  end
40
40
 
41
41
  translate :list_platforms do |platforms|
42
- platforms.inject({}) { |h, (k, v)| h[k] = v.symbolize_keys; h }
42
+ platforms.inject({}) { |h, (k, v)| h[k] = v.my_symbolize_keys; h }
43
43
  end
44
44
 
45
45
  translate :progress do |data|
46
46
  sitemap = data.delete('sitemap')
47
47
  issues = data.delete('issues')
48
48
 
49
- data = data.symbolize_keys
49
+ data = data.my_symbolize_keys
50
50
  data[:status] = data[:status].to_sym
51
51
 
52
52
  if data[:instances]
53
- data[:instances] = data[:instances].map(&:symbolize_keys)
53
+ data[:instances] = data[:instances].map(&:my_symbolize_keys)
54
54
  end
55
55
 
56
56
  if issues
@@ -68,7 +68,7 @@ class Service < Proxy
68
68
  sitemap = data.delete('sitemap')
69
69
  issues = data.delete('issues')
70
70
 
71
- data = data.symbolize_keys
71
+ data = data.my_symbolize_keys
72
72
  data[:status] = data[:status].to_sym
73
73
 
74
74
  if issues
@@ -76,7 +76,7 @@ class Service < Proxy
76
76
  end
77
77
 
78
78
  if data[:instances]
79
- data[:instances] = data[:instances].map(&:symbolize_keys)
79
+ data[:instances] = data[:instances].map(&:my_symbolize_keys)
80
80
  end
81
81
 
82
82
  if sitemap
@@ -76,15 +76,21 @@ class Dispatcher
76
76
  @consumed_pids = []
77
77
  @pool = Reactor.global.create_queue
78
78
 
79
+ print_status "Populating the pool with #{@options.dispatcher.pool_size} Instances."
79
80
  if @options.dispatcher.pool_size > 0
80
81
  @options.dispatcher.pool_size.times { add_instance_to_pool( false ) }
81
82
  end
82
83
 
84
+ print_status 'Waiting for Instances to come on-line.'
85
+
83
86
  # Check up on the pool and start the server once it has been filled.
84
87
  Reactor.global.at_interval( 0.1 ) do |task|
88
+ print_debug "Instances: #{@pool.size}/#{@options.dispatcher.pool_size}"
85
89
  next if @options.dispatcher.pool_size != @pool.size
86
90
  task.done
87
91
 
92
+ print_status 'Instances are on-line.'
93
+
88
94
  _services.each do |name, service|
89
95
  @server.add_handler( name, service.new( @options, self ) )
90
96
  end
@@ -284,6 +290,14 @@ class Dispatcher
284
290
 
285
291
  # Starts the dispatcher's server
286
292
  def run
293
+ Reactor.global.on_error do |_, e|
294
+ print_error "Arachni::Reactor: #{e}"
295
+
296
+ e.backtrace.each do |l|
297
+ print_error "Arachni::Reactor: #{l}"
298
+ end
299
+ end
300
+
287
301
  print_status 'Ready'
288
302
  @server.start
289
303
  rescue => e
@@ -326,17 +340,15 @@ class Dispatcher
326
340
  print_status "Instance added to pool -- PID: #{pid} - " +
327
341
  "Port: #{port} - Owner: #{owner}"
328
342
 
329
- url = "#{@options.dispatcher.external_address}:#{port}"
330
-
331
343
  # Wait until the Instance has booted before adding it to the pool.
332
- Client::Instance.when_ready( url, token ) do
344
+ Client::Instance.when_ready( "#{@options.rpc.server_address}:#{port}", token ) do
333
345
  @operation_in_progress = false
334
346
 
335
347
  @pool << {
336
348
  'token' => token,
337
349
  'pid' => pid,
338
350
  'port' => port,
339
- 'url' => url,
351
+ 'url' => "#{@options.dispatcher.external_address}:#{port}",
340
352
  'owner' => owner,
341
353
  'birthdate' => Time.now.to_s
342
354
  }
@@ -350,7 +362,8 @@ class Dispatcher
350
362
  end
351
363
 
352
364
  def connect_to_peer( url )
353
- Client::Dispatcher.new( @options, url )
365
+ @rpc_clients ||= {}
366
+ @rpc_clients[url] ||= Client::Dispatcher.new( @options, url )
354
367
  end
355
368
 
356
369
  def struct_to_h( struct )
@@ -52,12 +52,18 @@ class Server::Dispatcher::Node
52
52
  @nodes_info_cache = []
53
53
 
54
54
  if (neighbour = @options.dispatcher.neighbour)
55
- # add neighbour and announce him to everyone
56
- add_neighbour( neighbour, true )
57
-
58
- # grab the neighbour's neighbours
55
+ # Grab the neighbour's neighbours.
59
56
  connect_to_peer( neighbour ).neighbours do |urls|
60
- fail "Neighbour '#{neighbour}' is unreachable." if urls.rpc_exception?
57
+ if urls.rpc_exception?
58
+ add_dead_neighbour( neighbour )
59
+ print_info "Neighbour seems dead: #{neighbour}"
60
+ add_dead_neighbour( neighbour )
61
+ next
62
+ end
63
+
64
+ # Add neighbour and announce it to everyone.
65
+ add_neighbour( neighbour, true )
66
+
61
67
  urls.each { |url| @neighbours << url if url != @url }
62
68
  end
63
69
  end
@@ -229,7 +235,8 @@ class Server::Dispatcher::Node
229
235
  end
230
236
 
231
237
  def connect_to_peer( url )
232
- Client::Dispatcher.new( @options, url ).node
238
+ @rpc_clients ||= {}
239
+ @rpc_clients[url] ||= Client::Dispatcher.new( @options, url ).node
233
240
  end
234
241
 
235
242
  end
@@ -27,7 +27,7 @@ module Distributor
27
27
  # The hash must hold the `'url'` and the `'token'`. In subsequent calls
28
28
  # the `'token'` can be omitted.
29
29
  def connect_to_instance( instance )
30
- instance = instance.symbolize_keys
30
+ instance = instance.my_symbolize_keys
31
31
  @instance_connections ||= {}
32
32
 
33
33
  if @instance_connections[instance[:url]]