arachni 1.0.5 → 1.0.6

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +9 -2
  4. data/components/checks/active/code_injection.rb +5 -5
  5. data/components/checks/active/code_injection_timing.rb +3 -3
  6. data/components/checks/active/no_sql_injection_differential.rb +3 -2
  7. data/components/checks/active/os_cmd_injection.rb +11 -5
  8. data/components/checks/active/os_cmd_injection_timing.rb +11 -4
  9. data/components/checks/active/path_traversal.rb +2 -2
  10. data/components/checks/active/sql_injection.rb +1 -1
  11. data/components/checks/active/sql_injection/patterns/mssql +1 -0
  12. data/components/checks/active/sql_injection_differential.rb +3 -2
  13. data/components/checks/active/unvalidated_redirect.rb +3 -3
  14. data/components/checks/passive/common_directories/directories.txt +2 -0
  15. data/components/checks/passive/common_files/filenames.txt +1 -0
  16. data/lib/arachni/browser.rb +17 -1
  17. data/lib/arachni/check/auditor.rb +5 -2
  18. data/lib/arachni/check/base.rb +30 -5
  19. data/lib/arachni/element/capabilities/analyzable/differential.rb +2 -5
  20. data/lib/arachni/element/capabilities/auditable.rb +3 -1
  21. data/lib/arachni/element/capabilities/with_dom.rb +1 -0
  22. data/lib/arachni/element/capabilities/with_node.rb +1 -1
  23. data/lib/arachni/element/cookie.rb +2 -2
  24. data/lib/arachni/element/form.rb +1 -1
  25. data/lib/arachni/element/header.rb +2 -2
  26. data/lib/arachni/element/link_template.rb +1 -1
  27. data/lib/arachni/framework.rb +21 -1144
  28. data/lib/arachni/framework/parts/audit.rb +282 -0
  29. data/lib/arachni/framework/parts/browser.rb +132 -0
  30. data/lib/arachni/framework/parts/check.rb +86 -0
  31. data/lib/arachni/framework/parts/data.rb +158 -0
  32. data/lib/arachni/framework/parts/platform.rb +34 -0
  33. data/lib/arachni/framework/parts/plugin.rb +61 -0
  34. data/lib/arachni/framework/parts/report.rb +128 -0
  35. data/lib/arachni/framework/parts/scope.rb +40 -0
  36. data/lib/arachni/framework/parts/state.rb +457 -0
  37. data/lib/arachni/http/client.rb +33 -30
  38. data/lib/arachni/http/request.rb +6 -2
  39. data/lib/arachni/issue.rb +55 -1
  40. data/lib/arachni/platform/manager.rb +25 -21
  41. data/lib/arachni/state/framework.rb +7 -1
  42. data/lib/arachni/utilities.rb +10 -0
  43. data/lib/version +1 -1
  44. data/spec/arachni/browser_spec.rb +13 -0
  45. data/spec/arachni/check/auditor_spec.rb +1 -0
  46. data/spec/arachni/check/base_spec.rb +80 -0
  47. data/spec/arachni/element/cookie_spec.rb +2 -2
  48. data/spec/arachni/framework/parts/audit_spec.rb +391 -0
  49. data/spec/arachni/framework/parts/browser_spec.rb +26 -0
  50. data/spec/arachni/framework/parts/check_spec.rb +24 -0
  51. data/spec/arachni/framework/parts/data_spec.rb +187 -0
  52. data/spec/arachni/framework/parts/platform_spec.rb +62 -0
  53. data/spec/arachni/framework/parts/plugin_spec.rb +41 -0
  54. data/spec/arachni/framework/parts/report_spec.rb +66 -0
  55. data/spec/arachni/framework/parts/scope_spec.rb +86 -0
  56. data/spec/arachni/framework/parts/state_spec.rb +528 -0
  57. data/spec/arachni/framework_spec.rb +17 -1344
  58. data/spec/arachni/http/client_spec.rb +12 -7
  59. data/spec/arachni/issue_spec.rb +35 -0
  60. data/spec/arachni/platform/manager_spec.rb +2 -3
  61. data/spec/arachni/state/framework_spec.rb +15 -0
  62. data/spec/components/checks/active/code_injection_timing_spec.rb +5 -5
  63. data/spec/components/checks/active/no_sql_injection_differential_spec.rb +4 -0
  64. data/spec/components/checks/active/os_cmd_injection_spec.rb +20 -7
  65. data/spec/components/checks/active/os_cmd_injection_timing_spec.rb +5 -5
  66. data/spec/components/checks/active/sql_injection_differential_spec.rb +4 -0
  67. data/spec/components/checks/active/sql_injection_spec.rb +2 -3
  68. data/spec/support/servers/arachni/browser.rb +31 -0
  69. data/spec/support/servers/checks/active/code_injection.rb +1 -1
  70. data/spec/support/servers/checks/active/no_sql_injection_differential.rb +36 -34
  71. data/spec/support/servers/checks/active/os_cmd_injection.rb +6 -12
  72. data/spec/support/servers/checks/active/os_cmd_injection_timing.rb +9 -4
  73. data/spec/support/servers/checks/active/sql_injection.rb +1 -1
  74. data/spec/support/servers/checks/active/sql_injection_differential.rb +37 -34
  75. data/spec/support/shared/element/capabilities/with_node.rb +25 -0
  76. data/spec/support/shared/framework.rb +26 -0
  77. data/ui/cli/output.rb +2 -0
  78. data/ui/cli/rpc/server/dispatcher/option_parser.rb +1 -1
  79. metadata +32 -4
  80. data/components/checks/active/sql_injection/patterns/coldfusion +0 -1
@@ -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 = @request_count
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
- request.on_complete do |response|
732
- synchronize do
733
- @response_count += 1
734
- @burst_response_count += 1
735
- @burst_response_time_sum += response.time
736
- @total_response_time_sum += response.time
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
- if Platform::Manager.fingerprint?( response )
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
- notify_on_complete( response )
747
+ parse_and_set_cookies( response ) if request.update_cookies?
744
748
 
745
- parse_and_set_cookies( response ) if request.update_cookies?
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
- if debug_level_3?
748
- print_debug_level_3 '------------'
749
- print_debug_level_3 "Got response for request ID#: #{response.request.id}"
750
- print_debug_level_3 "Performer: #{response.request.performer}"
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
- if response.timed_out?
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
 
@@ -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
@@ -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('@','')] = instance_variable_get( ivar ).to_rpc_data_or_self
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
- :mysql,
65
- :pgsql,
66
- :mssql,
67
- :oracle,
68
- :sqlite,
69
- :emc,
70
- :db2,
71
- :coldfusion,
72
- :interbase,
73
- :informix,
74
- :firebird,
75
- :maxdb,
76
- :sybase,
77
- :frontbase,
78
- :ingres,
79
- :hsqldb,
80
- :access,
81
- :mongodb
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 suspend an idle state.'
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.
@@ -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 )) ||
@@ -1 +1 @@
1
- 1.0.5
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' }
@@ -40,6 +40,7 @@ class AuditorTest < Arachni::Check::Base
40
40
  end
41
41
 
42
42
  def self.clear_info_cache
43
+ super
43
44
  @check_info = nil
44
45
  end
45
46
  end
@@ -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