contrast-agent 4.9.1 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec_parallel +6 -0
  4. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  5. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  6. data/ext/cs__common/cs__common.c +24 -7
  7. data/ext/cs__common/cs__common.h +12 -2
  8. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -12
  9. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
  10. data/ext/cs__os_information/cs__os_information.c +31 -0
  11. data/ext/cs__os_information/cs__os_information.h +7 -0
  12. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  13. data/lib/contrast/agent/assess/contrast_event.rb +1 -2
  14. data/lib/contrast/agent/assess/contrast_object.rb +1 -4
  15. data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
  16. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  17. data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
  18. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
  19. data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
  20. data/lib/contrast/agent/assess/policy/propagation_method.rb +71 -142
  21. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  22. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
  23. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  24. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  25. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
  26. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
  27. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
  28. data/lib/contrast/agent/assess/policy/source_method.rb +15 -88
  29. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
  30. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -172
  31. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  32. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  33. data/lib/contrast/agent/assess/property/tagged.rb +15 -132
  34. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
  35. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  36. data/lib/contrast/agent/disable_reaction.rb +1 -1
  37. data/lib/contrast/agent/exclusion_matcher.rb +0 -4
  38. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  39. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +7 -5
  40. data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
  41. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  42. data/lib/contrast/agent/middleware.rb +23 -0
  43. data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
  44. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
  45. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  46. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  47. data/lib/contrast/agent/patching/policy/patch.rb +42 -238
  48. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  49. data/lib/contrast/agent/patching/policy/patcher.rb +10 -49
  50. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  53. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  54. data/lib/contrast/agent/reaction_processor.rb +1 -1
  55. data/lib/contrast/agent/request.rb +9 -4
  56. data/lib/contrast/agent/request_context.rb +51 -33
  57. data/lib/contrast/agent/request_handler.rb +7 -3
  58. data/lib/contrast/agent/rule_set.rb +2 -4
  59. data/lib/contrast/agent/scope.rb +32 -20
  60. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
  61. data/lib/contrast/agent/static_analysis.rb +5 -3
  62. data/lib/contrast/agent/telemetry.rb +129 -0
  63. data/lib/contrast/agent/telemetry_event.rb +34 -0
  64. data/lib/contrast/agent/thread_watcher.rb +43 -14
  65. data/lib/contrast/agent/tracepoint_hook.rb +16 -3
  66. data/lib/contrast/agent/version.rb +1 -1
  67. data/lib/contrast/agent.rb +6 -1
  68. data/lib/contrast/api/communication/messaging_queue.rb +12 -6
  69. data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
  70. data/lib/contrast/api/communication/socket_client.rb +4 -4
  71. data/lib/contrast/api/decorators/agent_startup.rb +4 -4
  72. data/lib/contrast/api/decorators/application_startup.rb +6 -5
  73. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  74. data/lib/contrast/components/agent.rb +5 -2
  75. data/lib/contrast/components/api.rb +34 -0
  76. data/lib/contrast/components/app_context.rb +24 -0
  77. data/lib/contrast/components/assess.rb +13 -3
  78. data/lib/contrast/components/base.rb +2 -2
  79. data/lib/contrast/components/config.rb +91 -11
  80. data/lib/contrast/components/contrast_service.rb +10 -2
  81. data/lib/contrast/components/logger.rb +13 -8
  82. data/lib/contrast/components/scope.rb +9 -28
  83. data/lib/contrast/config/api_configuration.rb +22 -0
  84. data/lib/contrast/config/assess_configuration.rb +1 -0
  85. data/lib/contrast/config/base_configuration.rb +14 -6
  86. data/lib/contrast/config/env_variables.rb +25 -0
  87. data/lib/contrast/config/root_configuration.rb +1 -0
  88. data/lib/contrast/config/service_configuration.rb +2 -1
  89. data/lib/contrast/config.rb +1 -0
  90. data/lib/contrast/configuration.rb +22 -15
  91. data/lib/contrast/extension/assess/array.rb +1 -11
  92. data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
  93. data/lib/contrast/extension/assess/fiber.rb +0 -11
  94. data/lib/contrast/extension/assess/hash.rb +0 -10
  95. data/lib/contrast/extension/assess/kernel.rb +1 -10
  96. data/lib/contrast/extension/assess/marshal.rb +3 -11
  97. data/lib/contrast/extension/assess/regexp.rb +0 -11
  98. data/lib/contrast/extension/assess/string.rb +1 -26
  99. data/lib/contrast/extension/extension.rb +61 -0
  100. data/lib/contrast/framework/grape/support.rb +174 -0
  101. data/lib/contrast/framework/manager.rb +56 -18
  102. data/lib/contrast/framework/rack/support.rb +1 -1
  103. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  104. data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
  105. data/lib/contrast/framework/rails/patch/support.rb +35 -30
  106. data/lib/contrast/framework/rails/railtie.rb +1 -1
  107. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
  108. data/lib/contrast/framework/rails/support.rb +60 -13
  109. data/lib/contrast/framework/sinatra/support.rb +1 -1
  110. data/lib/contrast/logger/application.rb +4 -0
  111. data/lib/contrast/logger/log.rb +89 -15
  112. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  113. data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
  114. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  115. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  116. data/lib/contrast/utils/class_util.rb +58 -44
  117. data/lib/contrast/utils/exclude_key.rb +20 -0
  118. data/lib/contrast/utils/io_util.rb +43 -35
  119. data/lib/contrast/utils/lru_cache.rb +45 -0
  120. data/lib/contrast/utils/metrics_hash.rb +59 -0
  121. data/lib/contrast/utils/os.rb +23 -0
  122. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  123. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  124. data/lib/contrast/utils/requests_client.rb +150 -0
  125. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  126. data/lib/contrast/utils/tag_util.rb +2 -1
  127. data/lib/contrast/utils/telemetry.rb +78 -0
  128. data/lib/contrast/utils/telemetry_identifier.rb +137 -0
  129. data/lib/contrast.rb +19 -1
  130. data/resources/assess/policy.json +208 -7
  131. data/resources/deadzone/policy.json +91 -0
  132. data/ruby-agent.gemspec +12 -2
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +102 -18
  137. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  138. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  139. data/lib/contrast/extension/protect/kernel.rb +0 -39
  140. data/lib/contrast/utils/inventory_util.rb +0 -113
