contrast-agent 4.2.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent/assess/contrast_event.rb +49 -130
  6. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  7. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  8. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  9. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  10. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  11. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  12. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  13. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  14. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  15. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  17. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  19. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  20. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  21. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  23. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  26. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
  27. data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
  28. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
  29. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  30. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  31. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  32. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  33. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  34. data/lib/contrast/agent/assess/property/tagged.rb +21 -15
  35. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  36. data/lib/contrast/agent/assess/tracker.rb +16 -18
  37. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  38. data/lib/contrast/agent/middleware.rb +50 -1
  39. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  40. data/lib/contrast/agent/patching/policy/patch.rb +4 -4
  41. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  42. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  43. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  44. data/lib/contrast/agent/protect/rule/cmd_injection.rb +3 -3
  45. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  46. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  47. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  48. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  49. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  50. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  51. data/lib/contrast/agent/reaction_processor.rb +1 -1
  52. data/lib/contrast/agent/response.rb +5 -5
  53. data/lib/contrast/agent/rewriter.rb +3 -3
  54. data/lib/contrast/agent/scope.rb +33 -13
  55. data/lib/contrast/agent/static_analysis.rb +13 -7
  56. data/lib/contrast/agent/version.rb +1 -1
  57. data/lib/contrast/api/decorators/library.rb +1 -0
  58. data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
  59. data/lib/contrast/api/decorators/trace_event.rb +19 -31
  60. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  61. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  62. data/lib/contrast/api/decorators/user_input.rb +2 -1
  63. data/lib/contrast/common_agent_configuration.rb +1 -1
  64. data/lib/contrast/components/assess.rb +36 -0
  65. data/lib/contrast/components/interface.rb +5 -3
  66. data/lib/contrast/components/scope.rb +23 -0
  67. data/lib/contrast/components/settings.rb +3 -3
  68. data/lib/contrast/config/assess_configuration.rb +2 -1
  69. data/lib/contrast/extension/assess/array.rb +1 -2
  70. data/lib/contrast/extension/assess/erb.rb +1 -3
  71. data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
  72. data/lib/contrast/extension/assess/fiber.rb +2 -3
  73. data/lib/contrast/extension/assess/hash.rb +4 -2
  74. data/lib/contrast/extension/assess/kernel.rb +1 -2
  75. data/lib/contrast/extension/assess/marshal.rb +34 -26
  76. data/lib/contrast/extension/assess/regexp.rb +3 -8
  77. data/lib/contrast/extension/assess/string.rb +1 -2
  78. data/lib/contrast/framework/base_support.rb +51 -53
  79. data/lib/contrast/framework/manager.rb +3 -2
  80. data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
  81. data/lib/contrast/framework/rack/support.rb +2 -1
  82. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  83. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  84. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  85. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  86. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  87. data/lib/contrast/framework/rails/support.rb +2 -1
  88. data/lib/contrast/framework/sinatra/support.rb +3 -2
  89. data/lib/contrast/logger/application.rb +0 -3
  90. data/lib/contrast/utils/duck_utils.rb +1 -1
  91. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  92. data/lib/contrast/utils/object_share.rb +3 -3
  93. data/lib/contrast/utils/preflight_util.rb +1 -1
  94. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  95. data/lib/contrast/utils/resource_loader.rb +1 -1
  96. data/lib/contrast/utils/sha256_builder.rb +2 -2
  97. data/lib/contrast/utils/string_utils.rb +1 -1
  98. data/lib/contrast/utils/tag_util.rb +9 -13
  99. data/resources/assess/policy.json +9 -9
  100. data/resources/deadzone/policy.json +156 -0
  101. data/resources/protect/policy.json +12 -0
  102. data/ruby-agent.gemspec +9 -6
  103. data/service_executables/VERSION +1 -1
  104. data/service_executables/linux/contrast-service +0 -0
  105. data/service_executables/mac/contrast-service +0 -0
  106. metadata +68 -25
@@ -92,8 +92,7 @@ module Contrast
92
92
  end
93
93
 
94
94
  def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
95
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
96
- return unless properties
95
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
97
96
 
98
97
  incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
99
98
  parent_event = incoming_properties&.event
@@ -141,22 +140,23 @@ module Contrast
141
140
  end
142
141
 
143
142
  def block_sub self_tracked, source, ret
144
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
145
- properties&.splat_from(source, ret) if self_tracked
143
+ return unless self_tracked
144
+
145
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
146
+ properties&.splat_from(source, ret)
146
147
  end
