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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rspec_parallel +6 -0
- data/ext/cs__assess_module/cs__assess_module.c +48 -0
- data/ext/cs__assess_module/cs__assess_module.h +7 -0
- data/ext/cs__common/cs__common.c +24 -7
- data/ext/cs__common/cs__common.h +12 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -12
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
- data/ext/cs__os_information/cs__os_information.c +31 -0
- data/ext/cs__os_information/cs__os_information.h +7 -0
- data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
- data/lib/contrast/agent/assess/contrast_event.rb +1 -2
- data/lib/contrast/agent/assess/contrast_object.rb +1 -4
- data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
- data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
- data/lib/contrast/agent/assess/policy/propagation_method.rb +71 -142
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
- data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
- data/lib/contrast/agent/assess/policy/source_method.rb +15 -88
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -172
- data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
- data/lib/contrast/agent/assess/property/evented.rb +2 -1
- data/lib/contrast/agent/assess/property/tagged.rb +15 -132
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/disable_reaction.rb +1 -1
- data/lib/contrast/agent/exclusion_matcher.rb +0 -4
- data/lib/contrast/agent/inventory/database_config.rb +117 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +7 -5
- data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
- data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
- data/lib/contrast/agent/middleware.rb +23 -0
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
- data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
- data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
- data/lib/contrast/agent/patching/policy/patch.rb +42 -238
- data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
- data/lib/contrast/agent/patching/policy/patcher.rb +10 -49
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
- data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/request.rb +9 -4
- data/lib/contrast/agent/request_context.rb +51 -33
- data/lib/contrast/agent/request_handler.rb +7 -3
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/scope.rb +32 -20
- data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
- data/lib/contrast/agent/static_analysis.rb +5 -3
- data/lib/contrast/agent/telemetry.rb +129 -0
- data/lib/contrast/agent/telemetry_event.rb +34 -0
- data/lib/contrast/agent/thread_watcher.rb +43 -14
- data/lib/contrast/agent/tracepoint_hook.rb +16 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -1
- data/lib/contrast/api/communication/messaging_queue.rb +12 -6
- data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
- data/lib/contrast/api/communication/socket_client.rb +4 -4
- data/lib/contrast/api/decorators/agent_startup.rb +4 -4
- data/lib/contrast/api/decorators/application_startup.rb +6 -5
- data/lib/contrast/api/decorators/route_coverage.rb +24 -1
- data/lib/contrast/components/agent.rb +5 -2
- data/lib/contrast/components/api.rb +34 -0
- data/lib/contrast/components/app_context.rb +24 -0
- data/lib/contrast/components/assess.rb +13 -3
- data/lib/contrast/components/base.rb +2 -2
- data/lib/contrast/components/config.rb +91 -11
- data/lib/contrast/components/contrast_service.rb +10 -2
- data/lib/contrast/components/logger.rb +13 -8
- data/lib/contrast/components/scope.rb +9 -28
- data/lib/contrast/config/api_configuration.rb +22 -0
- data/lib/contrast/config/assess_configuration.rb +1 -0
- data/lib/contrast/config/base_configuration.rb +14 -6
- data/lib/contrast/config/env_variables.rb +25 -0
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/service_configuration.rb +2 -1
- data/lib/contrast/config.rb +1 -0
- data/lib/contrast/configuration.rb +22 -15
- data/lib/contrast/extension/assess/array.rb +1 -11
- data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
- data/lib/contrast/extension/assess/fiber.rb +0 -11
- data/lib/contrast/extension/assess/hash.rb +0 -10
- data/lib/contrast/extension/assess/kernel.rb +1 -10
- data/lib/contrast/extension/assess/marshal.rb +3 -11
- data/lib/contrast/extension/assess/regexp.rb +0 -11
- data/lib/contrast/extension/assess/string.rb +1 -26
- data/lib/contrast/extension/extension.rb +61 -0
- data/lib/contrast/framework/grape/support.rb +174 -0
- data/lib/contrast/framework/manager.rb +56 -18
- data/lib/contrast/framework/rack/support.rb +1 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
- data/lib/contrast/framework/rails/patch/support.rb +35 -30
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
- data/lib/contrast/framework/rails/support.rb +60 -13
- data/lib/contrast/framework/sinatra/support.rb +1 -1
- data/lib/contrast/logger/application.rb +4 -0
- data/lib/contrast/logger/log.rb +89 -15
- data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
- data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
- data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
- data/lib/contrast/utils/class_util.rb +58 -44
- data/lib/contrast/utils/exclude_key.rb +20 -0
- data/lib/contrast/utils/io_util.rb +43 -35
- data/lib/contrast/utils/lru_cache.rb +45 -0
- data/lib/contrast/utils/metrics_hash.rb +59 -0
- data/lib/contrast/utils/os.rb +23 -0
- data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
- data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
- data/lib/contrast/utils/requests_client.rb +150 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
- data/lib/contrast/utils/tag_util.rb +2 -1
- data/lib/contrast/utils/telemetry.rb +78 -0
- data/lib/contrast/utils/telemetry_identifier.rb +137 -0
- data/lib/contrast.rb +19 -1
- data/resources/assess/policy.json +208 -7
- data/resources/deadzone/policy.json +91 -0
- data/ruby-agent.gemspec +12 -2
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +102 -18
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/lib/contrast/extension/protect/kernel.rb +0 -39
- 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
|
-
#
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
#
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
#
|
97
|
-
# @param preshift [Contrast::Agent::Assess::PreShift] The capture
|
98
|
-
#
|
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
|
-
#
|
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
|
-
#
|
271
|
-
#
|
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? &&
|
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::
|
99
|
-
action == Contrast::
|
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::
|
109
|
-
action == Contrast::
|
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
|
-
|
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
|
21
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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...
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
49
|
-
|
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
|
-
|
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
|