@@ -7,60 +7,29 @@ require 'contrast/agent/assess/policy/propagator'
7
7
  require 'contrast/components/logger'
8
8
  require 'contrast/utils/object_share'
9
9
  require 'contrast/utils/sha256_builder'
10
+ require 'contrast/utils/assess/propagation_method_utils'
10
11
 
11
12
  module Contrast
12
13
  module Agent
13
14
  module Assess
14
15
  module Policy
15
- # This class is responsible for the continuation of traces. A
16
- # Propagator is any method that transforms an untrusted value. In
17
- # general, these methods work on the String class or a holder of
18
- # Strings
16
+ # This class is responsible for the continuation of traces. A Propagator is any method that transforms an
17
+ # untrusted value. In general, these methods work on the String class or a holder of Strings.
19
18
  module PropagationMethod
20
19
  extend Contrast::Components::Logger::InstanceMethods
21
-
22
-
23
- APPEND_ACTION = 'APPEND'
24
- CENTER_ACTION = 'CENTER'
25
- INSERT_ACTION = 'INSERT'
26
- KEEP_ACTION = 'KEEP'
27
- NEXT_ACTION = 'NEXT'
28
- NOOP_ACTION = 'NOOP'
29
- PREPEND_ACTION = 'PREPEND'
30
- REPLACE_ACTION = 'REPLACE'
31
- REMOVE_ACTION = 'REMOVE'
32
- REVERSE_ACTION = 'REVERSE'
33
- SPLAT_ACTION = 'SPLAT'
34
- SPLIT_ACTION = 'SPLIT'
35
- DB_WRITE_ACTION = 'DB_WRITE'
36
- CUSTOM_ACTION = 'CUSTOM'
20
+ extend Contrast::Utils::Assess::PropagationMethodUtils
37
21
 
38
22
  class << self