147
148
 
148
149
  def hash_sub self_tracked, source, ret
149
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
150
- properties&.splat_from(source, ret) if self_tracked
150
+ return unless self_tracked
151
+
152
+ properties = Contrast::Agent::Assess::Tracker.properties!(ret)
153
+ properties&.splat_from(source, ret)
151
154
  end
152
155
 
153
156
  def pattern_gsub parent_events, preshift, ret
154
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
155
- return unless properties
156
-
157
157
  source = preshift.object
158
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
159
- return unless source_properties
158
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
159
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
160
160
 
161
161
  source_properties.tag_keys.each do |key|
162
162
  properties.add_tag(key, 0...1)
@@ -11,13 +11,11 @@ module Contrast
11
11
  # Disclaimer: there may be a better way, but we're
12
12
  # in a 'get it work' state. hopefully, we'll be in
13
13
  # a 'get it right' state soon.
14
- class Trim
14
+ module Trim
15
15
  class << self
16
16
  def tr_tagger patcher, preshift, ret, _block
17
17
  return ret unless ret && !ret.empty?
18
-
19
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
20
- return unless properties
18
+ return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
21
19
 
22
20
  source = preshift.object
23
21
  args = preshift.args
@@ -59,9 +57,7 @@ module Contrast
59
57
 
60
58
  def tr_s_tagger patcher, preshift, ret, _block
61
59
  return unless ret && !ret.empty?
62
-
63
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
64
- return unless properties
60
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
65
61
 
66
62
  source = preshift.object
67
63
  args = preshift.args
@@ -178,8 +178,8 @@ module Contrast
178
178
  # don't apply second source -- probably needs tuning later if we
179
179
  # use more than 'UNTRUSTED' in our sources
180
180
  return if Contrast::Agent::Assess::Tracker.tracked?(target)
181
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
181
182
 
182
- properties = Contrast::Agent::Assess::Tracker.properties(target)
183
183
  # otherwise for each tag this source_node applies, create a tag range
184
184
  # on the target object
185
185
  # I realize this looping is counter-intuitive from the above
@@ -243,19 +243,7 @@ module Contrast
243
243
  when Contrast::Utils::ObjectShare::OBJECT_KEY
244
244
  object
245
245
  else
246
- if source_target.is_a?(Integer)
247
- args[source_target]
248
- # If this isn't an index param, it's a named one. R.I.P.
249
- else
250
- arg = nil
251
- args.each do |search|
252
- next unless search.is_a?(Hash)
253
-
254
- arg = search[source_target]
255
- break if arg
256
- end
257
- arg
258
- end
246
+ args[source_target]
259
247
  end
260
248
  end
261
249
 
@@ -25,14 +25,13 @@ module Contrast
25
25
  TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
26
26
 
27
27
  def xss_tilt_trigger context, trigger_node, _source, object, ret, *args
28
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
29
- return unless properties
28
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
30
29
 
31
30
  scope = args[0]
32
31
  erb_template_prerender = object.instance_variable_get(:@data)
33
32
  interpolated_inputs = []
34
- handle_binding_variables(scope, erb_template_prerender, ret, interpolated_inputs)
35
- handle_local_variables(args, erb_template_prerender, ret, interpolated_inputs)
33
+ handle_binding_variables(scope, erb_template_prerender, ret, properties, interpolated_inputs)
34
+ handle_local_variables(args, erb_template_prerender, ret, properties, interpolated_inputs)
36
35
  properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
37
36
  unless interpolated_inputs.empty?
38
37
  current_event = properties.event
@@ -53,8 +52,7 @@ module Contrast
53
52
 
54
53
  private
55
54
 
56
- def handle_binding_variables scope, erb_template_prerender, ret, interpolated_inputs
57
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
55
+ def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs
58
56
  binding_variables = scope.instance_variables
59
57
 
60
58
  binding_variables.each do |bound_variable_sym|
@@ -71,8 +69,7 @@ module Contrast
71
69
  end
72
70
  end
73
71
 
74
- def handle_local_variables args, erb_template_prerender, ret, interpolated_inputs
75
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
72
+ def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs
76
73
  locals = args[1]
77
74
  locals.each do |local_name, local_value|
78
75
  next unless Contrast::Agent::Assess::Tracker.tracked?(local_value)
@@ -43,7 +43,7 @@ module Contrast
43
43
  next unless trigger_node.violated?(arg)
44
44
 
45
45
  Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(
46
- context, trigger_node, arg, object, ret, args)
46
+ context, trigger_node, arg, object, ret, *args)
47
47
  end
