contrast-agent 3.14.0 → 3.15.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/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +18 -15
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +1 -0
- data/ext/cs__assess_string/cs__assess_string.c +24 -25
- data/ext/cs__assess_string/cs__assess_string.h +3 -1
- data/ext/cs__common/cs__common.c +4 -2
- data/ext/cs__common/cs__common.h +1 -1
- data/lib/contrast.rb +1 -1
- data/lib/contrast/agent/assess.rb +1 -0
- data/lib/contrast/agent/assess/contrast_event.rb +4 -12
- data/lib/contrast/agent/assess/finalizers/freeze.rb +3 -1
- data/lib/contrast/agent/assess/finalizers/hash.rb +45 -1
- data/lib/contrast/agent/assess/policy/patcher.rb +1 -1
- data/lib/contrast/agent/assess/policy/policy.rb +0 -2
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -1
- data/lib/contrast/agent/assess/policy/preshift.rb +7 -11
- data/lib/contrast/agent/assess/policy/propagation_method.rb +50 -33
- data/lib/contrast/agent/assess/policy/propagator/append.rb +8 -5
- data/lib/contrast/agent/assess/policy/propagator/base.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/center.rb +9 -5
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -3
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +6 -3
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +4 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +6 -6
- data/lib/contrast/agent/assess/policy/propagator/next.rb +7 -5
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +8 -5
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +8 -4
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -2
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +7 -5
- data/lib/contrast/agent/assess/policy/propagator/select.rb +15 -7
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +14 -8
- data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -8
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -21
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +11 -5
- data/lib/contrast/agent/assess/policy/source_method.rb +85 -58
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -11
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +38 -15
- data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -13
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -1
- data/lib/contrast/agent/assess/properties.rb +2 -0
- data/lib/contrast/agent/assess/property/updated.rb +136 -0
- data/lib/contrast/agent/assess/tracker.rb +66 -0
- data/lib/contrast/agent/class_reopener.rb +7 -5
- data/lib/contrast/agent/middleware.rb +0 -1
- data/lib/contrast/agent/patching/policy/patcher.rb +13 -22
- data/lib/contrast/agent/patching/policy/policy.rb +1 -4
- data/lib/contrast/agent/response.rb +17 -6
- data/lib/contrast/agent/rewriter.rb +1 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +1 -4
- data/lib/contrast/api/decorators/application_update.rb +2 -4
- data/lib/contrast/api/decorators/trace_event.rb +5 -5
- data/lib/contrast/components/app_context.rb +11 -9
- data/lib/contrast/components/config.rb +3 -13
- data/lib/contrast/components/contrast_service.rb +2 -2
- data/lib/contrast/config/application_configuration.rb +5 -2
- data/lib/contrast/config/service_configuration.rb +8 -2
- data/lib/contrast/configuration.rb +88 -47
- data/lib/contrast/extension/assess.rb +0 -2
- data/lib/contrast/extension/assess/array.rb +8 -5
- data/lib/contrast/extension/assess/erb.rb +6 -3
- data/lib/contrast/extension/assess/fiber.rb +9 -9
- data/lib/contrast/extension/assess/hash.rb +2 -3
- data/lib/contrast/extension/assess/kernel.rb +12 -5
- data/lib/contrast/extension/assess/marshal.rb +3 -2
- data/lib/contrast/extension/assess/regexp.rb +5 -4
- data/lib/contrast/extension/assess/string.rb +8 -10
- data/lib/contrast/framework/rack/patch/session_cookie.rb +12 -18
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +4 -10
- data/lib/contrast/framework/rails/support.rb +2 -0
- data/lib/contrast/logger/application.rb +11 -3
- data/lib/contrast/utils/assess/tracking_util.rb +48 -3
- data/lib/contrast/utils/duck_utils.rb +0 -10
- data/lib/contrast/utils/env_configuration_item.rb +2 -1
- data/lib/contrast/utils/invalid_configuration_util.rb +21 -19
- data/lib/contrast/utils/string_utils.rb +10 -5
- data/resources/assess/policy.json +0 -10
- data/ruby-agent.gemspec +16 -15
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +42 -21
- data/lib/contrast/agent/assess/finalizers/finalize.rb +0 -21
- data/lib/contrast/extension/assess/assess_extension.rb +0 -145
- data/lib/contrast/utils/freeze_util.rb +0 -32
@@ -46,8 +46,7 @@ module Contrast
|
|
46
46
|
|
47
47
|
begin
|
48
48
|
source = preshift.object
|
49
|
-
self_tracked = Contrast::
|
50
|
-
source.cs__tracked?
|
49
|
+
self_tracked = Contrast::Agent::Assess::Tracker.tracked?(source)
|
51
50
|
args = preshift.args[1]
|
52
51
|
incoming_tracked = args && determine_tracked(args)
|
53
52
|
return ret unless self_tracked || incoming_tracked
|
@@ -75,10 +74,10 @@ module Contrast
|
|
75
74
|
# if it's a string, just ask if it's tracked
|
76
75
|
case args
|
77
76
|
when String
|
78
|
-
Contrast::
|
77
|
+
Contrast::Agent::Assess::Tracker.tracked?(args)
|
79
78
|
# if it's a hash, ask if it has a tracked string
|
80
79
|
when Hash
|
81
|
-
args.values.any? { |value| value.is_a?(String) && Contrast::
|
80
|
+
args.values.any? { |value| value.is_a?(String) && Contrast::Agent::Assess::Tracker.tracked?(value) }
|
82
81
|
# this should never happen
|
83
82
|
else
|
84
83
|
false
|
@@ -86,6 +85,9 @@ module Contrast
|
|
86
85
|
end
|
87
86
|
|
88
87
|
def string_sub self_tracked, preshift, ret, incoming, incoming_tracked, global
|
88
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
89
|
+
return unless properties
|
90
|
+
|
89
91
|
pattern = preshift.args[0]
|
90
92
|
source = preshift.object
|
91
93
|
|
@@ -93,13 +95,13 @@ module Contrast
|
|
93
95
|
# copied from regexp / captures. Trading accuracy for
|
94
96
|
# performance
|
95
97
|
if incoming.match?(CAPTURE_GROUP_REGEXP) || incoming.match?(CAPTURE_NAME_REGEXP)
|
96
|
-
|
98
|
+
properties.splat_from(source, ret) if self_tracked
|
97
99
|
return
|
98
100
|
end
|
99
101
|
|
100
102
|
# if it's just a straight insert, that we can do
|
101
103
|
# Copy the tags from us to the return
|
102
|
-
|
104
|
+
properties.copy_from(source, ret)
|
103
105
|
# Figure out where inserts occurred
|
104
106
|
last_idx = 0
|
105
107
|
ranges = []
|
@@ -114,48 +116,55 @@ module Contrast
|
|
114
116
|
ranges << (start_index...end_index)
|
115
117
|
break unless global
|
116
118
|
end
|
117
|
-
|
118
|
-
|
119
|
+
properties.delete_tags_at_ranges(ranges)
|
120
|
+
properties.shift_tags(ranges)
|
119
121
|
return unless incoming_tracked
|
120
122
|
|
121
|
-
|
123
|
+
incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
|
124
|
+
tags = incoming_properties.tag_keys
|
122
125
|
ranges.each do |range|
|
123
126
|
tags.each do |tag|
|
124
|
-
|
127
|
+
properties.add_tag(tag, range)
|
125
128
|
end
|
126
129
|
end
|
127
130
|
end
|
128
131
|
|
129
132
|
def block_sub self_tracked, source, ret
|
130
|
-
|
133
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
134
|
+
properties&.splat_from(source, ret) if self_tracked
|
131
135
|
end
|
132
136
|
|
133
137
|
def hash_sub self_tracked, source, ret
|
134
|
-
|
138
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
139
|
+
properties&.splat_from(source, ret) if self_tracked
|
135
140
|
end
|
136
141
|
|
137
142
|
def pattern_gsub preshift, ret
|
138
|
-
|
143
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
144
|
+
return unless properties
|
139
145
|
|
140
146
|
source = preshift.object
|
141
|
-
|
142
|
-
|
147
|
+
source_properties = Contrast::Agent::Assess::Tracker.properties(source)
|
148
|
+
return unless source_properties
|
149
|
+
|
150
|
+
source_properties.tag_keys.each do |key|
|
151
|
+
properties.add_tag(key, 0...1)
|
143
152
|
end
|
144
153
|
end
|
145
154
|
|
146
155
|
def string_build_event patcher, preshift, ret
|
147
|
-
return unless Contrast::
|
156
|
+
return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
|
148
157
|
|
158
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
149
159
|
args = preshift.args
|
150
160
|
if args.length > 1
|
151
161
|
arg = args[1]
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
162
|
+
arg_properties = Contrast::Agent::Assess::Tracker.properties(arg)
|
163
|
+
arg_properties&.events&.each do |event|
|
164
|
+
properties.events << event
|
156
165
|
end
|
157
166
|
end
|
158
|
-
|
167
|
+
properties.build_event(
|
159
168
|
patcher,
|
160
169
|
ret,
|
161
170
|
preshift.object,
|
@@ -16,9 +16,12 @@ module Contrast
|
|
16
16
|
def tr_tagger patcher, preshift, ret, _block
|
17
17
|
return ret unless ret && !ret.empty?
|
18
18
|
|
19
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
20
|
+
return unless properties
|
21
|
+
|
19
22
|
source = preshift.object
|
20
23
|
args = preshift.args
|
21
|
-
|
24
|
+
properties.copy_from(source, ret)
|
22
25
|
replace_string = args[1]
|
23
26
|
source_chars = source.chars
|
24
27
|
# if the replace string is empty, then there's a bunch of deletes. this
|
@@ -41,10 +44,10 @@ module Contrast
|
|
41
44
|
end
|
42
45
|
# account for the last char being different
|
43
46
|
remove_ranges << (start...source_chars.length) if start
|
44
|
-
|
47
|
+
properties.delete_tags_at_ranges(remove_ranges, false)
|
45
48
|
end
|
46
49
|
|
47
|
-
|
50
|
+
properties.build_event(
|
48
51
|
patcher,
|
49
52
|
ret,
|
50
53
|
source,
|
@@ -57,10 +60,13 @@ module Contrast
|
|
57
60
|
def tr_s_tagger patcher, preshift, ret, _block
|
58
61
|
return unless ret && !ret.empty?
|
59
62
|
|
63
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
64
|
+
return unless properties
|
65
|
+
|
60
66
|
source = preshift.object
|
61
67
|
args = preshift.args
|
62
|
-
|
63
|
-
|
68
|
+
properties.splat_from(source, ret)
|
69
|
+
properties.build_event(
|
64
70
|
patcher,
|
65
71
|
ret,
|
66
72
|
source,
|
@@ -17,7 +17,7 @@ module Contrast
|
|
17
17
|
# dataflows used in Assess vulnerability detection.
|
18
18
|
module SourceMethod
|
19
19
|
include Contrast::Components::Interface
|
20
|
-
access_component :
|
20
|
+
access_component :analysis, :logging
|
21
21
|
|
22
22
|
PARAMETER_TYPE = 'PARAMETER'
|
23
23
|
PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
|
@@ -44,38 +44,26 @@ module Contrast
|
|
44
44
|
current_context = Contrast::Agent::REQUEST_TRACKER.current
|
45
45
|
return unless current_context&.analyze_request? && ASSESS.enabled?
|
46
46
|
|
47
|
-
replaced_return = nil
|
48
47
|
source_node = method_policy.source_node
|
49
|
-
|
50
48
|
target = determine_target(source_node, object, ret, args)
|
49
|
+
restore_frozen_state = false
|
50
|
+
if target.cs__frozen? && !Contrast::Agent::Assess::Tracker.trackable?(target)
|
51
|
+
return unless ASSESS.track_frozen_sources?
|
52
|
+
return unless source_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
# previously been tracked; therefore, we can break out early.
|
55
|
-
if target.cs__frozen?
|
56
|
-
# That being said, we don't have enough context to know if we
|
57
|
-
# can make this assumption and still function, so we'll allow for
|
58
|
-
# source tracking of frozen things by a common config setting.
|
59
|
-
#
|
60
|
-
# Rails' StrongParameters make a case for this to be default
|
61
|
-
# behavior
|
62
|
-
return replaced_return unless ASSESS.track_frozen_sources?
|
63
|
-
|
64
|
-
# If we're tracking the frozen target, we need to unfreeze
|
65
|
-
# (dup) it to track and then freeze that result. For
|
66
|
-
# simplicities sake, we ONLY do this if the return is the
|
67
|
-
# target (I don't want to have to deal with unfreezing self)
|
68
|
-
return replaced_return unless source_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
|
54
|
+
dup = safe_dup(ret)
|
55
|
+
return unless dup
|
69
56
|
|
70
57
|
restore_frozen_state = true
|
71
|
-
ret =
|
58
|
+
ret = dup
|
72
59
|
target = ret
|
60
|
+
Contrast::Agent::Assess::Tracker.pre_freeze(ret)
|
61
|
+
ret.cs__freeze
|
62
|
+
# double check that we were able to finalize the replaced return
|
63
|
+
return unless Contrast::Agent::Assess::Tracker.trackable?(target)
|
73
64
|
end
|
74
|
-
|
75
65
|
apply_source(current_context, source_node, target, object, ret, source_node.type, nil, *args)
|
76
|
-
|
77
|
-
ret.cs__freeze if restore_frozen_state
|
78
|
-
ret
|
66
|
+
restore_frozen_state ? ret : nil
|
79
67
|
end
|
80
68
|
|
81
69
|
private
|
@@ -103,34 +91,10 @@ module Contrast
|
|
103
91
|
source_name ||= determine_source_name(source_node, object, ret, *args)
|
104
92
|
# We know we only work on certain things.
|
105
93
|
# Skip if this isn't one of them
|
106
|
-
if Contrast::
|
94
|
+
if Contrast::Agent::Assess::Tracker.trackable?(target)
|
107
95
|
apply_tags(source_node, target, object, ret, source_type, source_name, *args)
|
108
|
-
# While we don't taint hashes themselves, we may taint the things
|
109
|
-
# they hold. Let's pass their keys and values back to ourselves and
|
110
|
-
# try again
|
111
96
|
elsif Contrast::Utils::DuckUtils.iterable_hash?(target)
|
112
|
-
|
113
|
-
target.each_pair do |key, value|
|
114
|
-
# We only do this for Strings b/c of the way Hash lookup works.
|
115
|
-
# To replace another object would break hash lookup and,
|
116
|
-
# therefore, the application
|
117
|
-
if can_track_hash_key?(key, target)
|
118
|
-
key = Contrast::Utils::FreezeUtil.unfreeze_dup(key)
|
119
|
-
to_replace << key
|
120
|
-
end
|
121
|
-
apply_source(context, source_node, key, object, ret, key_type(source_type), key, *args)
|
122
|
-
apply_source(context, source_node, value, object, ret, source_type, key, *args)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Hash is designed to keep one instance of the string key in it.
|
126
|
-
# We need to remove the existing one and replace it with our new
|
127
|
-
# tracked one.
|
128
|
-
to_replace.each do |key|
|
129
|
-
key.cs__freeze
|
130
|
-
value = target[key]
|
131
|
-
target.delete(key)
|
132
|
-
target[key] = value
|
133
|
-
end
|
97
|
+
apply_hash_tags(context, source_node, target, object, ret, source_type, *args)
|
134
98
|
# While we don't taint arrays themselves, we may taint the things
|
135
99
|
# they hold. Let's pass their keys and values back to ourselves and
|
136
100
|
# try again
|
@@ -141,17 +105,81 @@ module Contrast
|
|
141
105
|
logger.warn('Unable to apply source', e, node_id: source_node.id)
|
142
106
|
end
|
143
107
|
|
144
|
-
|
108
|
+
# While we don't taint hashes themselves, we may taint the things
|
109
|
+
# they hold. Let's pass their keys and values back to ourselves and
|
110
|
+
# try again
|
111
|
+
#
|
112
|
+
# @param context [Contrast::Utils::ThreadTracker] the current request
|
113
|
+
# context
|
114
|
+
# @param source_node [Contrast::Agent::Assess::Policy::SourceNode]
|
115
|
+
# the node to direct applying this source event
|
116
|
+
# @param target [Object] the target of the Source Event
|
117
|
+
# @param object [Object] the Object on which the method was invoked
|
118
|
+
# @param ret [Object] the Return of the invoked method
|
119
|
+
# @param source_type [String] the type of this source, from the
|
120
|
+
# source_node, or a KEY_TYPE if invoked for a map
|
121
|
+
# @param args [Array<Object>] the Arguments with which the method
|
122
|
+
# was invoked
|
123
|
+
def apply_hash_tags context, source_node, target, object, ret, source_type, *args
|
124
|
+
to_replace = []
|
125
|
+
target.each_pair do |key, value|
|
126
|
+
# We only do this for Strings b/c of the way Hash lookup works.
|
127
|
+
# To replace another object would break hash lookup and,
|
128
|
+
# therefore, the application
|
129
|
+
if replace_hash_key?(key, target)
|
130
|
+
key = key.dup
|
131
|
+
to_replace << key
|
132
|
+
end
|
133
|
+
apply_source(context, source_node, key, object, ret, key_type(source_type), key, *args)
|
134
|
+
apply_source(context, source_node, value, object, ret, source_type, key, *args)
|
135
|
+
end
|
136
|
+
handle_hash_key(target, to_replace)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Given an unfrozen hash, if the key is a String, we should replace
|
140
|
+
# it with one that we can finalize, allowing us to track that key.
|
141
|
+
# This method handles checking if that replace can and should
|
142
|
+
# occur.
|
143
|
+
#
|
144
|
+
# @param key [Object] the key in the hash that may need replacing.
|
145
|
+
# @param hash [Hash] the hash to which the key belongs.
|
146
|
+
# @return [Boolean] whether replace the key in the hash or not.
|
147
|
+
def replace_hash_key? key, hash
|
145
148
|
ASSESS.track_frozen_sources? &&
|
149
|
+
!hash.cs__frozen? &&
|
146
150
|
key.is_a?(String) &&
|
147
|
-
Contrast::
|
151
|
+
!Contrast::Agent::Assess::Tracker.trackable?(key)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Safely duplicate the target, or return nil
|
155
|
+
#
|
156
|
+
# @param target [Object] the thing to check for duplication
|
157
|
+
def safe_dup target
|
158
|
+
target.dup
|
159
|
+
rescue StandardError => _e
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Hash is designed to keep one instance of the string key in it.
|
164
|
+
# We need to remove the existing one and replace it with our new
|
165
|
+
# tracked one.
|
166
|
+
def handle_hash_key target, to_replace
|
167
|
+
to_replace.each do |key|
|
168
|
+
Contrast::Agent::Assess::Tracker.pre_freeze(key)
|
169
|
+
key.cs__freeze
|
170
|
+
value = target.delete(key)
|
171
|
+
target[key] = value
|
172
|
+
end
|
148
173
|
end
|
149
174
|
|
150
175
|
def apply_tags source_node, target, object, ret, source_type, source_name, *args
|
176
|
+
# don't apply tags if we can't track the thing
|
177
|
+
return unless Contrast::Agent::Assess::Tracker.trackable?(target)
|
151
178
|
# don't apply second source -- probably needs tuning later if we
|
152
179
|
# use more than 'UNTRUSTED' in our sources
|
153
|
-
return if
|
180
|
+
return if Contrast::Agent::Assess::Tracker.tracked?(target)
|
154
181
|
|
182
|
+
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
155
183
|
# otherwise for each tag this source_node applies, create a tag range
|
156
184
|
# on the target object
|
157
185
|
# I realize this looping is counter-intuitive from the above
|
@@ -160,16 +188,15 @@ module Contrast
|
|
160
188
|
next unless Contrast::Agent::Assess::Policy::SourceValidation.valid?(tag, source_type, source_name)
|
161
189
|
|
162
190
|
length = Contrast::Utils::StringUtils.ret_length(target)
|
163
|
-
|
164
|
-
|
191
|
+
properties.add_tag(tag, 0...length)
|
192
|
+
properties.add_properties(source_node.properties)
|
165
193
|
logger.trace('Source detected',
|
166
194
|
node_id: source_node.id,
|
167
195
|
target_id: target.__id__,
|
168
196
|
tag: tag)
|
169
197
|
end
|
170
|
-
|
171
198
|
# make a representation of this method that TeamServer can render
|
172
|
-
|
199
|
+
properties.build_event(source_node, target, object, ret, args, source_type, source_name)
|
173
200
|
end
|
174
201
|
|
175
202
|
# Find the name of the source
|
@@ -25,24 +25,27 @@ module Contrast
|
|
25
25
|
TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
|
26
26
|
|
27
27
|
def xss_tilt_trigger context, trigger_node, _source, object, ret, *args
|
28
|
-
|
28
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
29
|
+
return unless properties
|
29
30
|
|
31
|
+
scope = args[0]
|
30
32
|
erb_template_prerender = object.instance_variable_get(:@data)
|
31
33
|
interpolated_inputs = []
|
32
34
|
handle_binding_variables(scope, erb_template_prerender, ret, interpolated_inputs)
|
33
|
-
|
34
35
|
handle_local_variables(args, erb_template_prerender, ret, interpolated_inputs)
|
35
|
-
|
36
36
|
unless interpolated_inputs.empty?
|
37
37
|
interpolated_inputs.each do |input|
|
38
|
-
|
39
|
-
|
38
|
+
input_properties = Contrast::Agent::Assess::Tracker.properties(input)
|
39
|
+
next unless input_properties
|
40
|
+
|
41
|
+
input_properties.events.each do |event|
|
42
|
+
properties.events << event
|
40
43
|
end
|
41
44
|
end
|
42
|
-
|
45
|
+
properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
|
43
46
|
end
|
44
47
|
|
45
|
-
if
|
48
|
+
if Contrast::Agent::Assess::Tracker.tracked?(ret)
|
46
49
|
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, ret, erb_template_prerender, ret, interpolated_inputs)
|
47
50
|
end
|
48
51
|
|
@@ -52,32 +55,34 @@ module Contrast
|
|
52
55
|
private
|
53
56
|
|
54
57
|
def handle_binding_variables scope, erb_template_prerender, ret, interpolated_inputs
|
58
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
55
59
|
binding_variables = scope.instance_variables
|
56
60
|
|
57
61
|
binding_variables.each do |bound_variable_sym|
|
58
62
|
bound_variable_value = scope.instance_variable_get(bound_variable_sym)
|
59
63
|
|
60
|
-
next unless
|
64
|
+
next unless Contrast::Agent::Assess::Tracker.tracked?(bound_variable_value)
|
61
65
|
next unless erb_template_prerender.include?(bound_variable_sym.to_s)
|
62
66
|
|
63
67
|
start_index = ret.index(bound_variable_value)
|
64
68
|
next if start_index.nil?
|
65
69
|
|
66
|
-
|
70
|
+
properties.copy_from(bound_variable_value, ret, start_index)
|
67
71
|
interpolated_inputs << bound_variable_sym
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
75
|
def handle_local_variables args, erb_template_prerender, ret, interpolated_inputs
|
76
|
+
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
72
77
|
locals = args[1]
|
73
78
|
locals.each do |local_name, local_value|
|
74
|
-
next unless
|
79
|
+
next unless Contrast::Agent::Assess::Tracker.tracked?(local_value)
|
75
80
|
next unless erb_template_prerender.include?(local_name.to_s)
|
76
81
|
|
77
82
|
start_index = ret.index(local_value)
|
78
83
|
next if start_index.nil?
|
79
84
|
|
80
|
-
|
85
|
+
properties.copy_from(local_value, ret, start_index)
|
81
86
|
interpolated_inputs << local_name
|
82
87
|
end
|
83
88
|
end
|
@@ -39,7 +39,7 @@ module Contrast
|
|
39
39
|
def process context, trigger_node, object, ret, *args
|
40
40
|
args.each do |arg|
|
41
41
|
next unless arg.cs__is_a?(String) || arg.cs__is_a?(Symbol)
|
42
|
-
next unless
|
42
|
+
next unless Contrast::Agent::Assess::Tracker.tracked?(arg)
|
43
43
|
next unless trigger_node.violated?(arg)
|
44
44
|
|
45
45
|
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(
|
@@ -20,8 +20,11 @@ module Contrast
|
|
20
20
|
include Contrast::Components::Interface
|
21
21
|
access_component :analysis, :logging
|
22
22
|
|
23
|
+
# The level of TeamServer compliance our traces meet when in the
|
24
|
+
# abnormal condition of being dataflow rules without routes
|
25
|
+
MINIMUM_FINDING_VERSION = 3
|
23
26
|
# The level of TeamServer compliance our traces meet
|
24
|
-
CURRENT_FINDING_VERSION =
|
27
|
+
CURRENT_FINDING_VERSION = 4
|
25
28
|
|
26
29
|
class << self
|
27
30
|
# This is called from within our woven proc. It will be called as if it
|
@@ -89,13 +92,12 @@ module Contrast
|
|
89
92
|
|
90
93
|
finding = Contrast::Api::Dtm::Finding.new
|
91
94
|
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
|
92
|
-
finding.version = CURRENT_FINDING_VERSION
|
93
|
-
|
94
95
|
build_from_source(finding, source)
|
95
96
|
trigger_event = Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret, args).to_dtm_event
|
96
97
|
finding.events << trigger_event
|
97
98
|
build_hash(finding, source)
|
98
99
|
finding.routes << context.route if context.route
|
100
|
+
finding.version = determine_compliance_version(finding)
|
99
101
|
context.activity.findings << finding
|
100
102
|
logger.trace('Finding created',
|
101
103
|
node_id: trigger_node.id,
|
@@ -211,8 +213,8 @@ module Contrast
|
|
211
213
|
def apply_dataflow_rule context, trigger_node, source, object, ret, *args
|
212
214
|
return unless source
|
213
215
|
|
214
|
-
if Contrast::
|
215
|
-
return unless
|
216
|
+
if Contrast::Agent::Assess::Tracker.trackable?(source)
|
217
|
+
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
216
218
|
return unless trigger_node.violated?(source)
|
217
219
|
|
218
220
|
build_finding(context, trigger_node, source, object, ret, *args)
|
@@ -226,30 +228,31 @@ module Contrast
|
|
226
228
|
apply_dataflow_rule(context, trigger_node, value, object, ret, *args)
|
227
229
|
end
|
228
230
|
else
|
229
|
-
logger.
|
230
|
-
|
231
|
-
|
232
|
-
|
231
|
+
logger.debug('Trigger source is untrackable. Unable to inspect.',
|
232
|
+
node_id: trigger_node.id,
|
233
|
+
source_id: source.__id__,
|
234
|
+
source_type: source.cs__class.to_s,
|
235
|
+
frozen: source.cs__frozen?)
|
233
236
|
logger.trace(source.to_s[0..99])
|
234
237
|
end
|
235
238
|
end
|
236
239
|
|
237
240
|
def build_from_source finding, source
|
238
241
|
return unless source
|
239
|
-
return unless Contrast::
|
240
|
-
|
241
|
-
|
242
|
-
return unless
|
242
|
+
return unless Contrast::Agent::Assess::Tracker.trackable?(source)
|
243
|
+
|
244
|
+
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
245
|
+
return unless properties
|
243
246
|
|
244
247
|
# events could technically be nil, but we would have failed
|
245
248
|
# the rule check before getting here. not worth the nil check
|
246
|
-
|
249
|
+
properties.events.each do |event|
|
247
250
|
finding.events << event.to_dtm_event
|
248
251
|
end
|
249
252
|
|
250
253
|
# Google::Protobuf::Map doesn't support merge!, so we have to do this
|
251
254
|
# long form
|
252
|
-
source_props =
|
255
|
+
source_props = properties.properties
|
253
256
|
return unless source_props
|
254
257
|
|
255
258
|
source_props.each_pair do |key, value|
|
@@ -263,6 +266,26 @@ module Contrast
|
|
263
266
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
|
264
267
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
265
268
|
end
|
269
|
+
|
270
|
+
# Because our APIs are not versioned, TeamServer relies on a field,
|
271
|
+
# version, to tell it what, if any, validation it can preform on
|
272
|
+
# the findings we send it. Examine the finding and determine the
|
273
|
+
# level to which it conforms.
|
274
|
+
#
|
275
|
+
# @param finding [Contrast::Api::Dtm::Finding]
|
276
|
+
# @return [int] the version of compliance
|
277
|
+
def determine_compliance_version finding
|
278
|
+
return MINIMUM_FINDING_VERSION unless finding
|
279
|
+
# as routes are the only variable between findings, in the case
|
280
|
+
# where we couldn't determine one, any finding with a route is at
|
281
|
+
# maximum compliance
|
282
|
+
return CURRENT_FINDING_VERSION if finding.routes.any?
|
283
|
+
# any finding without events is not of a dataflow type and
|
284
|
+
# therefore at maximum compliance
|
285
|
+
return CURRENT_FINDING_VERSION unless finding.events.any?
|
286
|
+
|
287
|
+
MINIMUM_FINDING_VERSION
|
288
|
+
end
|
266
289
|
end
|
267
290
|
end
|
268
291
|
end
|