39
- def determine_target propagation_node, ret, object, args
40
- target = propagation_node.targets[0]
41
- case target
42
- when Contrast::Utils::ObjectShare::OBJECT_KEY
43
- object
44
- when Contrast::Utils::ObjectShare::RETURN_KEY
45
- ret
46
- else
47
- args[target]
48
- end
49
- end
50
-
51
- # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
52
- # the policy that governs the patches to this method
53
- # @param preshift [Contrast::Agent::Assess::PreShift] The capture
54
- # of the state of the code just prior to the invocation of the
55
- # patched method.
23
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that governs the
24
+ # patches to this method
25
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
26
+ # the invocation of the patched method.
56
27
  # @param object [Object] the Object on which the method was invoked
57
28
  # @param ret [Object] the Return of the invoked method
58
- # @param args [Array<Object>] the Arguments with which the method
59
- # was invoked
29
+ # @param args [Array<Object>] the Arguments with which the method was invoked
60
30
  # @param block [Block] the Block passed to the original method
61
- # @return [Object, nil] the tracked Return or nil if no changes
62
- # were made; will replace the return of the original function if
63
- # not nil
31
+ # @return [Object, nil] the tracked Return or nil if no changes were made; will replace the return of the
32
+ # original function if not nil
64
33
  def apply_propagation method_policy, preshift, object, ret, args, block
65
34
  return unless method_policy.propagation_node
66
35
  return unless preshift
@@ -71,41 +40,21 @@ module Contrast
71
40
  PropagationMethod.apply_propagator(propagation_node, preshift, target, object, ret, args, block)
72
41
  end
73
42
 
74
- PROPAGATION_ACTIONS = {
75
- APPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Append,
76
- CENTER_ACTION => Contrast::Agent::Assess::Policy::Propagator::Center,
77
- INSERT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Insert,
78
- KEEP_ACTION => Contrast::Agent::Assess::Policy::Propagator::Keep,
79
- NEXT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Next,
80
- NOOP_ACTION => nil,
81
- PREPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Prepend,
82
- REPLACE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Replace,
83
- REMOVE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Remove,
84
- REVERSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Reverse,
85
- SPLAT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Splat,
86
- SPLIT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Split
87
- }.cs__freeze
88
-
89
- # I lied above. We had to figure out what the target of the
90
- # propagation was. Now that we know, we'll actually do things to
91
- # it. Note that the return of this method will replace the original
92
- # return of the patched function unless it is nil, so be sure
93
- # you're returning what you intend.
43
+ # I lied above. We had to figure out what the target of the propagation was. Now that we know, we'll
44
+ # actually do things to it. Note that the return of this method will replace the original return of the
45
+ # patched function unless it is nil, so be sure you're returning what you intend.
94
46
  #
95
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
96
- # the node that governs this propagation event.
97
- # @param preshift [Contrast::Agent::Assess::PreShift] The capture
98
- # of the state of the code just prior to the invocation of the
99
- # patched method.
47
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
48
+ # propagation event.
49
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
50
+ # the invocation of the patched method.
100
51
  # @param target [Object] the Target to which to propagate.
101
52
  # @param object [Object] the Object on which the method was invoked
102
53
  # @param ret [Object] the Return of the invoked method
103
- # @param args [Array<Object>] the Arguments with which the method
104
- # was invoked
54
+ # @param args [Array<Object>] the Arguments with which the method was invoked
105
55
  # @param block [Block] the Block passed to the original method
106
- # @return [Object, nil] the tracked Return or nil if no changes
107
- # were made; will replace the return of the original function if
108
- # not nil
56
+ # @return [Object, nil] the tracked Return or nil if no changes were made; will replace the return of the
57
+ # original function if not nil
109
58
  def apply_propagator propagation_node, preshift, target, object, ret, args, block
110
59
  return unless propagation_possible?(propagation_node, target)
111
60
 
@@ -127,70 +76,10 @@ module Contrast
127
76
  nil
128
77
  end
129
78
 