48
48
 
49
49
  ret
@@ -39,7 +39,7 @@ module Contrast
39
39
  finish ||= url.length
40
40
 
41
41
  properties = Contrast::Agent::Assess::Tracker.properties(args[0])
42
- properties.any_tags_between?(start, finish)
42
+ properties&.any_tags_between?(start, finish)
43
43
  end
44
44
  end
45
45
  end
@@ -79,6 +79,10 @@ module Contrast
79
79
  # range : 5-10
80
80
  # result : 0-05
81
81
  #
82
+ # Note that we disable Lint/DuplicateBranch in this branch in order
83
+ # list out all tag range cases in the proper order to make this
84
+ # easier to understand
85
+ #
82
86
  # @param range [Range] the span to check, inclusive to exclusive
83
87
  # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
84
88
  # key to tags
@@ -101,10 +105,10 @@ module Contrast
101
105
  finish = range.size - start
102
106
  add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
103
107
  # the tag spans the requested range.
104
- when Contrast::Agent::Assess::Tag::WITHOUT
108
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
105
109
  add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
106
110
  # part of the tag is being selected
107
- when Contrast::Agent::Assess::Tag::HIGH_SPAN
111
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
108
112
  add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
109
113
  end
110
114
  end
@@ -173,14 +177,6 @@ module Contrast
173
177
  tags.fetch(label, nil) if tracked?
174
178
  end
175
179
 
176
- # Convert the tags of this object into the TraceTaintRange required
177
- # to be sent to the service
178
- #
179
- # @return [Array<Contrast::Api::Dtm::TraceTaintRange>]
180
- def tags_to_dtm
181
- Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
182
- end
183
-
184
180
  # Remove all tags within the given ranges.
185
181
  # This does not delete an entire tag if part of that tag is
186
182
  # outside this range, meaning we may reduce sizes of tags
@@ -301,9 +297,19 @@ module Contrast
301
297
  end
302
298
  end
303
299
 
304
- # Shift the tag ranges covering the given range
305
- # We assume this is for a insertion, meaning we
306
- # have to move tags to the right
300
+ # Shift the tag ranges covering the given range to account for new
301
+ # data being added. We assume this is for a insertion, meaning we
302
+ # have to move tags out of the range and to the right. For example,
303
+ # given current tags: 0-15
304
+ # when we insert a range: 5-10
305
+ # then the result is: 0-5, 10-20
306
+ #
307
+ # Note that we disable Lint/DuplicateBranch in this branch in order
308
+ # list out all tag range cases in the proper order to make this
309
+ # easier to understand
310
+ #
311
+ # @param range [Range] the range of new information that's been
312
+ # inserted
307
313
  def shift_tags_for_insertion range
308
314
  return unless tracked?
309
315
 
@@ -325,13 +331,13 @@ module Contrast
325
331
  when Contrast::Agent::Assess::Tag::WITHIN
326
332
  tag.shift(length)
327
333
  # the tag spans the range. leave the part below alone
328
- when Contrast::Agent::Assess::Tag::WITHOUT
334
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
329
335
  new_tag = tag.clone
330
336
  new_tag.update_start(range.begin)
331
337
  new_tag.shift(length)
332
338
  add << new_tag
333
339
  tag.update_end(range.begin)
334
- when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
340
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
335
341
  tag.shift(length)
336
342
  end
337
343
  end
@@ -33,7 +33,7 @@ module Contrast
33
33
  # (2) regexp must evaluate against user input
34
34
  return unless trigger_node.violated?(string)
35
35
 
36
- Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, args)
36
+ Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, *args)
37
37
  end
38
38
 
39
39
  protected
@@ -14,7 +14,20 @@ module Contrast
14
14
  PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
15
15
 
16
16
  class << self
17
+ # Retrieve the properties of the given Object, iff they exist.
18
+ #
19
+ # @param source [Object] the thing for which to look up properties.
20
+ # @return [Contrast::Agent::Assess::Properties, nil]
17
21
  def properties source
22
+ PROPERTIES_HASH[source]
23
+ end
24
+
25
+ # Retrieve the properties of the given Object, returning a new
26
+ # instance if one does not already exist and the source is trackable.
27
+ #
28
+ # @param source [Object] the thing for which to look up properties.
29
+ # @return [Contrast::Agent::Assess::Properties, nil]
30
+ def properties! source
18
31
  return unless trackable?(source)
19
32
 
20
33
  PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
