arachni 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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