130
- # Custom actions tend to be the more complex of our propagations.
131
- # Often, the method has to make decisions about the target based on
132
- # the context with which the method was called. As such, defer
133
- # determining if the target is valid to that method.
134
- #
135
- # In all other cases, a target is valid for propagation if it is not
136
- # nil
137
- def valid_target? target, propagation_node
138
- return true if propagation_node.action == CUSTOM_ACTION
139
-
140
- !!target
141
- end
142
-
143
- ZERO_LENGTH_ACTIONS = [DB_WRITE_ACTION, CUSTOM_ACTION, KEEP_ACTION, REPLACE_ACTION, SPLAT_ACTION].cs__freeze
144
- # If the action required needs a length and the target does not have
145
- # one, the length is not valid
146
- def valid_length? target, action
147
- return true if ZERO_LENGTH_ACTIONS.include?(action)
148
-
149
- if Contrast::Utils::DuckUtils.quacks_to?(target, :length)
150
- target.length != 0 # rubocop:disable Style/ZeroLengthPredicate
151
- else
152
- !target.to_s.empty?
153
- end
154
- end
155
-
156
- # Before we do any work, we should check if we even need to.
157
- # If the source of this patcher is not tracked, there's no need to do
158
- # anything. A copy of nothing is still nothing.
159
- def can_propagate? propagation_node, preshift, target
160
- return false unless appropriate_target?(propagation_node, target)
161
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target)
162
- return false unless preshift
163
-
164
- propagation_node.sources.each do |source|
165
- case source
166
- when Contrast::Utils::ObjectShare::OBJECT_KEY
167
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.object)
168
- else # has to be P, there's no ret source type (yet? ever?)
169
- return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source])
170
- end
171
- end
172
- false
173
- end
174
-
175
- # We cannot propagate to frozen things that have not been updated
176
- # to work with our property tracking, unless they're duplicable and
177
- # the return.
178
- # We probably shouldn't propagate to frozen things at all, as
179
- # they're supposed to be immutable, but third parties do jenky
180
- # things, so allow it as long as it is safe to do.
181
- #
182
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
183
- # the node that governs this propagation event.
184
- # @param target [Object] the Target to which to propagate.
185
- # @return [Boolean] if the target can be propagated to
186
- def appropriate_target? propagation_node, target
187
- # special handle Returns b/c we can do unfreezing magic during propagation
188
- return true if propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
189
-
190
- Contrast::Agent::Assess::Tracker.trackable?(target)
191
- end
192
-
193
79
  # If this patcher has tags, apply them to the entire target
80
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
81
+ # propagation event.
82
+ # @param target [Object] the Target to which to propagate.
194
83
  def apply_tags propagation_node, target
195
84
  return unless propagation_node.tags
196
85
  return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
@@ -202,6 +91,10 @@ module Contrast
202
91
  end
203
92
 
204
93
  # If this patcher has tags, remove them from the entire target
94
+ #
95
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
96
+ # propagation event.
97
+ # @param target [Object] the Target to which to propagate.
205
98
  def apply_untags propagation_node, target
206
99
  return unless propagation_node.untags
207
100
  return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
@@ -214,6 +107,10 @@ module Contrast
214
107
  private
215
108
 
216
109
  # This is checked right before actual propagation
110
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
111
+ # propagation event.
112
+ # @param target [Object] the Target to which to propagate.
113
+ # @return [Boolean]
217
114
  def propagation_possible? propagation_node, target
218
115
  return false unless propagation_node && valid_target?(target, propagation_node)
219
116
  return false unless valid_length?(target, propagation_node.action)
@@ -224,12 +121,24 @@ module Contrast
224
121
  # Safely duplicate the target, or return nil
225
122
  #
226
123
  # @param target [Object] the thing to check for duplication
124
+ # @return [Object, nil]
227
125
  def safe_dup target
228
126
  target.dup
229
127
  rescue StandardError => _e
230
128
  nil
231
129
  end
232
130
 
131
+ # Iterate over each key and value in a hash to allow for propagation to each.
132
+ #
133
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
134
+ # propagation event.
135
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
136
+ # the invocation of the patched method.
137
+ # @param target [Object] the Target to which to propagate.
138
+ # @param object [Object] the Object on which the method was invoked
139
+ # @param ret [Object] the Return of the invoked method
140
+ # @param args [Array<Object>] the Arguments with which the method was invoked
141
+ # @param block [Block] the Block passed to the original method
233
142
  def handle_hash_propagation propagation_node, preshift, target, object, ret, args, block
234
143
  target.each_pair do |key, value|
