arachni 1.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +2 -2
- data/arachni.gemspec +1 -1
- data/components/checks/active/code_injection_php_input_wrapper.rb +8 -3
- data/components/checks/active/file_inclusion.rb +7 -3
- data/components/checks/active/path_traversal.rb +7 -3
- data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +5 -3
- data/components/plugins/cookie_collector.rb +1 -1
- data/components/plugins/proxy.rb +4 -3
- data/components/plugins/vector_feed.rb +1 -1
- data/components/reporters/html/default/issue.erb +5 -0
- data/components/reporters/stdout.rb +4 -0
- data/lib/arachni/browser.rb +25 -6
- data/lib/arachni/browser/element_locator.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer.rb +3 -3
- data/lib/arachni/browser/javascript/taint_tracer/frame.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer/sink/base.rb +1 -1
- data/lib/arachni/check/auditor.rb +2 -0
- data/lib/arachni/component/manager.rb +2 -2
- data/lib/arachni/component/options/base.rb +2 -2
- data/lib/arachni/element/base.rb +2 -2
- data/lib/arachni/element/cookie.rb +4 -4
- data/lib/arachni/element/form.rb +1 -1
- data/lib/arachni/element/generic_dom.rb +1 -1
- data/lib/arachni/framework.rb +9 -1
- data/lib/arachni/http/client.rb +2 -0
- data/lib/arachni/http/request.rb +2 -2
- data/lib/arachni/http/response.rb +1 -1
- data/lib/arachni/issue.rb +2 -2
- data/lib/arachni/option_group.rb +1 -1
- data/lib/arachni/option_groups/input.rb +1 -1
- data/lib/arachni/option_groups/scope.rb +1 -1
- data/lib/arachni/page.rb +1 -1
- data/lib/arachni/page/dom/transition.rb +3 -3
- data/lib/arachni/parser.rb +3 -1
- data/lib/arachni/platform/list.rb +1 -1
- data/lib/arachni/report.rb +1 -1
- data/lib/arachni/rpc/client/instance/framework.rb +6 -6
- data/lib/arachni/rpc/client/instance/service.rb +7 -7
- data/lib/arachni/rpc/server/dispatcher.rb +18 -5
- data/lib/arachni/rpc/server/dispatcher/node.rb +13 -6
- data/lib/arachni/rpc/server/framework/distributor.rb +1 -1
- data/lib/arachni/rpc/server/framework/master.rb +1 -1
- data/lib/arachni/rpc/server/framework/multi_instance.rb +2 -2
- data/lib/arachni/rpc/server/instance.rb +11 -3
- data/lib/arachni/ruby/hash.rb +7 -6
- data/lib/arachni/state/framework.rb +1 -0
- data/lib/version +1 -1
- data/spec/arachni/browser_spec.rb +25 -0
- data/spec/arachni/component/manager_spec.rb +1 -1
- data/spec/arachni/element/cookie_spec.rb +3 -3
- data/spec/arachni/http/request_spec.rb +3 -3
- data/spec/arachni/option_groups/scope_spec.rb +2 -2
- data/spec/arachni/parser_spec.rb +7 -0
- data/spec/arachni/reporter/manager_spec.rb +1 -1
- data/spec/arachni/rpc/server/dispatcher/node_spec.rb +2 -0
- data/spec/arachni/rpc/server/framework_spec.rb +1 -1
- data/spec/arachni/ruby/hash_spec.rb +8 -8
- data/spec/support/servers/checks/passive/grep/cookie_set_for_parent_domain.rb +1 -1
- data/spec/support/shared/element/capabilities/inputtable.rb +2 -2
- data/ui/cli/utilities.rb +3 -0
- metadata +4 -4
data/lib/arachni/element/base.rb
CHANGED
@@ -48,7 +48,7 @@ class Base
|
|
48
48
|
attr_reader :initialization_options
|
49
49
|
|
50
50
|
def initialize( options )
|
51
|
-
options = options.
|
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.
|
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] ||=
|
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.
|
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.
|
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( ' ', '+' )
|
data/lib/arachni/element/form.rb
CHANGED
@@ -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.
|
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.
|
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
|
data/lib/arachni/framework.rb
CHANGED
@@ -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
|
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.
|
data/lib/arachni/http/client.rb
CHANGED
@@ -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}"
|
data/lib/arachni/http/request.rb
CHANGED
@@ -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
|
data/lib/arachni/issue.rb
CHANGED
@@ -462,13 +462,13 @@ class Issue
|
|
462
462
|
end)
|
463
463
|
end
|
464
464
|
|
465
|
-
value.
|
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.
|
471
|
+
value.my_symbolize_keys
|
472
472
|
|
473
473
|
when 'platform_name', 'platform_type'
|
474
474
|
next if !value
|
data/lib/arachni/option_group.rb
CHANGED
@@ -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 }.
|
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].
|
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|
|
data/lib/arachni/page.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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.
|
279
|
+
value.my_symbolize_keys
|
280
280
|
|
281
281
|
else
|
282
282
|
value
|
data/lib/arachni/parser.rb
CHANGED
@@ -217,7 +217,9 @@ class Parser
|
|
217
217
|
# @return [Hash]
|
218
218
|
# Parameters found in {#url}.
|
219
219
|
def link_vars
|
220
|
-
|
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>]
|
data/lib/arachni/report.rb
CHANGED
@@ -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.
|
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.
|
29
|
+
c = c.my_symbolize_keys
|
30
30
|
c[:options] = c[:options].map do |o|
|
31
|
-
o = o.
|
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.
|
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.
|
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.
|
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(&:
|
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.
|
29
|
+
c = c.my_symbolize_keys
|
30
30
|
c[:options] = c[:options].map do |o|
|
31
|
-
o = o.
|
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.
|
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.
|
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(&:
|
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.
|
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(&:
|
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(
|
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' =>
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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.
|
30
|
+
instance = instance.my_symbolize_keys
|
31
31
|
@instance_connections ||= {}
|
32
32
|
|
33
33
|
if @instance_connections[instance[:url]]
|