@@ -33,30 +46,15 @@ module Contrast
33
46
  end
34
47
 
35
48
  # Copy the properties from one object to the next, assuming the
36
- # target does not already have its own properties.
49
+ # target does not already have its own properties. This should only
50
+ # ever be called when building PreShift for those objects sources
51
+ # which are already tracked.
37
52
  #
38
53
  # @param source [Object] the instance from which to copy properties
39
54
  # @param target [Object] the instance to which to copy properties
40
55
  def copy source, target
41
56
  PROPERTIES_HASH[target] ||= properties(source).dup
42
57
  end
43
-
44
- # Duplicate the given object, returning the duplicate after copying
45
- # the properties of the original and storing them as the properties
46
- # of the duplicate.
47
- #
48
- # @param source [Object] the thing to duplicate
49
- # @return [Object] the duplicate of the original, or the original if
50
- # it does not respond to duplication
51
- def duplicate source
52
- return source unless source
53
-
54
- duplicate = source.dup
55
- PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
56
- duplicate
57
- rescue StandardError
58
- source
59
- end
60
58
  end
61
59
  end
62
60
  end
@@ -19,6 +19,13 @@ module Contrast
19
19
  def feature
20
20
  'Deadzone'
21
21
  end
22
+
23
+ # Deadzone means place the code inside of the contrast scope so that
24
+ # none of our code executes. As such, all DeadZone nodes have that as
25
+ # their method scope
26
+ def method_scope
27
+ :contrast
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -79,7 +79,7 @@ module Contrast
79
79
  Contrast::Utils::HeapDumpUtil.run
80
80
 
81
81
  if AGENT.enabled?
82
- Contrast::Agent::StaticAnalysis.catchup
82
+ handle_first_request
83
83
  call_with_agent(env)
84
84
  else
85
85
  app.call(env)
@@ -91,6 +91,8 @@ module Contrast
91
91
  def setup_agent
92
92
  SETTINGS.reset_state
93
93
 
94
+ inform_deprecations
95
+
94
96
  if CONFIG.invalid?
95
97
  AGENT.disable!
96
98
  logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
@@ -101,6 +103,35 @@ module Contrast
101
103
  end
102
104
  end
103
105
 
106
+ # Some things have to wait until first request to happen, either because
107
+ # resolution is not complete or because the framework will preload
108
+ # classes, which confuses some of our instrumentation.
109
+ def handle_first_request
110
+ @_handle_first_request ||= begin
111
+ Contrast::Agent::StaticAnalysis.catchup
112
+ force_patching
113
+ true
114
+ end
115
+ end
116
+
117
+ # TODO: RUBY-1090 remove this method and those it calls.
118
+ #
119
+ # These modules are auto-loaded by Rails, meaning they are defined at
120
+ # startup, but that they don't actually exist. We account for this in
121
+ # most cases by using the ClassUtil.truly_defined? method, but it appears
122
+ # to fail for these Modules. In the short term, we can forcing a re-patch
123
+ # so that their dead zones apply.
124
+ def force_patching
125
+ force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
126
+ force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
127
+ force_patch(ActionView::Template) if defined?(ActionView::Template)
128
+ end
129
+
130
+ def force_patch mod
131
+ data = Contrast::Agent::ModuleData.new(mod)
132
+ Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
133
+ end
134
+
104
135
  # This is where we process each request we intercept as a middleware. We make the request context
105
136
  # available globally so that it can be accessed from anywhere. A RequestHandler object is made
106
137
  # for each request, which handles prefilter and postfilter operations.
@@ -173,6 +204,24 @@ module Contrast
173
204
  raise exception
174
205
  end
175
206
  end
207
+
208
+ # As we deprecate support to prepare to remove dead code, we need to
209
+ # inform our users still relying on the now deprecated and soon to be
210
+ # removed functionality. This method handles doing that by leveraging the
211
+ # standard Kernel#warn approach
212
+ def inform_deprecations
213
+ # Ruby 2.5 is currently in security maintenance, meaning int is only
214
+ # receiving updates for security issues. It will move to eol on 31
215
+ # March 2021. As such, we can remove support for it in Q2. We'll begin
216
+ # the deprecation warnings now so that customers have time to reach out
217
+ # if they'll be impacted.
218
+ # TODO: RUBY-715 remove this part of the method, leaving it empty if
219
+ # there are no other deprecations, when we drop 2.5 support.
220
+ return unless RUBY_VERSION < '2.6.0'
221
+
222
+ Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
223
+ 'Please contact Customer Support prior if you require continued support.')
224
+ end
176
225
  end