235
144
  apply_propagator(propagation_node, preshift, key, object, ret, args, block)
@@ -237,6 +146,17 @@ module Contrast
237
146
  end
238
147
  end
239
148
 
149
+ # Iterate over each value in an enumerable to allow for propagation to each.
150
+ #
151
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
152
+ # propagation event.
153
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
154
+ # the invocation of the patched method.
155
+ # @param target [Object] the Target to which to propagate.
156
+ # @param object [Object] the Object on which the method was invoked
157
+ # @param ret [Object] the Return of the invoked method
158
+ # @param args [Array<Object>] the Arguments with which the method was invoked
159
+ # @param block [Block] the Block passed to the original method
240
160
  def handle_enumerable_propagation propagation_node, preshift, target, object, ret, args, block
241
161
  target.each do |value|
242
162
  next if target == value
@@ -245,6 +165,17 @@ module Contrast
245
165
  end
246
166
  end
247
167
 
168
+ # Move the properties from the source(s) to the target of the propagation.
169
+ #
170
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
171
+ # propagation event.
172
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
173
+ # the invocation of the patched method.
174
+ # @param target [Object] the Target to which to propagate.
175
+ # @param object [Object] the Object on which the method was invoked
176
+ # @param ret [Object] the Return of the invoked method
177
+ # @param args [Array<Object>] the Arguments with which the method was invoked
178
+ # @param _block [Block] the Block passed to the original method
248
179
  def handle_cs_properties_propagation propagation_node, preshift, target, object, ret, args, _block
249
180
  return if propagation_node.action == NOOP_ACTION
250
181
  return unless can_propagate?(propagation_node, preshift, target)
@@ -266,12 +197,9 @@ module Contrast
266
197
  propagation_class.propagate(propagation_node, preshift, target)
267
198
  # Once we've propagated, attempt to tag the target if there is a tag(s) to be applied
268
199
  apply_tags(propagation_node, target)
269
- # Even though we skipped propagating tags from the source if they
270
- # were included in untags, the target may have already had some on
271
- # it. Let's go ahead and remove them.
272
- # In this order, untags takes precedent over tags; but we control
273
- # both and there should never be a propagator that has a tag in
274
- # its untag.
200
+ # Even though we skipped propagating tags from the source if they were included in untags, the target may
201
+ # have already had some on it. Let's go ahead and remove them. In this order, untags takes precedent over
202
+ # tags; but we control both and there should never be a propagator that has a tag in its untag.
275
203
  apply_untags(propagation_node, target)
276
204
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
277
205
 
@@ -302,7 +230,8 @@ module Contrast
302
230
  # propagation event.
303
231
  # @return [Boolean]
304
232
  def can_handle_frozen? propagation_node
305
- ::Contrast::ASSESS.track_frozen_sources? && propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
233
+ ::Contrast::ASSESS.track_frozen_sources? &&
234
+ propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
306
235
  end
307
236
  end
308
237
  end
@@ -95,8 +95,8 @@ module Contrast
95
95
 
96
96
  def needs_object?
97
97
  if @_needs_object.nil?
98
- @_needs_object = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
99
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
98
+ @_needs_object = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
99
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
100
100
  sources.any?(Contrast::Utils::ObjectShare::OBJECT_KEY) ||
101
101
  targets.any?(Contrast::Utils::ObjectShare::OBJECT_KEY)
102
102
  end
@@ -105,8 +105,8 @@ module Contrast
105
105
 
106
106
  def needs_args?
107
107
  if @_needs_args.nil?
108
- @_needs_args = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
109
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
108
+ @_needs_args = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
109
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
110
110
  sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) } ||
111
111
  targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
112
112
  end
@@ -11,10 +11,10 @@ module Contrast
11
11
  # results in new source nodes to track which columns in the database
12
12
  # have been tainted.
13
13
  class DatabaseWrite < Contrast::Agent::Assess::Policy::Propagator::Base
14
-
15
-
16
14
  class << self
17
15
  def propagate propagation_node, preshift, target
16
+ return unless Contrast::ASSESS.require_dynamic_sources?
17
+
18
18
  class_type = preshift.object.cs__class
