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.
- 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]]
|