arachni 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +9 -2
- data/components/checks/active/code_injection.rb +5 -5
- data/components/checks/active/code_injection_timing.rb +3 -3
- data/components/checks/active/no_sql_injection_differential.rb +3 -2
- data/components/checks/active/os_cmd_injection.rb +11 -5
- data/components/checks/active/os_cmd_injection_timing.rb +11 -4
- data/components/checks/active/path_traversal.rb +2 -2
- data/components/checks/active/sql_injection.rb +1 -1
- data/components/checks/active/sql_injection/patterns/mssql +1 -0
- data/components/checks/active/sql_injection_differential.rb +3 -2
- data/components/checks/active/unvalidated_redirect.rb +3 -3
- data/components/checks/passive/common_directories/directories.txt +2 -0
- data/components/checks/passive/common_files/filenames.txt +1 -0
- data/lib/arachni/browser.rb +17 -1
- data/lib/arachni/check/auditor.rb +5 -2
- data/lib/arachni/check/base.rb +30 -5
- data/lib/arachni/element/capabilities/analyzable/differential.rb +2 -5
- data/lib/arachni/element/capabilities/auditable.rb +3 -1
- data/lib/arachni/element/capabilities/with_dom.rb +1 -0
- data/lib/arachni/element/capabilities/with_node.rb +1 -1
- data/lib/arachni/element/cookie.rb +2 -2
- data/lib/arachni/element/form.rb +1 -1
- data/lib/arachni/element/header.rb +2 -2
- data/lib/arachni/element/link_template.rb +1 -1
- data/lib/arachni/framework.rb +21 -1144
- data/lib/arachni/framework/parts/audit.rb +282 -0
- data/lib/arachni/framework/parts/browser.rb +132 -0
- data/lib/arachni/framework/parts/check.rb +86 -0
- data/lib/arachni/framework/parts/data.rb +158 -0
- data/lib/arachni/framework/parts/platform.rb +34 -0
- data/lib/arachni/framework/parts/plugin.rb +61 -0
- data/lib/arachni/framework/parts/report.rb +128 -0
- data/lib/arachni/framework/parts/scope.rb +40 -0
- data/lib/arachni/framework/parts/state.rb +457 -0
- data/lib/arachni/http/client.rb +33 -30
- data/lib/arachni/http/request.rb +6 -2
- data/lib/arachni/issue.rb +55 -1
- data/lib/arachni/platform/manager.rb +25 -21
- data/lib/arachni/state/framework.rb +7 -1
- data/lib/arachni/utilities.rb +10 -0
- data/lib/version +1 -1
- data/spec/arachni/browser_spec.rb +13 -0
- data/spec/arachni/check/auditor_spec.rb +1 -0
- data/spec/arachni/check/base_spec.rb +80 -0
- data/spec/arachni/element/cookie_spec.rb +2 -2
- data/spec/arachni/framework/parts/audit_spec.rb +391 -0
- data/spec/arachni/framework/parts/browser_spec.rb +26 -0
- data/spec/arachni/framework/parts/check_spec.rb +24 -0
- data/spec/arachni/framework/parts/data_spec.rb +187 -0
- data/spec/arachni/framework/parts/platform_spec.rb +62 -0
- data/spec/arachni/framework/parts/plugin_spec.rb +41 -0
- data/spec/arachni/framework/parts/report_spec.rb +66 -0
- data/spec/arachni/framework/parts/scope_spec.rb +86 -0
- data/spec/arachni/framework/parts/state_spec.rb +528 -0
- data/spec/arachni/framework_spec.rb +17 -1344
- data/spec/arachni/http/client_spec.rb +12 -7
- data/spec/arachni/issue_spec.rb +35 -0
- data/spec/arachni/platform/manager_spec.rb +2 -3
- data/spec/arachni/state/framework_spec.rb +15 -0
- data/spec/components/checks/active/code_injection_timing_spec.rb +5 -5
- data/spec/components/checks/active/no_sql_injection_differential_spec.rb +4 -0
- data/spec/components/checks/active/os_cmd_injection_spec.rb +20 -7
- data/spec/components/checks/active/os_cmd_injection_timing_spec.rb +5 -5
- data/spec/components/checks/active/sql_injection_differential_spec.rb +4 -0
- data/spec/components/checks/active/sql_injection_spec.rb +2 -3
- data/spec/support/servers/arachni/browser.rb +31 -0
- data/spec/support/servers/checks/active/code_injection.rb +1 -1
- data/spec/support/servers/checks/active/no_sql_injection_differential.rb +36 -34
- data/spec/support/servers/checks/active/os_cmd_injection.rb +6 -12
- data/spec/support/servers/checks/active/os_cmd_injection_timing.rb +9 -4
- data/spec/support/servers/checks/active/sql_injection.rb +1 -1
- data/spec/support/servers/checks/active/sql_injection_differential.rb +37 -34
- data/spec/support/shared/element/capabilities/with_node.rb +25 -0
- data/spec/support/shared/framework.rb +26 -0
- data/ui/cli/output.rb +2 -0
- data/ui/cli/rpc/server/dispatcher/option_parser.rb +1 -1
- metadata +32 -4
- data/components/checks/active/sql_injection/patterns/coldfusion +0 -1
data/lib/arachni/http/client.rb
CHANGED
@@ -206,7 +206,7 @@ class Client
|
|
206
206
|
duped_after_run = observers_for( :after_run ).dup
|
207
207
|
observers_for( :after_run ).clear
|
208
208
|
duped_after_run.each { |block| block.call }
|
209
|
-
end while @queue_size > 0
|
209
|
+
end while @queue_size > 0 || observers_for( :after_run ).any?
|
210
210
|
|
211
211
|
notify_after_each_run
|
212
212
|
|
@@ -711,7 +711,8 @@ class Client
|
|
711
711
|
#
|
712
712
|
# @param [Request] request
|
713
713
|
def forward_request( request )
|
714
|
-
request.id
|
714
|
+
add_callbacks = !request.id
|
715
|
+
request.id = @request_count
|
715
716
|
|
716
717
|
if debug_level_3?
|
717
718
|
print_debug_level_3 '------------'
|
@@ -728,40 +729,42 @@ class Client
|
|
728
729
|
print_debug_level_3 '------------'
|
729
730
|
end
|
730
731
|
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
732
|
+
if add_callbacks
|
733
|
+
request.on_complete do |response|
|
734
|
+
synchronize do
|
735
|
+
@response_count += 1
|
736
|
+
@burst_response_count += 1
|
737
|
+
@burst_response_time_sum += response.time
|
738
|
+
@total_response_time_sum += response.time
|
739
|
+
|
740
|
+
if Platform::Manager.fingerprint?( response )
|
741
|
+
# Force a fingerprint by converting the Response to a Page object.
|
742
|
+
response.to_page
|
743
|
+
end
|
737
744
|
|
738
|
-
|
739
|
-
# Force a fingerprint by converting the Response to a Page object.
|
740
|
-
response.to_page
|
741
|
-
end
|
745
|
+
notify_on_complete( response )
|
742
746
|
|
743
|
-
|
747
|
+
parse_and_set_cookies( response ) if request.update_cookies?
|
744
748
|
|
745
|
-
|
749
|
+
if debug_level_3?
|
750
|
+
print_debug_level_3 '------------'
|
751
|
+
print_debug_level_3 "Got response for request ID#: #{response.request.id}"
|
752
|
+
print_debug_level_3 "Performer: #{response.request.performer}"
|
753
|
+
print_debug_level_3 "Status: #{response.code}"
|
754
|
+
print_debug_level_3 "Code: #{response.return_code}"
|
755
|
+
print_debug_level_3 "Message: #{response.return_message}"
|
756
|
+
print_debug_level_3 "URL: #{response.url}"
|
757
|
+
print_debug_level_3 "Headers:\n#{response.headers_string}"
|
758
|
+
print_debug_level_3 "Parsed headers: #{response.headers}"
|
759
|
+
end
|
746
760
|
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
print_debug_level_3 "Status: #{response.code}"
|
752
|
-
print_debug_level_3 "Code: #{response.return_code}"
|
753
|
-
print_debug_level_3 "Message: #{response.return_message}"
|
754
|
-
print_debug_level_3 "URL: #{response.url}"
|
755
|
-
print_debug_level_3 "Headers:\n#{response.headers_string}"
|
756
|
-
print_debug_level_3 "Parsed headers: #{response.headers}"
|
757
|
-
end
|
761
|
+
if response.timed_out?
|
762
|
+
print_debug_level_3 "Request timed-out! -- ID# #{response.request.id}"
|
763
|
+
@time_out_count += 1
|
764
|
+
end
|
758
765
|
|
759
|
-
|
760
|
-
print_debug_level_3 "Request timed-out! -- ID# #{response.request.id}"
|
761
|
-
@time_out_count += 1
|
766
|
+
print_debug_level_3 '------------'
|
762
767
|
end
|
763
|
-
|
764
|
-
print_debug_level_3 '------------'
|
765
768
|
end
|
766
769
|
end
|
767
770
|
|
data/lib/arachni/http/request.rb
CHANGED
@@ -97,6 +97,10 @@ class Request < Message
|
|
97
97
|
# @return [Bool]
|
98
98
|
attr_accessor :high_priority
|
99
99
|
|
100
|
+
# @return [Integer]
|
101
|
+
# Maximum HTTP response size to accept, in bytes.
|
102
|
+
attr_accessor :response_max_size
|
103
|
+
|
100
104
|
# @private
|
101
105
|
attr_accessor :root_redirect_id
|
102
106
|
|
@@ -447,8 +451,6 @@ class Request < Message
|
|
447
451
|
end
|
448
452
|
end
|
449
453
|
|
450
|
-
private
|
451
|
-
|
452
454
|
def prepare_headers
|
453
455
|
headers['Cookie'] = effective_cookies.
|
454
456
|
map { |k, v| "#{Cookie.encode( k )}=#{Cookie.encode( v )}" }.
|
@@ -462,6 +464,8 @@ class Request < Message
|
|
462
464
|
headers.each { |k, v| headers[k] = Header.encode( v ) if v }
|
463
465
|
end
|
464
466
|
|
467
|
+
private
|
468
|
+
|
465
469
|
def fill_in_data_from_typhoeus_response( response )
|
466
470
|
@headers_string = response.debug_info.header_out.first
|
467
471
|
@effective_body = response.debug_info.data_out.first
|
data/lib/arachni/issue.rb
CHANGED
@@ -112,6 +112,10 @@ class Issue
|
|
112
112
|
# Variations of this issue.
|
113
113
|
attr_accessor :variations
|
114
114
|
|
115
|
+
# @return [Issue,nil]
|
116
|
+
# Parent of variation.
|
117
|
+
attr_accessor :parent
|
118
|
+
|
115
119
|
# @param [Hash] options
|
116
120
|
# Configuration hash holding instance attributes.
|
117
121
|
def initialize( options = {} )
|
@@ -130,6 +134,42 @@ class Issue
|
|
130
134
|
@tags ||= []
|
131
135
|
@variations ||= []
|
132
136
|
@variation = nil
|
137
|
+
@parent = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# @note The whole environment needs to be fresh.
|
141
|
+
#
|
142
|
+
# Rechecks the existence of this issue.
|
143
|
+
#
|
144
|
+
# @param [Framework.nil] framework
|
145
|
+
# {Framework} to use, if `nil` is given a new {Framework} will be
|
146
|
+
# instantiated and used.
|
147
|
+
#
|
148
|
+
# @return [Issue,nil]
|
149
|
+
# Fresh {Issue} if the issue still exists, `nil` otherwise.
|
150
|
+
def recheck( framework = nil )
|
151
|
+
new_issue = nil
|
152
|
+
checker = proc do |f|
|
153
|
+
referring_page.update_element_audit_whitelist vector
|
154
|
+
|
155
|
+
f.options.url = referring_page.url
|
156
|
+
f.options.audit.elements vector.class.type
|
157
|
+
|
158
|
+
f.checks.load( parent ? parent.check[:shortname] : check[:shortname] )
|
159
|
+
f.push_to_page_queue referring_page
|
160
|
+
|
161
|
+
f.run
|
162
|
+
|
163
|
+
new_issue = Data.issues[digest]
|
164
|
+
end
|
165
|
+
|
166
|
+
if framework
|
167
|
+
checker.call framework
|
168
|
+
else
|
169
|
+
Arachni::Framework.new( &checker )
|
170
|
+
end
|
171
|
+
|
172
|
+
new_issue
|
133
173
|
end
|
134
174
|
|
135
175
|
# @return [HTTP::Response]
|
@@ -307,6 +347,8 @@ class Issue
|
|
307
347
|
h[:request] = request.to_h if request
|
308
348
|
end
|
309
349
|
|
350
|
+
h.delete :parent
|
351
|
+
|
310
352
|
h
|
311
353
|
end
|
312
354
|
alias :to_hash :to_h
|
@@ -358,6 +400,7 @@ class Issue
|
|
358
400
|
|
359
401
|
issue.unique_id = unique_id
|
360
402
|
issue.variation = false
|
403
|
+
issue.parent = nil
|
361
404
|
issue
|
362
405
|
end
|
363
406
|
|
@@ -376,6 +419,7 @@ class Issue
|
|
376
419
|
|
377
420
|
issue.unique_id = unique_id
|
378
421
|
issue.variation = true
|
422
|
+
issue.parent = self
|
379
423
|
issue
|
380
424
|
end
|
381
425
|
|
@@ -394,6 +438,7 @@ class Issue
|
|
394
438
|
|
395
439
|
@variations = []
|
396
440
|
@variation = nil
|
441
|
+
@parent = nil
|
397
442
|
|
398
443
|
self
|
399
444
|
end
|
@@ -425,9 +470,11 @@ class Issue
|
|
425
470
|
def to_rpc_data
|
426
471
|
data = {}
|
427
472
|
instance_variables.each do |ivar|
|
428
|
-
data[ivar.to_s.gsub('@','')] =
|
473
|
+
data[ivar.to_s.gsub('@','')] =
|
474
|
+
instance_variable_get( ivar ).to_rpc_data_or_self
|
429
475
|
end
|
430
476
|
|
477
|
+
data.delete 'parent'
|
431
478
|
|
432
479
|
if data['check'] && data['check'][:elements]
|
433
480
|
data['check'] = data['check'].dup
|
@@ -487,6 +534,13 @@ class Issue
|
|
487
534
|
|
488
535
|
instance.instance_variable_set( "@#{name}", value )
|
489
536
|
end
|
537
|
+
|
538
|
+
if instance.variations
|
539
|
+
instance.variations.each do |v|
|
540
|
+
v.parent = instance
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
490
544
|
instance
|
491
545
|
end
|
492
546
|
|
@@ -60,26 +60,29 @@ class Manager
|
|
60
60
|
windows: {}
|
61
61
|
}
|
62
62
|
|
63
|
-
DB =
|
64
|
-
:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
63
|
+
DB = {
|
64
|
+
sql: {
|
65
|
+
mysql: {},
|
66
|
+
pgsql: {},
|
67
|
+
mssql: {},
|
68
|
+
oracle: {},
|
69
|
+
sqlite: {},
|
70
|
+
ingres: {},
|
71
|
+
emc: {},
|
72
|
+
db2: {},
|
73
|
+
interbase: {},
|
74
|
+
informix: {},
|
75
|
+
firebird: {},
|
76
|
+
maxdb: {},
|
77
|
+
sybase: {},
|
78
|
+
frontbase: {},
|
79
|
+
hsqldb: {},
|
80
|
+
access: {},
|
81
|
+
},
|
82
|
+
nosql: {
|
83
|
+
mongodb: {}
|
84
|
+
}
|
85
|
+
}
|
83
86
|
|
84
87
|
SERVERS = [
|
85
88
|
:apache,
|
@@ -114,6 +117,7 @@ class Manager
|
|
114
117
|
windows: 'MS Windows',
|
115
118
|
|
116
119
|
# Databases
|
120
|
+
sql: 'Generic SQL family',
|
117
121
|
mysql: 'MySQL',
|
118
122
|
pgsql: 'Postgresql',
|
119
123
|
mssql: 'MSSQL',
|
@@ -121,7 +125,6 @@ class Manager
|
|
121
125
|
sqlite: 'SQLite',
|
122
126
|
emc: 'EMC',
|
123
127
|
db2: 'DB2',
|
124
|
-
coldfusion: 'ColdFusion',
|
125
128
|
interbase: 'InterBase',
|
126
129
|
informix: 'Informix',
|
127
130
|
firebird: 'Firebird',
|
@@ -131,6 +134,7 @@ class Manager
|
|
131
134
|
ingres: 'IngresDB',
|
132
135
|
hsqldb: 'HSQLDB',
|
133
136
|
access: 'MS Access',
|
137
|
+
nosql: 'Generic NoSQL family',
|
134
138
|
mongodb: 'MongoDB',
|
135
139
|
|
136
140
|
# Web servers
|
@@ -227,7 +227,7 @@ class Framework
|
|
227
227
|
return false if aborting? || aborted?
|
228
228
|
|
229
229
|
if !running?
|
230
|
-
fail Error::StateNotAbortable, 'Cannot
|
230
|
+
fail Error::StateNotAbortable, 'Cannot abort an idle state.'
|
231
231
|
end
|
232
232
|
|
233
233
|
set_status_message :aborting
|
@@ -264,6 +264,12 @@ class Framework
|
|
264
264
|
@status == :aborting
|
265
265
|
end
|
266
266
|
|
267
|
+
# @return [Bool]
|
268
|
+
# `true` if the system has completed successfully, `false` otherwise.
|
269
|
+
def done?
|
270
|
+
@status == :done
|
271
|
+
end
|
272
|
+
|
267
273
|
# @param [Bool] block
|
268
274
|
# `true` if the method should block until a suspend has completed,
|
269
275
|
# `false` otherwise.
|
data/lib/arachni/utilities.rb
CHANGED
@@ -413,6 +413,16 @@ module Utilities
|
|
413
413
|
nil
|
414
414
|
end
|
415
415
|
|
416
|
+
def regexp_array_match( regexps, str )
|
417
|
+
regexps = [regexps].flatten.compact.
|
418
|
+
map { |s| s.is_a?( Regexp ) ? s : Regexp.new( s.to_s ) }
|
419
|
+
return true if regexps.empty?
|
420
|
+
|
421
|
+
cnt = 0
|
422
|
+
regexps.each { |filter| cnt += 1 if str =~ filter }
|
423
|
+
cnt == regexps.size
|
424
|
+
end
|
425
|
+
|
416
426
|
def remove_constants( mod, skip = [] )
|
417
427
|
return if skip.include?( mod )
|
418
428
|
return if !(mod.is_a?( Class ) || mod.is_a?( Module )) ||
|
data/lib/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.6
|
@@ -1211,6 +1211,19 @@ describe Arachni::Browser do
|
|
1211
1211
|
inputs[:email]
|
1212
1212
|
end
|
1213
1213
|
|
1214
|
+
context 'when one of those inputs is a' do
|
1215
|
+
context 'select' do
|
1216
|
+
let(:url) { "#{@url}/fire_event/form/select" }
|
1217
|
+
|
1218
|
+
it 'selects it' do
|
1219
|
+
@browser.watir.div( id: 'container-name' ).text.should ==
|
1220
|
+
inputs[:name]
|
1221
|
+
@browser.watir.div( id: 'container-email' ).text.should ==
|
1222
|
+
inputs[:email]
|
1223
|
+
end
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
|
1214
1227
|
context 'but has missing values' do
|
1215
1228
|
let(:inputs) do
|
1216
1229
|
{ name: 'The Dude' }
|
@@ -13,6 +13,7 @@ describe Arachni::Check::Base do
|
|
13
13
|
after( :each ) do
|
14
14
|
@framework.reset
|
15
15
|
Arachni::Options.reset
|
16
|
+
described_class.clear_info_cache
|
16
17
|
end
|
17
18
|
|
18
19
|
subject { described_class.new( Factory[:page], framework ) }
|
@@ -30,4 +31,83 @@ describe Arachni::Check::Base do
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
describe '#has_platforms?' do
|
35
|
+
context 'when platforms are provided' do
|
36
|
+
before do
|
37
|
+
described_class.stub(:info) { { platforms: [ :unix ] } }
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns true' do
|
41
|
+
described_class.has_platforms?.should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when platforms are not provided' do
|
46
|
+
before do
|
47
|
+
described_class.stub(:info) { { platforms: [] } }
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns false' do
|
51
|
+
described_class.has_platforms?.should be_false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#supports_platforms?' do
|
57
|
+
context 'when empty platforms are given' do
|
58
|
+
it 'returns true' do
|
59
|
+
described_class.supports_platforms?([]).should be_true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when no supported platforms are declared' do
|
64
|
+
before do
|
65
|
+
described_class.stub(:info) { { platforms: [] } }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns true' do
|
69
|
+
described_class.supports_platforms?([]).should be_true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when any of the given platforms are supported' do
|
74
|
+
before do
|
75
|
+
described_class.stub(:info) { { platforms: [:php] } }
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns true' do
|
79
|
+
described_class.supports_platforms?([:unix, :php]).should be_true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when a parent of any of the given platforms is supported' do
|
84
|
+
before do
|
85
|
+
described_class.stub(:info) { { platforms: [:unix] } }
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns true' do
|
89
|
+
described_class.supports_platforms?([:linux]).should be_true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when a child of any of the given platforms is supported' do
|
94
|
+
before do
|
95
|
+
described_class.stub(:info) { { platforms: [:linux] } }
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'returns true' do
|
99
|
+
described_class.supports_platforms?([:unix]).should be_true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when none of the given platforms are not provided' do
|
104
|
+
before do
|
105
|
+
described_class.stub(:info) { { platforms: [:windows] } }
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns false' do
|
109
|
+
described_class.supports_platforms?([:unix]).should be_false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
33
113
|
end
|