19
19
  class_name = class_type.cs__name
20
20
  tainted_columns = {}
@@ -14,16 +14,15 @@ module Contrast
14
14
  def square_bracket_tagger propagation_node, preshift, ret, _block
15
15
  case ret
16
16
  when Array
17
- ret.each_with_index do |return_value, index|
17
+ idx = 0
18
+ while idx < ret.length
19
+ return_value = ret[idx]
20
+ index = idx
21
+ idx += 1
18
22
  next unless return_value
19
23
 
20
- target_matchdata_index = if preshift.args[0].is_a?(Range)
21
- arg_range = preshift.args[0]
22
- arg_range.to_a.empty? ? index + 1 : arg_range.to_a[index]
23
- else
24
- preshift.args[index]
25
- end
26
- square_bracket_single(target_matchdata_index, preshift, return_value, propagation_node)
24
+ square_bracket_single(target_matchdata_index(preshift, index), preshift, return_value,
25
+ propagation_node)
27
26
  end
28
27
  when String
29
28
  target_matchdata_index = preshift.args[0]
@@ -34,7 +33,11 @@ module Contrast
34
33
  end
35
34
 
36
35
  def captures_tagger propagation_node, preshift, ret, _block
37
- ret.each_with_index do |return_value, index|
36
+ idx = 0
37
+ while idx < ret.length
38
+ return_value = ret[idx]
39
+ index = idx
40
+ idx += 1
38
41
  next unless return_value
39
42
 
40
43
  targetted_index = index + 1
@@ -44,7 +47,11 @@ module Contrast
44
47
  end
45
48
 
46
49
  def to_a_tagger propagation_node, preshift, ret, _block
47
- ret.each_with_index do |return_value, index|
50
+ idx = 0
51
+ while idx < ret.length
52
+ return_value = ret[idx]
53
+ index = idx
54
+ idx += 1
48
55
  next unless return_value
49
56
 
50
57
  square_bracket_single(index, preshift, return_value, propagation_node)
@@ -53,7 +60,11 @@ module Contrast
53
60
  end
54
61
 
55
62
  def values_at_tagger propagation_node, preshift, ret, _block
56
- ret.each_with_index do |return_value, return_index|
63
+ idx = 0
64
+ while idx < ret.length
65
+ return_value = ret[idx]
66
+ return_index = idx
67
+ idx += 1
57
68
  next unless return_value
58
69
 
59
70
  original_group_arg_index = preshift.args[return_index]
@@ -64,6 +75,15 @@ module Contrast
64
75
 
65
76
  private
66
77
 
78
+ def target_matchdata_index preshift, index
79
+ if preshift.args[0].is_a?(Range)
80
+ arg_range = preshift.args[0]
81
+ arg_range.to_a.empty? ? index + 1 : arg_range.to_a[index]
82
+ else
83
+ preshift.args[index]
84
+ end
85
+ end
86
+
67
87
  def square_bracket_single argument_index, preshift, return_value, propagation_node
68
88
  original_start_index = preshift.object.begin(argument_index)
69
89
  original_end_index = preshift.object.end(argument_index)
@@ -26,7 +26,6 @@ module Contrast
26
26
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
27
27
 
28
28
  source_string = source.is_a?(String) ? source : source.to_s
29
-
30
29
  # If the lengths are the same, we should just copy the tags because nothing was removed, but a new
31
30
  # instance could have been created. copy_from will handle the case where the source is the target.
32
31
  if source_string.length == target.length
@@ -34,10 +33,7 @@ module Contrast
34
33
  return
35
34
  end
36
35
 
37
- source_chars = source_string.chars
38
36
  source_idx = 0
39
-
40
- target_chars = target.chars
41
37
  target_idx = 0
42
38
 
43
39
  remove_ranges = []
@@ -45,10 +41,9 @@ module Contrast
45
41
 
46
42
  # loop over the target, the result of the delete every range of characters that it differs from the
47
43
  # source represents a section that was deleted. these sections need to have their tags updated
48
- target_len = target_chars.length
49
- while target_idx < target_len
50
- target_char = target_chars[target_idx]
51
- source_char = source_chars[source_idx]
44
+ while target_idx < target.length
45
+ target_char = target[target_idx]
46
+ source_char = source_string[source_idx]
52
47
  if target_char == source_char