177
226
  end
178
227
  end
@@ -89,7 +89,7 @@ module Contrast
89
89
  end
90
90
 
91
91
  def find_method_node nodes, method_name, is_instance_method
92
- return nil unless nodes
92
+ return unless nodes
93
93
 
94
94
  nodes.find do |node|
95
95
  node.instance_method? == is_instance_method && node.method_name == method_name
@@ -366,17 +366,17 @@ module Contrast
366
366
  # alias_method may be private
367
367
  target_module.send(:alias_method, underlying_method_name, method_name)
368
368
  # TODO: RUBY-1052
369
- # rubocop:disable Kernel/DefineMethod
369
+ # rubocop:disable Performance/Kernel/DefineMethod
370
370
  target_module.send(:define_method, method_name, unbound_method.bind(target_module))
371
- # rubocop:enable Kernel/DefineMethod
371
+ # rubocop:enable Performance/Kernel/DefineMethod
372
372
  end
373
373
  target_module.send(visibility, method_name) # e.g., module.private(:my_method)
374
374
  when :prepend
375
375
  prepending_module = Module.new
376
376
  # TODO: RUBY-1052
377
- # rubocop:disable Kernel/DefineMethod
377
+ # rubocop:disable Performance/Kernel/DefineMethod
378
378
  prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
379
- # rubocop:enable Kernel/DefineMethod
379
+ # rubocop:enable Performance/Kernel/DefineMethod
380
380
  prepending_module.send(visibility, method_name)
381
381
  # This prepends to the singleton class (it patches a class method)
382
382
  target_module.prepend prepending_module
@@ -16,6 +16,23 @@ module Contrast
16
16
  extend Contrast::Agent::Protect::Policy::RuleApplicator
17
17
 
18
18
  class << self
19
+ # Calls the actual rule for this applicator, if required. Most rules
20
+ # invoke this from within their apply_rule method after doing
21
+ # whatever transformations they need to get into this common format.
22
+ #
23
+ # @param _method [Symbol] the name of the method for which this rule
24
+ # is invoked
25
+ # @param _exception [Exception] any exception raised; used for rules
26
+ # like Padding Oracle Attack (now defunct), which determine if the
27
+ # number and type of exceptions are an attack
28
+ # @param _properties [Hash] set of extra information provided by the
29
+ # applicator in an attempt to build a better story for the user
30
+ # @param _object [Object] the thing on which the triggering method
31
+ # was invoked
32
+ # @param args [Array<Object>] the arguments passed to the triggering
33
+ # method at invocation
34
+ # @raise [Contrast::SecurityException] on block, will pass the
35
+ # exception from the rule
19
36
  def invoke _method, _exception, _properties, _object, args
20
37
  return unless valid_input?(args)
21
38
  return if skip_analysis?
@@ -23,6 +40,28 @@ module Contrast
23
40
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
24
41
  end
25
42
 
43
+ # Calls the actual rule for this applicator, if required, when the
44
+ # triggering method is called from Marshal.load when it has been
45
+ # prepended.
46
+ #
47
+ # @param arg [Object] the argument passed to the triggering method
48
+ # at invocation
49
+ # @raise [Contrast::SecurityException] on block, will pass the
50
+ # exception from the rule
51
+ def prepended_invoke arg
52
+ return unless arg&.cs__is_a?(String)
53
+ return if skip_analysis?
54
+
55
+ rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
56
+ end
57
+
58
+ # Allow the rule to check if the given input is an attempt to
59
+ # deserialize something in a way that will result in a command
60
+ # execution
61
+ #
62
+ # @param command [String] user input that potentially contains a
63
+ # Gadget, a Module that results in code execution on
64
+ # deserialization, and some form of command.
26
65
  def apply_deserialization_command_check command
27
66
  return unless command
28
67
  return if skip_analysis?
@@ -38,11 +77,18 @@ module Contrast
38
77
 
39
78
  private
40
79
 
80
+ # Determine if the rule would care about the arguments passed in
81
+ # this method invocation. Required b/c Ruby doesn't do type
82
+ # checking on its method signatures.
83
+ #
84
+ # @param args [Array<Object>] the arguments provided to the
85
+ # instrumented method
86
+ # @return [Boolean]
41
87
  def valid_input? args
42
88
  return false unless args&.any?
43
89
 
44
90
  input = args[0]
45
- input.is_a?(String)
91
+ input.cs__is_a?(String)
46
92
  end
47
93
  end
48
94
  end