contrast-agent 3.15.0 → 3.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|