53
48
  target_idx += 1
54
49
  if start
@@ -63,7 +58,7 @@ module Contrast
63
58
 
64
59
  # once we're done looping over the target, anything left over is extra from the source that was
65
60
  # deleted. tags applying to it need to be removed.
66
- remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length
61
+ remove_ranges << (source_idx...source_string.length) if source_idx != source_string.length
67
62
 
68
63
  # handle deleting the removed ranges
69
64
  properties.delete_tags_at_ranges(remove_ranges)
@@ -17,7 +17,6 @@ module Contrast
17
17
  class Split < Contrast::Agent::Assess::Policy::Propagator::Base
18
18
  extend Contrast::Components::Scope::InstanceMethods
19
19
  extend Contrast::Components::Logger::InstanceMethods
20
- #cs__const_set('AGENT', Contrast::AGENT)
21
20
 
22
21
  SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
23
22
 
@@ -111,7 +110,9 @@ module Contrast
111
110
  # Load patch.
112
111
  def instrument_string_split
113
112
  @_instrument_string_split ||= begin
114
- require 'cs__assess_yield_track/cs__assess_yield_track' if ::Contrast::AGENT.patch_yield? && Funchook.available?
113
+ if ::Contrast::AGENT.patch_yield? && Funchook.available?
114
+ require 'cs__assess_yield_track/cs__assess_yield_track'
115
+ end
115
116
  true
116
117
  rescue StandardError => e
117
118
  logger.error('Error loading split rb_yield patch', e)
@@ -16,6 +16,7 @@ module Contrast
16
16
  # a 'get it right' state soon.
17
17
  class Substitution
18
18
  include Contrast::Components::Logger::InstanceMethods
19
+ extend Contrast::Components::Logger::InstanceMethods
19
20
 
20
21
  CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
21
22
  CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
@@ -22,7 +22,6 @@ module Contrast
22
22
  module RewriterPatch
23
23
  extend Contrast::Components::Logger::InstanceMethods
24
24
 
25
-
26
25
  class << self
27
26
  def rewrite_interpolations
28
27
  return unless agent_should_rewrite?
@@ -6,6 +6,7 @@ require 'contrast/agent/assess/policy/source_validation/source_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/source_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
@@ -16,7 +17,7 @@ module Contrast
16
17
  # used in Assess vulnerability detection.
17
18
  module SourceMethod
18
19
  extend Contrast::Components::Logger::InstanceMethods
19
-
20
+ extend Contrast::Utils::Assess::SourceMethodUtils
20
21
 
21
22
  PARAMETER_TYPE = 'PARAMETER'
22
23
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
@@ -26,8 +27,7 @@ module Contrast
26
27
  COOKIE_KEY_TYPE = 'COOKIE_KEY'
27
28
 
28
29
  class << self
29
- # This is called from within our woven proc. It will be called as if it were inline in the Rack
30
- # application.
30
+ # This is called from within our woven proc. It will be called as if it were inline in the Rack application.
31
31
  #
32
32
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
33
33
  # method being called
@@ -37,29 +37,27 @@ module Contrast
37
37
  # @return [Object, nil] the tracked Return or nil if no changes were made
38
38
  def source_patchers method_policy, object, ret, args
39
39
  return unless analyze?(method_policy, object, ret, args)
40
+ return unless (source_node = method_policy.source_node)
41
+ return unless (target = determine_target(source_node, object, ret, args))
40
42
 
41
- source_node = method_policy.source_node
42
- target = determine_target(source_node, object, ret, args)
43
- restore_frozen_state = false
43
+ return_val = nil
44
44
  if target.cs__frozen? && !Contrast::Agent::Assess::Tracker.trackable?(target)
45
45
  return unless ::Contrast::ASSESS.track_frozen_sources?
46
46
  return unless source_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
47
+ return unless (dup = safe_dup(ret))
48
+ return unless Contrast::Agent::Assess::Tracker.trackable?(dup)
47
49
 
