contrast-agent 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
- data/lib/contrast/agent/assess/contrast_event.rb +49 -130
- data/lib/contrast/agent/assess/contrast_object.rb +51 -0
- data/lib/contrast/agent/assess/events/source_event.rb +4 -9
- data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
- data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
- data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
- data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
- data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
- data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
- data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +21 -15
- data/lib/contrast/agent/assess/rule/redos.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +16 -18
- data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
- data/lib/contrast/agent/middleware.rb +50 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
- data/lib/contrast/agent/patching/policy/patch.rb +4 -4
- data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
- data/lib/contrast/agent/protect/rule/base.rb +63 -14
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +3 -3
- data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
- data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
- data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/response.rb +5 -5
- data/lib/contrast/agent/rewriter.rb +3 -3
- data/lib/contrast/agent/scope.rb +33 -13
- data/lib/contrast/agent/static_analysis.rb +13 -7
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators/library.rb +1 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +19 -31
- data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
- data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
- data/lib/contrast/api/decorators/user_input.rb +2 -1
- data/lib/contrast/common_agent_configuration.rb +1 -1
- data/lib/contrast/components/assess.rb +36 -0
- data/lib/contrast/components/interface.rb +5 -3
- data/lib/contrast/components/scope.rb +23 -0
- data/lib/contrast/components/settings.rb +3 -3
- data/lib/contrast/config/assess_configuration.rb +2 -1
- data/lib/contrast/extension/assess/array.rb +1 -2
- data/lib/contrast/extension/assess/erb.rb +1 -3
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
- data/lib/contrast/extension/assess/fiber.rb +2 -3
- data/lib/contrast/extension/assess/hash.rb +4 -2
- data/lib/contrast/extension/assess/kernel.rb +1 -2
- data/lib/contrast/extension/assess/marshal.rb +34 -26
- data/lib/contrast/extension/assess/regexp.rb +3 -8
- data/lib/contrast/extension/assess/string.rb +1 -2
- data/lib/contrast/framework/base_support.rb +51 -53
- data/lib/contrast/framework/manager.rb +3 -2
- data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
- data/lib/contrast/framework/rack/support.rb +2 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
- data/lib/contrast/framework/rails/support.rb +2 -1
- data/lib/contrast/framework/sinatra/support.rb +3 -2
- data/lib/contrast/logger/application.rb +0 -3
- data/lib/contrast/utils/duck_utils.rb +1 -1
- data/lib/contrast/utils/heap_dump_util.rb +1 -1
- data/lib/contrast/utils/object_share.rb +3 -3
- data/lib/contrast/utils/preflight_util.rb +1 -1
- data/lib/contrast/utils/prevent_serialization.rb +1 -1
- data/lib/contrast/utils/resource_loader.rb +1 -1
- data/lib/contrast/utils/sha256_builder.rb +2 -2
- data/lib/contrast/utils/string_utils.rb +1 -1
- data/lib/contrast/utils/tag_util.rb +9 -13
- data/resources/assess/policy.json +9 -9
- data/resources/deadzone/policy.json +156 -0
- data/resources/protect/policy.json +12 -0
- data/ruby-agent.gemspec +9 -6
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +68 -25
|
@@ -36,11 +36,11 @@ module Contrast
|
|
|
36
36
|
# the state of the object and arguments just prior to the method
|
|
37
37
|
# being called or nil if one is not required.
|
|
38
38
|
def build_preshift propagation_node, object, args
|
|
39
|
-
return
|
|
40
|
-
return
|
|
39
|
+
return unless propagation_node
|
|
40
|
+
return unless ASSESS.enabled?
|
|
41
41
|
|
|
42
42
|
initializing = propagation_node.method_name == :initialize
|
|
43
|
-
return
|
|
43
|
+
return if unsafe_io_object?(object, initializing)
|
|
44
44
|
|
|
45
45
|
needs_object = propagation_node.needs_object?
|
|
46
46
|
needs_args = propagation_node.needs_args?
|
|
@@ -37,20 +37,15 @@ module Contrast
|
|
|
37
37
|
|
|
38
38
|
class << self
|
|
39
39
|
def determine_target propagation_node, ret, object, args
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
next unless search.is_a?(Hash)
|
|
49
|
-
|
|
50
|
-
arg = search[target_key]
|
|
51
|
-
break if arg
|
|
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]
|
|
52
48
|
end
|
|
53
|
-
arg
|
|
54
49
|
end
|
|
55
50
|
|
|
56
51
|
# @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
|
|
@@ -208,8 +203,8 @@ module Contrast
|
|
|
208
203
|
# If this patcher has tags, apply them to the entire target
|
|
209
204
|
def apply_tags propagation_node, target
|
|
210
205
|
return unless propagation_node.tags
|
|
206
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
|
|
211
207
|
|
|
212
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
213
208
|
length = Contrast::Utils::StringUtils.ret_length(target)
|
|
214
209
|
propagation_node.tags.each do |tag|
|
|
215
210
|
properties.add_tag(tag, 0...length)
|
|
@@ -219,9 +214,7 @@ module Contrast
|
|
|
219
214
|
# If this patcher has tags, remove them from the entire target
|
|
220
215
|
def apply_untags propagation_node, target
|
|
221
216
|
return unless propagation_node.untags
|
|
222
|
-
|
|
223
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
224
|
-
return unless properties
|
|
217
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
|
|
225
218
|
|
|
226
219
|
propagation_node.untags.each do |tag|
|
|
227
220
|
properties.delete_tags(tag)
|
|
@@ -256,7 +249,7 @@ module Contrast
|
|
|
256
249
|
|
|
257
250
|
def handle_enumerable_propagation propagation_node, preshift, target, object, ret, args, block
|
|
258
251
|
target.each do |value|
|
|
259
|
-
next if target == value # Some Enumerable#each are
|
|
252
|
+
next if target == value # Some Enumerable#each are overridden to return self the first time which leads to infinite propagation
|
|
260
253
|
|
|
261
254
|
apply_propagator(propagation_node, preshift, value, object, ret, args, block)
|
|
262
255
|
end
|
|
@@ -300,7 +293,8 @@ module Contrast
|
|
|
300
293
|
# both and there should never be a propagator that has a tag in
|
|
301
294
|
# its untag.
|
|
302
295
|
apply_untags(propagation_node, target)
|
|
303
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
296
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
297
|
+
|
|
304
298
|
properties.add_properties(propagation_node.properties)
|
|
305
299
|
properties.build_event(propagation_node, target, object, ret, args)
|
|
306
300
|
logger.trace('Propagation detected',
|
|
@@ -83,35 +83,23 @@ module Contrast
|
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def needs_object?
|
|
86
|
-
@_needs_object
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
elsif sources.any? { |source| source == Contrast::Utils::ObjectShare::OBJECT_KEY }
|
|
92
|
-
true
|
|
93
|
-
elsif targets.any? { |target| target == Contrast::Utils::ObjectShare::OBJECT_KEY }
|
|
94
|
-
true
|
|
95
|
-
else
|
|
96
|
-
false
|
|
97
|
-
end
|
|
86
|
+
if @_needs_object.nil?
|
|
87
|
+
@_needs_object = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
|
|
88
|
+
action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
|
|
89
|
+
sources.any? { |source| source == Contrast::Utils::ObjectShare::OBJECT_KEY } ||
|
|
90
|
+
targets.any? { |target| target == Contrast::Utils::ObjectShare::OBJECT_KEY }
|
|
98
91
|
end
|
|
92
|
+
@_needs_object
|
|
99
93
|
end
|
|
100
94
|
|
|
101
95
|
def needs_args?
|
|
102
|
-
@_needs_args
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
elsif sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) }
|
|
108
|
-
true
|
|
109
|
-
elsif targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
|
|
110
|
-
true
|
|
111
|
-
else
|
|
112
|
-
false
|
|
113
|
-
end
|
|
96
|
+
if @_needs_args.nil?
|
|
97
|
+
@_needs_args = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
|
|
98
|
+
action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
|
|
99
|
+
sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) } ||
|
|
100
|
+
targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
|
|
114
101
|
end
|
|
102
|
+
@_needs_args
|
|
115
103
|
end
|
|
116
104
|
|
|
117
105
|
# This is a tagger if it has a tag or an untag.
|
|
@@ -16,8 +16,7 @@ module Contrast
|
|
|
16
16
|
# copy tags from the param to the target in chunks of param size or less
|
|
17
17
|
# if param is appended in space less than param length
|
|
18
18
|
def propagate propagation_node, preshift, target
|
|
19
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
20
|
-
return unless properties
|
|
19
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
21
20
|
|
|
22
21
|
sources = propagation_node.sources
|
|
23
22
|
source1 = find_source(sources[0], preshift)
|
|
@@ -12,8 +12,7 @@ module Contrast
|
|
|
12
12
|
class Center < Contrast::Agent::Assess::Policy::Propagator::Base
|
|
13
13
|
class << self
|
|
14
14
|
def propagate propagation_node, preshift, target
|
|
15
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
16
|
-
return unless properties
|
|
15
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
17
16
|
|
|
18
17
|
sources = propagation_node.sources
|
|
19
18
|
source1 = find_source(sources[0], preshift)
|
|
@@ -12,7 +12,7 @@ module Contrast
|
|
|
12
12
|
# of tags from the source to the target. Each node with the CUSTOM
|
|
13
13
|
# action knows the class and method it should call to preform this
|
|
14
14
|
# action.
|
|
15
|
-
|
|
15
|
+
module Custom
|
|
16
16
|
class << self
|
|
17
17
|
def propagate propagation_node, preshift, ret, block
|
|
18
18
|
clazz = propagation_node.patch_class
|
|
@@ -30,9 +30,7 @@ module Contrast
|
|
|
30
30
|
arg.each_pair do |key, value|
|
|
31
31
|
next unless value
|
|
32
32
|
next if known_tainted&.include?(key)
|
|
33
|
-
|
|
34
|
-
properties = Contrast::Agent::Assess::Tracker.properties(value)
|
|
35
|
-
next unless properties
|
|
33
|
+
next unless (properties = Contrast::Agent::Assess::Tracker.properties!(value))
|
|
36
34
|
|
|
37
35
|
# TODO: RUBY-540 handle sanitization, handle nested objects
|
|
38
36
|
Contrast::Agent::Assess::Policy::PropagationMethod.apply_tags(propagation_node, value)
|
|
@@ -16,8 +16,7 @@ module Contrast
|
|
|
16
16
|
# Unlike additive propagation, this currently only supports one source
|
|
17
17
|
# We assume that insert changes the preshift target
|
|
18
18
|
def propagate propagation_node, preshift, target
|
|
19
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
20
|
-
return unless properties
|
|
19
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
21
20
|
|
|
22
21
|
source = find_source(propagation_node.sources[1], preshift)
|
|
23
22
|
|
|
@@ -14,8 +14,7 @@ module Contrast
|
|
|
14
14
|
# Keep means the tags just pass from the source to the target
|
|
15
15
|
# as is.
|
|
16
16
|
def propagate propagation_node, preshift, target
|
|
17
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
18
|
-
return unless properties
|
|
17
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
19
18
|
|
|
20
19
|
source = find_source(propagation_node.sources[0], preshift)
|
|
21
20
|
properties.copy_from(source, target, 0, propagation_node.untags)
|
|
@@ -67,11 +67,12 @@ module Contrast
|
|
|
67
67
|
def square_bracket_single argument_index, preshift, return_value, propagation_node
|
|
68
68
|
original_start_index = preshift.object.begin(argument_index)
|
|
69
69
|
original_end_index = preshift.object.end(argument_index)
|
|
70
|
-
original_properties = Contrast::Agent::Assess::Tracker.properties(preshift.object)
|
|
70
|
+
return unless (original_properties = Contrast::Agent::Assess::Tracker.properties(preshift.object))
|
|
71
|
+
|
|
71
72
|
applicable_tags = original_properties.tags_at_range(original_start_index...original_end_index)
|
|
72
73
|
return if applicable_tags.empty?
|
|
74
|
+
return unless (return_properties = Contrast::Agent::Assess::Tracker.properties!(return_value))
|
|
73
75
|
|
|
74
|
-
return_properties = Contrast::Agent::Assess::Tracker.properties(return_value)
|
|
75
76
|
applicable_tags.each do |tag_name, tag_ranges|
|
|
76
77
|
return_properties.set_tags(tag_name, tag_ranges)
|
|
77
78
|
end
|
|
@@ -15,8 +15,7 @@ module Contrast
|
|
|
15
15
|
# String has some silly methods like next. Basically, this flips a
|
|
16
16
|
# character in a predictable manner
|
|
17
17
|
def propagate propagation_node, preshift, target
|
|
18
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
19
|
-
return unless properties
|
|
18
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
20
19
|
|
|
21
20
|
source = find_source(propagation_node.sources[0], preshift)
|
|
22
21
|
properties.copy_from(source, target, 0, propagation_node.untags)
|
|
@@ -14,8 +14,7 @@ module Contrast
|
|
|
14
14
|
# For the source, prepend its tags to the target. It's basically the
|
|
15
15
|
# opposite of append. :-P
|
|
16
16
|
def propagate propagation_node, preshift, target
|
|
17
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
18
|
-
return unless properties
|
|
17
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
19
18
|
|
|
20
19
|
sources = propagation_node.sources
|
|
21
20
|
# source1 is the copy of the thing being prepended to
|
|
@@ -17,8 +17,7 @@ module Contrast
|
|
|
17
17
|
# Once the tag is applied, remove the section that was removed by the delete.
|
|
18
18
|
# Unlike additive propagation, this currently only supports one source
|
|
19
19
|
def propagate propagation_node, preshift, target
|
|
20
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
21
|
-
return unless properties
|
|
20
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
22
21
|
|
|
23
22
|
source = find_source(propagation_node.sources[0], preshift)
|
|
24
23
|
properties.copy_from(source, target, 0, propagation_node.untags)
|
|
@@ -27,8 +26,7 @@ module Contrast
|
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def handle_removal source_chars, target
|
|
30
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
31
|
-
return unless properties
|
|
29
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
32
30
|
|
|
33
31
|
source_idx = 0
|
|
34
32
|
|
|
@@ -14,8 +14,7 @@ module Contrast
|
|
|
14
14
|
# Replace means we're replacing the target w/ the source. Anything
|
|
15
15
|
# on the source should be passed to the target.
|
|
16
16
|
def propagate propagation_node, preshift, target
|
|
17
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
18
|
-
return unless properties
|
|
17
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
19
18
|
|
|
20
19
|
source = find_source(propagation_node.sources[0], preshift)
|
|
21
20
|
properties.clear_tags
|
|
@@ -13,8 +13,7 @@ module Contrast
|
|
|
13
13
|
class Reverse < Contrast::Agent::Assess::Policy::Propagator::Base
|
|
14
14
|
class << self
|
|
15
15
|
def propagate propagation_node, preshift, target
|
|
16
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
17
|
-
return unless properties
|
|
16
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
18
17
|
|
|
19
18
|
source = find_source(propagation_node.sources[0], preshift)
|
|
20
19
|
properties.copy_from(source, target, 0, propagation_node.untags)
|
|
@@ -14,9 +14,6 @@ module Contrast
|
|
|
14
14
|
class Select
|
|
15
15
|
class << self
|
|
16
16
|
def select_tagger patcher, preshift, ret, _block
|
|
17
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
18
|
-
return unless properties
|
|
19
|
-
|
|
20
17
|
source = preshift.object
|
|
21
18
|
args = preshift.args
|
|
22
19
|
|
|
@@ -31,7 +28,9 @@ module Contrast
|
|
|
31
28
|
return
|
|
32
29
|
end
|
|
33
30
|
|
|
34
|
-
source_properties = Contrast::Agent::Assess::Tracker.properties(source)
|
|
31
|
+
return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
|
|
32
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
33
|
+
|
|
35
34
|
properties.build_event(
|
|
36
35
|
patcher,
|
|
37
36
|
ret,
|
|
@@ -36,14 +36,12 @@ module Contrast
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
splat_tags(tracked_inputs, target)
|
|
39
|
-
Contrast::Agent::Assess::Tracker.properties(target)
|
|
39
|
+
Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def splat_tags tracked_inputs, target
|
|
43
43
|
return if tracked_inputs.empty?
|
|
44
|
-
|
|
45
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
46
|
-
return unless properties
|
|
44
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
47
45
|
|
|
48
46
|
tracked_inputs.each do |input|
|
|
49
47
|
input_properties = Contrast::Agent::Assess::Tracker.properties(input)
|
|
@@ -15,13 +15,12 @@ module Contrast
|
|
|
15
15
|
class Split < Contrast::Agent::Assess::Policy::Propagator::Base
|
|
16
16
|
include Contrast::Components::Interface
|
|
17
17
|
|
|
18
|
-
access_component :agent, :logging
|
|
18
|
+
access_component :agent, :logging, :scope
|
|
19
19
|
|
|
20
20
|
SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
|
|
21
|
+
|
|
21
22
|
class << self
|
|
22
|
-
# Propagate taint from a source as it is split into composite
|
|
23
|
-
# sections. This method MUST return nil, otherwise it risks
|
|
24
|
-
# changing the result of of the propagation.
|
|
23
|
+
# Propagate taint from a source as it is split into composite sections.
|
|
25
24
|
#
|
|
26
25
|
# @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
|
|
27
26
|
# the node that governs this propagation event.
|
|
@@ -29,171 +28,132 @@ module Contrast
|
|
|
29
28
|
# of the state of the code just prior to the invocation of the
|
|
30
29
|
# patched method.
|
|
31
30
|
# @param target [Array, String] the target to which to propagate.
|
|
32
|
-
# @return [nil]
|
|
31
|
+
# @return [nil] so as not to risk changing the result of the propagation.
|
|
32
|
+
|
|
33
33
|
def propagate propagation_node, preshift, target
|
|
34
34
|
logger.trace('Propagation detected',
|
|
35
35
|
node_id: propagation_node.id,
|
|
36
36
|
target_id: target.__id__)
|
|
37
|
-
unless target.is_a?(Array)
|
|
38
|
-
Contrast::Agent::Assess::Policy::Propagator::Keep.propagate(propagation_node, preshift, target)
|
|
39
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
40
|
-
properties.build_event(propagation_node, target, object, ret, args)
|
|
41
|
-
return
|
|
42
|
-
end
|
|
43
37
|
|
|
44
38
|
source = find_source(propagation_node.sources[0], preshift)
|
|
39
|
+
return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
|
|
45
40
|
|
|
41
|
+
# grapheme_clusters break the string apart based on each "user-perceived" character.
|
|
42
|
+
# Otherwise, the default for String#split is to use a single whitespace.
|
|
46
43
|
separator_length = if propagation_node.method_name == :grapheme_clusters
|
|
47
|
-
# grapheme_clusters break the string apart based on each "user-perceived" character
|
|
48
44
|
0
|
|
49
45
|
else
|
|
50
|
-
# The default for String#split is to use a single whitespace
|
|
51
46
|
preshift&.args&.first&.to_s&.length || $FIELD_SEPARATOR&.to_s&.length || 1
|
|
52
47
|
end
|
|
53
48
|
|
|
54
49
|
current_index = 0
|
|
55
|
-
target.each do |
|
|
56
|
-
|
|
57
|
-
range = current_index...(current_index + elem_length)
|
|
58
|
-
elem_properties = Contrast::Agent::Assess::Tracker.properties(elem)
|
|
59
|
-
next unless elem_properties
|
|
50
|
+
target.each do |target_elem|
|
|
51
|
+
next unless (elem_properties = Contrast::Agent::Assess::Tracker.properties!(target_elem))
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
# Get tags for element from source by element range.
|
|
54
|
+
range = current_index...(current_index + target_elem.length)
|
|
62
55
|
tags = source_properties.tags_at_range(range)
|
|
56
|
+
|
|
57
|
+
# Set element properties accordingly.
|
|
63
58
|
elem_properties.clear_tags
|
|
64
|
-
tags.each_pair
|
|
65
|
-
|
|
66
|
-
end
|
|
67
|
-
elem_properties.build_event(propagation_node, elem, preshift.object, target, preshift.args, 0)
|
|
59
|
+
tags.each_pair { |key, value| elem_properties.set_tags(key, value) }
|
|
60
|
+
elem_properties.build_event(propagation_node, target_elem, preshift.object, target, preshift.args, 0)
|
|
68
61
|
elem_properties.add_properties(propagation_node.properties)
|
|
69
|
-
|
|
62
|
+
|
|
63
|
+
current_index = range.end + separator_length
|
|
70
64
|
end
|
|
71
65
|
nil
|
|
72
66
|
end
|
|
73
67
|
|
|
74
|
-
#
|
|
75
|
-
# Responsible for building the context required to propagate when
|
|
76
|
-
# the results of #split are yielded directly to a block
|
|
68
|
+
# Context for block split execution.
|
|
77
69
|
#
|
|
78
|
-
# @param string [String] the String on which split is invoked
|
|
79
|
-
# @param args [Array<Object>] the arguments passed to the
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
logger.warn('Unable to record split context', e)
|
|
90
|
-
end_split
|
|
91
|
-
end
|
|
70
|
+
# @param string [String] the String on which split is invoked.
|
|
71
|
+
# @param args [Array<Object>] the arguments passed to the original split call.
|
|
72
|
+
def wrap_split string, args
|
|
73
|
+
# String#split start. Build context and yield.
|
|
74
|
+
begin
|
|
75
|
+
enter_split_scope!
|
|
76
|
+
save_split_index!
|
|
77
|
+
save_split_value!(string, args)
|
|
78
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
79
|
+
logger.warn('Unable to record split context', e)
|
|
80
|
+
end
|
|
92
81
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
SPLIT_TRACKER.delete(:split_value)
|
|
105
|
-
else
|
|
106
|
-
SPLIT_TRACKER.set(:split_depth, depth)
|
|
82
|
+
yield
|
|
83
|
+
ensure
|
|
84
|
+
# String#split exit. Remove propagation context.
|
|
85
|
+
begin
|
|
86
|
+
exit_split_scope!
|
|
87
|
+
unless in_split_scope?
|
|
88
|
+
SPLIT_TRACKER.delete(:split_index)
|
|
89
|
+
SPLIT_TRACKER.delete(:split_value)
|
|
90
|
+
end
|
|
91
|
+
rescue StandardError => e
|
|
92
|
+
logger.warn('Unable to remove split context', e)
|
|
107
93
|
end
|
|
108
|
-
rescue StandardError => e
|
|
109
|
-
logger.warn('Unable to remove split context', e)
|
|
110
94
|
end
|
|
111
95
|
|
|
112
|
-
# This method is called whenever an rb_yield is called.
|
|
113
|
-
# to leave it as soon as possible with as little work as
|
|
114
|
-
# possible.
|
|
96
|
+
# This method is called whenever an rb_yield is called.
|
|
97
|
+
# We need to leave it as soon as possible with as little work as possible.
|
|
115
98
|
#
|
|
116
|
-
# @param target [String] the entity being passed to the yield
|
|
117
|
-
# block
|
|
99
|
+
# @param target [String] the entity being passed to the yield block
|
|
118
100
|
def propagate_yield target
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return unless depth
|
|
123
|
-
|
|
124
|
-
source = SPLIT_TRACKER.get(:split_value)&.fetch(depth)
|
|
125
|
-
return unless source
|
|
126
|
-
|
|
127
|
-
index = SPLIT_TRACKER.get(:split_index)&.fetch(depth)
|
|
128
|
-
return unless index
|
|
129
|
-
|
|
130
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
131
|
-
return unless properties
|
|
101
|
+
return unless (source = SPLIT_TRACKER.get(:split_value)&.fetch(split_scope_depth))
|
|
102
|
+
return unless (index = SPLIT_TRACKER.get(:split_index)&.fetch(split_scope_depth))
|
|
103
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
132
104
|
|
|
133
105
|
true_source = source[index]
|
|
134
106
|
properties.copy_from(true_source, target)
|
|
135
107
|
rescue StandardError => e
|
|
136
108
|
logger.warn('Unable to track within split context', e)
|
|
137
109
|
ensure
|
|
138
|
-
if
|
|
110
|
+
if in_split_scope? && index
|
|
139
111
|
idx = SPLIT_TRACKER.get(:split_index)
|
|
140
|
-
idx[
|
|
112
|
+
idx[split_scope_depth] = index + 1 if defined?(idx) && idx.is_a?(Array)
|
|
141
113
|
end
|
|
142
114
|
end
|
|
143
115
|
|
|
116
|
+
# Load patch.
|
|
144
117
|
def instrument_string_split
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
false
|
|
152
|
-
end
|
|
118
|
+
@_instrument_string_split ||= begin
|
|
119
|
+
require 'cs__assess_yield_track/cs__assess_yield_track' if AGENT.patch_yield? && Funchook.available?
|
|
120
|
+
true
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
logger.error('Error loading split rb_yield patch', e)
|
|
123
|
+
false
|
|
153
124
|
end
|
|
154
|
-
@_instrument_string_split
|
|
155
125
|
end
|
|
156
126
|
|
|
157
127
|
private
|
|
158
128
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
SPLIT_TRACKER.set(:split_depth, depth)
|
|
164
|
-
else
|
|
165
|
-
SPLIT_TRACKER.set(:split_depth, 0)
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def save_split_index! depth
|
|
170
|
-
split_index = SPLIT_TRACKER.get(:split_index)
|
|
171
|
-
unless split_index
|
|
129
|
+
# Save index of the current split object.
|
|
130
|
+
# Create index tracking array as needed.
|
|
131
|
+
def save_split_index!
|
|
132
|
+
unless (split_index = SPLIT_TRACKER.get(:split_index))
|
|
172
133
|
split_index = []
|
|
173
134
|
SPLIT_TRACKER.set(:split_index, split_index)
|
|
174
135
|
end
|
|
175
|
-
# save the index to the ThreadLocal; not useless
|
|
176
|
-
split_index[
|
|
136
|
+
# save the index to the ThreadLocal; not useless.
|
|
137
|
+
split_index[split_scope_depth] = 0 # rubocop:disable Lint/UselessSetterCall
|
|
177
138
|
end
|
|
178
139
|
|
|
179
|
-
|
|
140
|
+
# Save value of the current split object.
|
|
141
|
+
# Create value tracking array as needed.
|
|
142
|
+
def save_split_value! string, args
|
|
180
143
|
preshift = Contrast::Agent::Assess::PreShift.build_preshift(split_node, string, args)
|
|
181
144
|
target = string.split
|
|
182
145
|
propagate(split_node, preshift, target)
|
|
183
|
-
split_value = SPLIT_TRACKER.get(:split_value)
|
|
184
|
-
unless split_value
|
|
146
|
+
unless (split_value = SPLIT_TRACKER.get(:split_value))
|
|
185
147
|
split_value = []
|
|
186
148
|
SPLIT_TRACKER.set(:split_value, split_value)
|
|
187
149
|
end
|
|
188
|
-
#
|
|
189
|
-
split_value[
|
|
150
|
+
# Save the target to the ThreadLocal; not useless.
|
|
151
|
+
split_value[split_scope_depth] = target # rubocop:disable Lint/UselessSetterCall
|
|
190
152
|
end
|
|
191
153
|
|
|
192
|
-
# Quick hook to the String#split propagation node in our Assess
|
|
193
|
-
# policy
|
|
154
|
+
# Quick hook to the String#split propagation node in our Assess policy
|
|
194
155
|
#
|
|
195
|
-
# @return [Contrast::Agent::Assess::Policy::PropagationNode]
|
|
196
|
-
# String#split node
|
|
156
|
+
# @return [Contrast::Agent::Assess::Policy::PropagationNode] String#split node
|
|
197
157
|
def split_node
|
|
198
158
|
@_split_node ||= begin
|
|
199
159
|
Contrast::Agent::Assess::Policy::Policy.instance.propagators.find do |node|
|
|
@@ -215,19 +175,15 @@ if RUBY_VERSION >= '2.6.0'
|
|
|
215
175
|
class String
|
|
216
176
|
alias_method :cs__patched_string_split_special, :split
|
|
217
177
|
|
|
218
|
-
#
|
|
219
|
-
# yield case.
|
|
178
|
+
# Override of the the standard split method to handle the 2.6 direct yield case.
|
|
220
179
|
#
|
|
221
180
|
# Note: because this patch is applied before our standard propagation, this
|
|
222
|
-
# call wrapped in it. As such, any call here happens in scope, so there is
|
|
181
|
+
# call is wrapped in it. As such, any call here happens in scope, so there is
|
|
223
182
|
# no need to manage it on our own.
|
|
224
183
|
def split *args, &block
|
|
225
184
|
if block
|
|
226
|
-
Contrast::Agent::Assess::Policy::Propagator::Split.
|
|
227
|
-
begin
|
|
185
|
+
Contrast::Agent::Assess::Policy::Propagator::Split.wrap_split(self, args) do
|
|
228
186
|
cs__patched_string_split_special(*args, &block)
|
|
229
|
-
ensure
|
|
230
|
-
Contrast::Agent::Assess::Policy::Propagator::Split.end_split
|
|
231
187
|
end
|
|
232
188
|
else
|
|
233
189
|
cs__patched_string_split_special(*args, &block)
|