contrast-agent 3.15.0 → 3.16.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/lib/contrast/agent.rb +2 -9
- data/lib/contrast/agent/assess/contrast_event.rb +142 -70
- data/lib/contrast/agent/assess/events/source_event.rb +1 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +7 -1
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +0 -3
- data/lib/contrast/agent/assess/policy/propagator/select.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +0 -5
- data/lib/contrast/agent/assess/policy/propagator/split.rb +12 -13
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +21 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +4 -5
- data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
- data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
- data/lib/contrast/agent/assess/property/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +9 -3
- data/lib/contrast/agent/assess/property/updated.rb +0 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +82 -14
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
- data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
- data/lib/contrast/agent/patching/policy/policy.rb +16 -2
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
- data/lib/contrast/agent/request.rb +34 -34
- data/lib/contrast/agent/static_analysis.rb +6 -6
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators/address.rb +13 -13
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +20 -18
- data/lib/contrast/components/app_context.rb +39 -30
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/settings.rb +20 -23
- data/lib/contrast/config/service_configuration.rb +4 -2
- data/lib/contrast/configuration.rb +1 -1
- data/lib/contrast/extension/assess/array.rb +7 -3
- data/lib/contrast/extension/assess/erb.rb +5 -0
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
- data/lib/contrast/extension/assess/fiber.rb +3 -3
- data/lib/contrast/extension/assess/hash.rb +3 -3
- data/lib/contrast/extension/assess/kernel.rb +18 -20
- data/lib/contrast/extension/assess/marshal.rb +8 -4
- data/lib/contrast/extension/assess/regexp.rb +3 -3
- data/lib/contrast/extension/assess/string.rb +13 -11
- data/lib/contrast/extension/protect/kernel.rb +3 -3
- data/lib/contrast/framework/base_support.rb +1 -1
- data/lib/contrast/framework/manager.rb +3 -3
- data/lib/contrast/framework/rack/patch/session_cookie.rb +9 -9
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +4 -4
- data/lib/contrast/logger/log.rb +7 -2
- data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
- data/resources/assess/policy.json +31 -12
- data/ruby-agent.gemspec +4 -3
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +31 -17
@@ -51,16 +51,23 @@ module Contrast
|
|
51
51
|
incoming_tracked = args && determine_tracked(args)
|
52
52
|
return ret unless self_tracked || incoming_tracked
|
53
53
|
|
54
|
+
parent_events = []
|
54
55
|
if block
|
55
56
|
block_sub(self_tracked, source, ret)
|
56
57
|
elsif args.is_a?(String)
|
57
|
-
string_sub(self_tracked, preshift, ret, args, incoming_tracked, global)
|
58
|
+
string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
|
58
59
|
elsif args.is_a?(Hash)
|
59
60
|
hash_sub(self_tracked, source, ret)
|
60
61
|
else # Enumerator, only for gsub
|
61
|
-
pattern_gsub(preshift, ret)
|
62
|
+
pattern_gsub(parent_events, preshift, ret)
|
62
63
|
end
|
63
|
-
|
64
|
+
|
65
|
+
if self_tracked
|
66
|
+
source_properties = Contrast::Agent::Assess::Tracker.properties(source)
|
67
|
+
parent_event = source_properties&.event
|
68
|
+
parent_events.prepend(parent_event) if parent_event
|
69
|
+
end
|
70
|
+
string_build_event(parent_events, patcher, preshift, ret)
|
64
71
|
rescue StandardError => e
|
65
72
|
logger.error('Unable to apply gsub propagator', e)
|
66
73
|
end
|
@@ -84,10 +91,14 @@ module Contrast
|
|
84
91
|
end
|
85
92
|
end
|
86
93
|
|
87
|
-
def string_sub self_tracked, preshift, ret, incoming, incoming_tracked, global
|
94
|
+
def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
|
88
95
|
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
89
96
|
return unless properties
|
90
97
|
|
98
|
+
incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
|
99
|
+
parent_event = incoming_properties&.event
|
100
|
+
parent_events << parent_event if parent_event
|
101
|
+
|
91
102
|
pattern = preshift.args[0]
|
92
103
|
source = preshift.object
|
93
104
|
|
@@ -119,8 +130,8 @@ module Contrast
|
|
119
130
|
properties.delete_tags_at_ranges(ranges)
|
120
131
|
properties.shift_tags(ranges)
|
121
132
|
return unless incoming_tracked
|
133
|
+
return unless incoming_properties
|
122
134
|
|
123
|
-
incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
|
124
135
|
tags = incoming_properties.tag_keys
|
125
136
|
ranges.each do |range|
|
126
137
|
tags.each do |tag|
|
@@ -139,7 +150,7 @@ module Contrast
|
|
139
150
|
properties&.splat_from(source, ret) if self_tracked
|
140
151
|
end
|
141
152
|
|
142
|
-
def pattern_gsub preshift, ret
|
153
|
+
def pattern_gsub parent_events, preshift, ret
|
143
154
|
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
144
155
|
return unless properties
|
145
156
|
|
@@ -150,20 +161,15 @@ module Contrast
|
|
150
161
|
source_properties.tag_keys.each do |key|
|
151
162
|
properties.add_tag(key, 0...1)
|
152
163
|
end
|
164
|
+
parent_event = source_properties.event
|
165
|
+
parent_events << parent_event if parent_event
|
153
166
|
end
|
154
167
|
|
155
|
-
def string_build_event patcher, preshift, ret
|
168
|
+
def string_build_event parent_events, patcher, preshift, ret
|
156
169
|
return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
|
157
170
|
|
158
171
|
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
159
172
|
args = preshift.args
|
160
|
-
if args.length > 1
|
161
|
-
arg = args[1]
|
162
|
-
arg_properties = Contrast::Agent::Assess::Tracker.properties(arg)
|
163
|
-
arg_properties&.events&.each do |event|
|
164
|
-
properties.events << event
|
165
|
-
end
|
166
|
-
end
|
167
173
|
properties.build_event(
|
168
174
|
patcher,
|
169
175
|
ret,
|
@@ -171,6 +177,7 @@ module Contrast
|
|
171
177
|
ret,
|
172
178
|
args,
|
173
179
|
2)
|
180
|
+
properties.event.instance_variable_set(:@_parent_events, parent_events)
|
174
181
|
end
|
175
182
|
end
|
176
183
|
end
|
@@ -33,16 +33,15 @@ module Contrast
|
|
33
33
|
interpolated_inputs = []
|
34
34
|
handle_binding_variables(scope, erb_template_prerender, ret, interpolated_inputs)
|
35
35
|
handle_local_variables(args, erb_template_prerender, ret, interpolated_inputs)
|
36
|
+
properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
|
36
37
|
unless interpolated_inputs.empty?
|
38
|
+
current_event = properties.event
|
37
39
|
interpolated_inputs.each do |input|
|
38
40
|
input_properties = Contrast::Agent::Assess::Tracker.properties(input)
|
39
|
-
next unless input_properties
|
41
|
+
next unless input_properties&.event
|
40
42
|
|
41
|
-
|
42
|
-
properties.events << event
|
43
|
-
end
|
43
|
+
current_event.parent_events << input_properties.event
|
44
44
|
end
|
45
|
-
properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
|
46
45
|
end
|
47
46
|
|
48
47
|
if Contrast::Agent::Assess::Tracker.tracked?(ret)
|
@@ -27,6 +27,24 @@ module Contrast
|
|
27
27
|
CURRENT_FINDING_VERSION = 4
|
28
28
|
|
29
29
|
class << self
|
30
|
+
# Append the given finding to the given context to be reported when
|
31
|
+
# the Context's activity is sent to the Service or, in the absence
|
32
|
+
# of that Context, generate an Activity and queue it manually
|
33
|
+
# @param finding [Contrast::Api::Dtm::Finding]
|
34
|
+
def report_finding finding
|
35
|
+
context = Contrast::Agent::REQUEST_TRACKER.current
|
36
|
+
if context
|
37
|
+
context.activity.findings << finding
|
38
|
+
else
|
39
|
+
activity = Contrast::Api::Dtm::Activity.new
|
40
|
+
activity.findings << finding
|
41
|
+
|
42
|
+
Contrast::Agent.messaging_queue.send_event_eventually(activity)
|
43
|
+
end
|
44
|
+
logger.debug('Finding reported',
|
45
|
+
rule: finding.rule_id)
|
46
|
+
end
|
47
|
+
|
30
48
|
# This is called from within our woven proc. It will be called as if it
|
31
49
|
# were inline in the Rack application.
|
32
50
|
#
|
@@ -69,8 +87,8 @@ module Contrast
|
|
69
87
|
# This converts the source of the finding, and the events leading
|
70
88
|
# up to it into a Finding
|
71
89
|
#
|
72
|
-
# @param context [Contrast::
|
73
|
-
# context
|
90
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
91
|
+
# request context
|
74
92
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
75
93
|
# the node to direct applying this trigger event
|
76
94
|
# @param source [Object] the source of the Trigger Event
|
@@ -98,11 +116,11 @@ module Contrast
|
|
98
116
|
build_hash(finding, source)
|
99
117
|
finding.routes << context.route if context.route
|
100
118
|
finding.version = determine_compliance_version(finding)
|
101
|
-
context.activity.findings << finding
|
102
119
|
logger.trace('Finding created',
|
103
120
|
node_id: trigger_node.id,
|
104
121
|
source_id: source.__id__,
|
105
122
|
rule: trigger_node.rule_id)
|
123
|
+
report_finding(finding)
|
106
124
|
rescue StandardError => e
|
107
125
|
logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
|
108
126
|
end
|
@@ -112,8 +130,8 @@ module Contrast
|
|
112
130
|
# This is our method that actually checks the taint on the object
|
113
131
|
# our trigger_node targets.
|
114
132
|
#
|
115
|
-
# @param context [Contrast::
|
116
|
-
# context
|
133
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
134
|
+
# request context
|
117
135
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
118
136
|
# the node to direct applying this trigger event
|
119
137
|
# @param source [Object] the source of the Trigger Event
|
@@ -181,8 +199,8 @@ module Contrast
|
|
181
199
|
# This is our method that actually checks the taint on the object
|
182
200
|
# our trigger_node targets for our Regexp based rules.
|
183
201
|
#
|
184
|
-
# @param context [Contrast::
|
185
|
-
# context
|
202
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
203
|
+
# request context
|
186
204
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
187
205
|
# the node to direct applying this trigger event
|
188
206
|
# @param source [Object] the source of the Trigger Event
|
@@ -201,8 +219,8 @@ module Contrast
|
|
201
219
|
# This is our method that actually checks the taint on the object
|
202
220
|
# our trigger_node targets for our Dataflow based rules.
|
203
221
|
#
|
204
|
-
# @param context [Contrast::
|
205
|
-
# context
|
222
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
223
|
+
# request context
|
206
224
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
207
225
|
# the node to direct applying this trigger event
|
208
226
|
# @param source [Object] the source of the Trigger Event
|
@@ -244,11 +262,7 @@ module Contrast
|
|
244
262
|
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
245
263
|
return unless properties
|
246
264
|
|
247
|
-
|
248
|
-
# the rule check before getting here. not worth the nil check
|
249
|
-
properties.events.each do |event|
|
250
|
-
finding.events << event.to_dtm_event
|
251
|
-
end
|
265
|
+
build_events finding, properties.event if properties.event
|
252
266
|
|
253
267
|
# Google::Protobuf::Map doesn't support merge!, so we have to do this
|
254
268
|
# long form
|
@@ -261,6 +275,17 @@ module Contrast
|
|
261
275
|
end
|
262
276
|
end
|
263
277
|
|
278
|
+
def build_events finding, event
|
279
|
+
return unless event
|
280
|
+
|
281
|
+
event.parent_events&.each do |parent_event|
|
282
|
+
build_events(finding, parent_event)
|
283
|
+
end
|
284
|
+
# events could technically be nil, but we would have failed
|
285
|
+
# the rule check before getting here. not worth the nil check
|
286
|
+
finding.events << event.to_dtm_event
|
287
|
+
end
|
288
|
+
|
264
289
|
def build_hash finding, source
|
265
290
|
hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
|
266
291
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
|
@@ -104,13 +104,13 @@ module Contrast
|
|
104
104
|
|
105
105
|
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
106
106
|
# find the ranges that violate the rule (untrusted, etc)
|
107
|
-
vulnerable_ranges =
|
107
|
+
vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties, required_tags)
|
108
108
|
# if there aren't any vulnerable ranges, nope out
|
109
109
|
return false if vulnerable_ranges.empty?
|
110
110
|
|
111
111
|
# find the ranges that are exempt from the rule
|
112
112
|
# (validated, sanitized, etc)
|
113
|
-
secure_ranges =
|
113
|
+
secure_ranges = ranges_with_any_tag(properties, disallowed_tags)
|
114
114
|
# if there are vulnerable ranges and no secure, report
|
115
115
|
return true if secure_ranges.empty?
|
116
116
|
|
@@ -181,49 +181,43 @@ module Contrast
|
|
181
181
|
# tags.
|
182
182
|
# @param properties [Contrast::Agent::Assess::Properties] the
|
183
183
|
# properties to check for the tags
|
184
|
-
# @param
|
184
|
+
# @param required_tags [Set<String>] the list of tags on which to match
|
185
185
|
# @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
|
186
186
|
# by the given conditions
|
187
|
-
def
|
188
|
-
# if there
|
187
|
+
def ranges_with_all_tags length, properties, required_tags
|
188
|
+
# if there are no tags, not required tags, or the tags don't have
|
189
|
+
# all the required tags, we can just return here.
|
189
190
|
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
|
190
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless
|
191
|
-
|
192
|
-
# :zap: faster to treat all as any if there's only one tag
|
193
|
-
return find_ranges_by_any_tag(properties, tags) if tags.length == 1
|
191
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless required_tags&.any?
|
192
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless required_tags.all? { |tag| properties.tag_keys.include?(tag) }
|
194
193
|
|
195
194
|
ranges = []
|
196
|
-
|
197
|
-
# tags.each { |tag| applicable << properties.fetch_tag(tag) }
|
198
|
-
# return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless applicable.length == tags.length
|
199
|
-
# ...
|
195
|
+
chunking = false
|
200
196
|
# find all the indicies on the source that have all the given tags
|
201
197
|
(0..length).each do |idx|
|
202
|
-
tags_at = properties.tags_at(idx)
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
198
|
+
tags_at = properties.tags_at(idx).to_a
|
199
|
+
# only those that have all the required tags in the tags_at
|
200
|
+
# satisfy the requirement
|
201
|
+
satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
|
202
|
+
# if this range matches all the required tags and we're already
|
203
|
+
# chunking, meaning the previous range matched, do nothing
|
204
|
+
next if satisfied && chunking
|
205
|
+
|
206
|
+
# if we are satisfied and we were not chunking, this represents
|
207
|
+
# the start of the next range, so create a new entry.
|
208
|
+
if satisfied
|
209
|
+
ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
|
210
|
+
chunking = true
|
211
|
+
# if we are chunking and not satisfied, this represents the end
|
212
|
+
# of the range, meaning the last index is what satisfied the
|
213
|
+
# range. Because the range is exclusive end, we can just use this
|
214
|
+
# index.
|
215
|
+
elsif chunking
|
216
|
+
ranges[-1]&.update_end(idx)
|
217
|
+
chunking = false
|
210
218
|
end
|
211
219
|
end
|
212
|
-
|
213
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY if ranges.empty?
|
214
|
-
|
215
|
-
# chunk all the adjacent ranges
|
216
|
-
chunked = ranges.chunk_while { |i, j| i + 1 == j }
|
217
|
-
tag_ranges = []
|
218
|
-
# and convert them into Tags
|
219
|
-
chunked.each do |join|
|
220
|
-
start = join[0]
|
221
|
-
stop = join[-1]
|
222
|
-
# add the 1 to account for end index being exclusive
|
223
|
-
tag_length = stop - start + 1
|
224
|
-
tag_ranges = Contrast::Utils::TagUtil.ordered_merge(tag_ranges, Tag.new(tag_length, start))
|
225
|
-
end
|
226
|
-
tag_ranges
|
220
|
+
ranges
|
227
221
|
end
|
228
222
|
|
229
223
|
# Find the ranges that satisfy any of the given tags.
|
@@ -233,7 +227,7 @@ module Contrast
|
|
233
227
|
# @param tags [Set<String>] the list of tags on which to match
|
234
228
|
# @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
|
235
229
|
# by the given conditions
|
236
|
-
def
|
230
|
+
def ranges_with_any_tag properties, tags
|
237
231
|
# if there aren't any all_tags or tags, break early
|
238
232
|
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
|
239
233
|
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any?
|
@@ -10,23 +10,11 @@ module Contrast
|
|
10
10
|
module Property
|
11
11
|
# This module serves to hold the functionality required for the
|
12
12
|
# management of our dataflow events.
|
13
|
+
#
|
14
|
+
# @attr_reader event [Contrast::Agent::Assess::ContrastEvent] the
|
15
|
+
# latest event to track
|
13
16
|
module Evented
|
14
|
-
|
15
|
-
#
|
16
|
-
# @return [Array<Contrast::Agent::Assess::ContrastEvent>]
|
17
|
-
def events
|
18
|
-
@_events ||= []
|
19
|
-
end
|
20
|
-
|
21
|
-
# Add an event to these properties. It will be used to build
|
22
|
-
# a trace if this object ends up in a trigger.
|
23
|
-
#
|
24
|
-
# @param event [Contrast::Agent::Assess::ContrastEvent] the latest
|
25
|
-
# event to track
|
26
|
-
def add_event event
|
27
|
-
events << event
|
28
|
-
self
|
29
|
-
end
|
17
|
+
attr_accessor :event
|
30
18
|
|
31
19
|
# Create a new event and add it to the event set.
|
32
20
|
#
|
@@ -43,8 +31,7 @@ module Contrast
|
|
43
31
|
# the key used to accessed if from a map or nil if a type like
|
44
32
|
# BODY
|
45
33
|
def build_event policy_node, tagged, object, ret, args, source_type = nil, source_name = nil
|
46
|
-
event = Contrast::Agent::Assess::Events::EventFactory.build(policy_node, tagged, object, ret, args, source_type, source_name)
|
47
|
-
add_event(event)
|
34
|
+
@event = Contrast::Agent::Assess::Events::EventFactory.build(policy_node, tagged, object, ret, args, source_type, source_name)
|
48
35
|
report_sources(tagged, event)
|
49
36
|
end
|
50
37
|
|
@@ -162,15 +162,21 @@ module Contrast
|
|
162
162
|
end
|
163
163
|
|
164
164
|
# We'll use this as a helper method to retrieve tags from the hash.
|
165
|
-
# Because the hash auto-populates an empty array when we try to
|
166
|
-
# a tag in it, we cannot use the [] method without side
|
167
|
-
# around this, we'll use a fetch work around.
|
165
|
+
# Because the hash auto-populates an empty array when we try to
|
166
|
+
# access a tag in it, we cannot use the [] method without side
|
167
|
+
# effect. To get around this, we'll use a fetch work around.
|
168
|
+
#
|
169
|
+
# @param label [Symbol] the label to look up
|
170
|
+
# @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
|
171
|
+
# that label
|
168
172
|
def fetch_tag label
|
169
173
|
tags.fetch(label, nil) if tracked?
|
170
174
|
end
|
171
175
|
|
172
176
|
# Convert the tags of this object into the TraceTaintRange required
|
173
177
|
# to be sent to the service
|
178
|
+
#
|
179
|
+
# @return [Array<Contrast::Api::Dtm::TraceTaintRange>]
|
174
180
|
def tags_to_dtm
|
175
181
|
Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
|
176
182
|
end
|
@@ -33,6 +33,64 @@ module Contrast
|
|
33
33
|
NON_KEY_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
34
34
|
end
|
35
35
|
|
36
|
+
BYTE_HOLDERS = %i[ARRAY LIST].cs__freeze
|
37
|
+
# Determine if the given value node violates the hardcode key rule
|
38
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
39
|
+
# evaluate
|
40
|
+
# @return [Boolean]
|
41
|
+
def value_node_passes? value_node
|
42
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
43
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
44
|
+
# If it's a String being turned into bytes, then it matches key
|
45
|
+
# expectations
|
46
|
+
return true if bytes_call?(value_node)
|
47
|
+
|
48
|
+
type = value_node.type
|
49
|
+
return false unless BYTE_HOLDERS.include?(type)
|
50
|
+
return false unless value_node.children.any?
|
51
|
+
|
52
|
+
# Unless this is an array of literal numerics, we don't match.
|
53
|
+
# That array seems to always end in a nil value, so we allow
|
54
|
+
# those as well.
|
55
|
+
value_node.children.each do |child|
|
56
|
+
next unless child
|
57
|
+
|
58
|
+
return false unless child.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
59
|
+
child.type == :LIT &&
|
60
|
+
child.children[0]&.cs__is_a?(Integer)
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
REDACTED_MARKER = ' = [**REDACTED**]'
|
67
|
+
def redacted_marker
|
68
|
+
REDACTED_MARKER
|
69
|
+
end
|
70
|
+
|
71
|
+
# A node is a bytes_call if it's the Node for String#bytes. We care
|
72
|
+
# about this specifically as it's likely to be a common way to
|
73
|
+
# generate a key constant, rather than directly declaring an
|
74
|
+
# integer array.
|
75
|
+
#
|
76
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
77
|
+
# evaluate
|
78
|
+
# @return [Boolean] is this a node for String#bytes or not
|
79
|
+
def bytes_call? value_node
|
80
|
+
return false unless value_node.type == :CALL
|
81
|
+
|
82
|
+
children = value_node.children
|
83
|
+
return false unless children
|
84
|
+
return false unless children.length >= 2
|
85
|
+
|
86
|
+
potential_string_node = children[0]
|
87
|
+
return false unless potential_string_node.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
88
|
+
potential_string_node.type == :STR
|
89
|
+
|
90
|
+
children[1] == :bytes
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
36
94
|
# If the value is a byte array, or at least an array of numbers, it
|
37
95
|
# passes for this rule
|
38
96
|
def value_type_passes? value
|
@@ -49,11 +107,6 @@ module Contrast
|
|
49
107
|
def value_passes? _value
|
50
108
|
true
|
51
109
|
end
|
52
|
-
|
53
|
-
REDACTED_MARKER = ' = [**REDACTED**]'
|
54
|
-
def redacted_marker
|
55
|
-
REDACTED_MARKER
|
56
|
-
end
|
57
110
|
end
|
58
111
|
end
|
59
112
|
end
|