48
- dup = safe_dup(ret)
49
- return unless dup
50
-
51
- restore_frozen_state = true
52
- ret = dup
53
- target = ret
54
- Contrast::Agent::Assess::Tracker.pre_freeze(ret)
55
- ret.cs__freeze
56
- # double check that we were able to finalize the replaced return
57
- return unless Contrast::Agent::Assess::Tracker.trackable?(target)
50
+ Contrast::Agent::Assess::Tracker.pre_freeze(dup)
51
+ return_val = dup.cs__freeze
52
+ target = dup
58
53
  end
54
+
59
55
  apply_source(Contrast::Agent::REQUEST_TRACKER.current, source_node, target, object, ret,
60
56
  source_node.type, nil, *args)
61
- restore_frozen_state ? ret : nil
57
+
58
+ return_val
62
59
  end
60
+ Contrast::Components::Logger.add_trace_log_timing_for(SourceMethod, :source_patchers)
63
61
 
64
62
  private
65
63
 
@@ -137,15 +135,6 @@ module Contrast
137
135
  !Contrast::Agent::Assess::Tracker.trackable?(key)
138
136
  end
139
137
 
140
- # Safely duplicate the target, or return nil
141
- #
142
- # @param target [Object] the thing to check for duplication
143
- def safe_dup target
144
- target.dup
145
- rescue StandardError => _e
146
- nil
147
- end
148
-
149
138
  # Hash is designed to keep one instance of the string key in it. We need to remove the existing one and
150
139
  # replace it with our new tracked one.
151
140
  def handle_hash_key target, to_replace
@@ -178,68 +167,6 @@ module Contrast
178
167
  properties.build_event(source_node, target, object, ret, args, source_type, source_name)
179
168
  end
180
169
 
181
- # Find the name of the source
182
- #
183
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
184
- # event
185
- # @param object [Object] the Object on which the method was invoked
186
- # @param ret [Object] the Return of the invoked method
187
- # @param args [Array<Object>] the Arguments with which the method was invoked
188
- # @return [String, nil] the human readable name of the target to which this source event applies, or nil if
189
- # none provided by the node
190
- def determine_source_name source_node, object, ret, *args
191
- return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE'
192
-
193
- source_node_source = source_node.sources[0]
194
- case source_node_source
195
- when nil
196
- nil
197
- when Contrast::Utils::ObjectShare::RETURN_KEY
198
- ret
199
- when Contrast::Utils::ObjectShare::OBJECT_KEY
200
- object
201
- else
202
- args[source_node_source]
203
- end
204
- end
205
-
206
- # Determine if we should analyze this method invocation for a Source or not. We should if we have enough
207
- # information to build the context of this invocation, we're not disabled, and we can't immediately
208
- # determine the invocation was done safely.
209
- #
210
- # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that applies to the
211
- # method being called
212
- # @param object [Object] the Object on which the method was invoked
213
- # @param ret [Object] the Return of the invoked method
214
- # @param args [Array<Object>] the Arguments with which the method was invoked
215
- # @return [boolean] if the invocation of this method should be analyzed
216
- def analyze? method_policy, object, ret, args
217
- return false unless method_policy&.source_node
218
- return false unless ::Contrast::ASSESS.enabled?
219
- return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request?
220
-
221
- !safe_invocation?(method_policy.source_node, object, ret, args)
222
- end
223
-
224
- # Determine if the method was invoked safely.
225
- #
226
- # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
227
- # event
228
- # @param _object [Object] the Object on which the method was invoked
229
- # @param _ret [Object] the Return of the invoked method
230
- # @param args [Array<Object>] the Arguments with which the method was invoked
231
- # @return [boolean] if the invocation of this method was safe
232
- def safe_invocation? source_node, _object, _ret, args
233
- # According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header
234
- # from the Request will start with HTTP_. As such, only Headers with that key should be considered for
235
- # tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for
236
- # instance, uses action_dispatch. to store several values. Technically, you can't call
237
- # Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one.
238
- source_node.id == 'Assess:Source:Rack::Request::Env#get_header' &&
239
- args&.any? &&
240
- !args[0].to_s.start_with?('HTTP_')
241
- end
242
-
243
170
  # Find the literal target of the propagation
244
171
  #
245
172
  # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
-
5
4
  module Contrast
6
5
  module Agent
7
6
  module Assess