contrast-agent 3.14.0 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.rb +4 -12
- data/lib/contrast/agent/assess.rb +1 -0
- data/lib/contrast/agent/assess/contrast_event.rb +143 -79
- data/lib/contrast/agent/assess/events/source_event.rb +1 -1
- 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/dynamic_source_factory.rb +10 -3
- 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_node.rb +15 -10
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -3
- 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 +7 -4
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +4 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -7
- 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 +13 -7
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +10 -9
- data/lib/contrast/agent/assess/policy/propagator/split.rb +24 -19
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +47 -31
- 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 -12
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +76 -28
- data/lib/contrast/agent/assess/policy/trigger_node.rb +38 -43
- 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/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +9 -3
- data/lib/contrast/agent/assess/property/updated.rb +131 -0
- 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 +83 -14
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +66 -0
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/class_reopener.rb +7 -5
- data/lib/contrast/agent/inventory.rb +15 -0
- data/lib/contrast/agent/inventory/dependencies.rb +50 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
- data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
- data/lib/contrast/agent/middleware.rb +1 -3
- 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/patch.rb +6 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +13 -22
- data/lib/contrast/agent/patching/policy/policy.rb +17 -6
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +9 -25
- 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/request_handler.rb +1 -1
- data/lib/contrast/agent/response.rb +17 -6
- data/lib/contrast/agent/rewriter.rb +1 -3
- data/lib/contrast/agent/scope.rb +59 -53
- data/lib/contrast/agent/static_analysis.rb +7 -7
- data/lib/contrast/agent/tracepoint_hook.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +1 -4
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators.rb +3 -0
- data/lib/contrast/api/decorators/address.rb +13 -14
- data/lib/contrast/api/decorators/application_update.rb +2 -4
- data/lib/contrast/api/decorators/library.rb +53 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +30 -0
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +25 -23
- data/lib/contrast/common_agent_configuration.rb +2 -1
- data/lib/contrast/components/agent.rb +6 -5
- data/lib/contrast/components/app_context.rb +49 -38
- data/lib/contrast/components/config.rb +30 -48
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/interface.rb +25 -3
- data/lib/contrast/components/inventory.rb +6 -1
- data/lib/contrast/components/scope.rb +49 -6
- data/lib/contrast/components/settings.rb +23 -23
- data/lib/contrast/config/application_configuration.rb +5 -2
- data/lib/contrast/config/inventory_configuration.rb +2 -2
- data/lib/contrast/config/service_configuration.rb +8 -0
- data/lib/contrast/configuration.rb +88 -47
- data/lib/contrast/extension/assess.rb +0 -2
- data/lib/contrast/extension/assess/array.rb +15 -8
- data/lib/contrast/extension/assess/erb.rb +11 -3
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
- data/lib/contrast/extension/assess/fiber.rb +12 -12
- data/lib/contrast/extension/assess/hash.rb +5 -6
- data/lib/contrast/extension/assess/kernel.rb +28 -23
- data/lib/contrast/extension/assess/marshal.rb +11 -6
- data/lib/contrast/extension/assess/regexp.rb +8 -7
- data/lib/contrast/extension/assess/string.rb +21 -21
- 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 +22 -28
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -11
- 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/rails/support.rb +5 -0
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +4 -4
- data/lib/contrast/logger/application.rb +11 -3
- data/lib/contrast/logger/log.rb +7 -2
- 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 +20 -21
- data/lib/contrast/utils/inventory_util.rb +0 -7
- data/lib/contrast/utils/sha256_builder.rb +0 -12
- data/lib/contrast/utils/string_utils.rb +10 -5
- data/resources/assess/policy.json +31 -22
- data/ruby-agent.gemspec +21 -18
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +71 -30
- data/lib/contrast/agent/assess/finalizers/finalize.rb +0 -21
- data/lib/contrast/extension/assess/assess_extension.rb +0 -145
- data/lib/contrast/utils/boolean_util.rb +0 -30
- data/lib/contrast/utils/freeze_util.rb +0 -32
- data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -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,10 +20,31 @@ 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
|
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
|
+
|
27
48
|
# This is called from within our woven proc. It will be called as if it
|
28
49
|
# were inline in the Rack application.
|
29
50
|
#
|
@@ -66,8 +87,8 @@ module Contrast
|
|
66
87
|
# This converts the source of the finding, and the events leading
|
67
88
|
# up to it into a Finding
|
68
89
|
#
|
69
|
-
# @param context [Contrast::
|
70
|
-
# context
|
90
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
91
|
+
# request context
|
71
92
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
72
93
|
# the node to direct applying this trigger event
|
73
94
|
# @param source [Object] the source of the Trigger Event
|
@@ -89,18 +110,17 @@ module Contrast
|
|
89
110
|
|
90
111
|
finding = Contrast::Api::Dtm::Finding.new
|
91
112
|
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
|
92
|
-
finding.version = CURRENT_FINDING_VERSION
|
93
|
-
|
94
113
|
build_from_source(finding, source)
|
95
114
|
trigger_event = Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret, args).to_dtm_event
|
96
115
|
finding.events << trigger_event
|
97
116
|
build_hash(finding, source)
|
98
117
|
finding.routes << context.route if context.route
|
99
|
-
|
118
|
+
finding.version = determine_compliance_version(finding)
|
100
119
|
logger.trace('Finding created',
|
101
120
|
node_id: trigger_node.id,
|
102
121
|
source_id: source.__id__,
|
103
122
|
rule: trigger_node.rule_id)
|
123
|
+
report_finding(finding)
|
104
124
|
rescue StandardError => e
|
105
125
|
logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
|
106
126
|
end
|
@@ -110,8 +130,8 @@ module Contrast
|
|
110
130
|
# This is our method that actually checks the taint on the object
|
111
131
|
# our trigger_node targets.
|
112
132
|
#
|
113
|
-
# @param context [Contrast::
|
114
|
-
# context
|
133
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
134
|
+
# request context
|
115
135
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
116
136
|
# the node to direct applying this trigger event
|
117
137
|
# @param source [Object] the source of the Trigger Event
|
@@ -179,8 +199,8 @@ module Contrast
|
|
179
199
|
# This is our method that actually checks the taint on the object
|
180
200
|
# our trigger_node targets for our Regexp based rules.
|
181
201
|
#
|
182
|
-
# @param context [Contrast::
|
183
|
-
# context
|
202
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
203
|
+
# request context
|
184
204
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
185
205
|
# the node to direct applying this trigger event
|
186
206
|
# @param source [Object] the source of the Trigger Event
|
@@ -199,8 +219,8 @@ module Contrast
|
|
199
219
|
# This is our method that actually checks the taint on the object
|
200
220
|
# our trigger_node targets for our Dataflow based rules.
|
201
221
|
#
|
202
|
-
# @param context [Contrast::
|
203
|
-
# context
|
222
|
+
# @param context [Contrast::Agent::RequestContext] the current
|
223
|
+
# request context
|
204
224
|
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
205
225
|
# the node to direct applying this trigger event
|
206
226
|
# @param source [Object] the source of the Trigger Event
|
@@ -211,8 +231,8 @@ module Contrast
|
|
211
231
|
def apply_dataflow_rule context, trigger_node, source, object, ret, *args
|
212
232
|
return unless source
|
213
233
|
|
214
|
-
if Contrast::
|
215
|
-
return unless
|
234
|
+
if Contrast::Agent::Assess::Tracker.trackable?(source)
|
235
|
+
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
216
236
|
return unless trigger_node.violated?(source)
|
217
237
|
|
218
238
|
build_finding(context, trigger_node, source, object, ret, *args)
|
@@ -226,30 +246,27 @@ module Contrast
|
|
226
246
|
apply_dataflow_rule(context, trigger_node, value, object, ret, *args)
|
227
247
|
end
|
228
248
|
else
|
229
|
-
logger.
|
230
|
-
|
231
|
-
|
232
|
-
|
249
|
+
logger.debug('Trigger source is untrackable. Unable to inspect.',
|
250
|
+
node_id: trigger_node.id,
|
251
|
+
source_id: source.__id__,
|
252
|
+
source_type: source.cs__class.to_s,
|
253
|
+
frozen: source.cs__frozen?)
|
233
254
|
logger.trace(source.to_s[0..99])
|
234
255
|
end
|
235
256
|
end
|
236
257
|
|
237
258
|
def build_from_source finding, source
|
238
259
|
return unless source
|
239
|
-
return unless Contrast::
|
240
|
-
source,
|
241
|
-
:cs__properties)
|
242
|
-
return unless source.cs__properties
|
260
|
+
return unless Contrast::Agent::Assess::Tracker.trackable?(source)
|
243
261
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
end
|
262
|
+
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
263
|
+
return unless properties
|
264
|
+
|
265
|
+
build_events finding, properties.event if properties.event
|
249
266
|
|
250
267
|
# Google::Protobuf::Map doesn't support merge!, so we have to do this
|
251
268
|
# long form
|
252
|
-
source_props =
|
269
|
+
source_props = properties.properties
|
253
270
|
return unless source_props
|
254
271
|
|
255
272
|
source_props.each_pair do |key, value|
|
@@ -258,11 +275,42 @@ module Contrast
|
|
258
275
|
end
|
259
276
|
end
|
260
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
|
+
|
261
289
|
def build_hash finding, source
|
262
290
|
hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
|
263
291
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
|
264
292
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
265
293
|
end
|
294
|
+
|
295
|
+
# Because our APIs are not versioned, TeamServer relies on a field,
|
296
|
+
# version, to tell it what, if any, validation it can preform on
|
297
|
+
# the findings we send it. Examine the finding and determine the
|
298
|
+
# level to which it conforms.
|
299
|
+
#
|
300
|
+
# @param finding [Contrast::Api::Dtm::Finding]
|
301
|
+
# @return [int] the version of compliance
|
302
|
+
def determine_compliance_version finding
|
303
|
+
return MINIMUM_FINDING_VERSION unless finding
|
304
|
+
# as routes are the only variable between findings, in the case
|
305
|
+
# where we couldn't determine one, any finding with a route is at
|
306
|
+
# maximum compliance
|
307
|
+
return CURRENT_FINDING_VERSION if finding.routes.any?
|
308
|
+
# any finding without events is not of a dataflow type and
|
309
|
+
# therefore at maximum compliance
|
310
|
+
return CURRENT_FINDING_VERSION unless finding.events.any?
|
311
|
+
|
312
|
+
MINIMUM_FINDING_VERSION
|
313
|
+
end
|
266
314
|
end
|
267
315
|
end
|
268
316
|
end
|
@@ -100,16 +100,17 @@ module Contrast
|
|
100
100
|
# if the source isn't tracked, there can't be a violation
|
101
101
|
# this condition may not hold true forever, but for now it's
|
102
102
|
# a nice optimization
|
103
|
-
return false unless
|
103
|
+
return false unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
104
104
|
|
105
|
+
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
105
106
|
# find the ranges that violate the rule (untrusted, etc)
|
106
|
-
vulnerable_ranges =
|
107
|
+
vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties, required_tags)
|
107
108
|
# if there aren't any vulnerable ranges, nope out
|
108
109
|
return false if vulnerable_ranges.empty?
|
109
110
|
|
110
111
|
# find the ranges that are exempt from the rule
|
111
112
|
# (validated, sanitized, etc)
|
112
|
-
secure_ranges =
|
113
|
+
secure_ranges = ranges_with_any_tag(properties, disallowed_tags)
|
113
114
|
# if there are vulnerable ranges and no secure, report
|
114
115
|
return true if secure_ranges.empty?
|
115
116
|
|
@@ -178,68 +179,62 @@ module Contrast
|
|
178
179
|
# @param length [Integer] the length of the object which may have the
|
179
180
|
# given tags -- used as the maximum index to search for all of the
|
180
181
|
# tags.
|
181
|
-
# @param
|
182
|
+
# @param properties [Contrast::Agent::Assess::Properties] the
|
182
183
|
# properties to check for the tags
|
183
|
-
# @param
|
184
|
+
# @param required_tags [Set<String>] the list of tags on which to match
|
184
185
|
# @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
|
185
186
|
# by the given conditions
|
186
|
-
def
|
187
|
-
# if there
|
188
|
-
|
189
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless
|
190
|
-
|
191
|
-
|
192
|
-
return find_ranges_by_any_tag(cs__properties, tags) if tags.length == 1
|
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.
|
190
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
|
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) }
|
193
193
|
|
194
194
|
ranges = []
|
195
|
-
|
196
|
-
# tags.each { |tag| applicable << cs__properties.fetch_tag(tag) }
|
197
|
-
# return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless applicable.length == tags.length
|
198
|
-
# ...
|
195
|
+
chunking = false
|
199
196
|
# find all the indicies on the source that have all the given tags
|
200
197
|
(0..length).each do |idx|
|
201
|
-
tags_at =
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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
|
209
218
|
end
|
210
219
|
end
|
211
|
-
|
212
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY if ranges.empty?
|
213
|
-
|
214
|
-
# chunk all the adjacent ranges
|
215
|
-
chunked = ranges.chunk_while { |i, j| i + 1 == j }
|
216
|
-
tag_ranges = []
|
217
|
-
# and convert them into Tags
|
218
|
-
chunked.each do |join|
|
219
|
-
start = join[0]
|
220
|
-
stop = join[-1]
|
221
|
-
# add the 1 to account for end index being exclusive
|
222
|
-
tag_length = stop - start + 1
|
223
|
-
tag_ranges = Contrast::Utils::TagUtil.ordered_merge(tag_ranges, Tag.new(tag_length, start))
|
224
|
-
end
|
225
|
-
tag_ranges
|
220
|
+
ranges
|
226
221
|
end
|
227
222
|
|
228
223
|
# Find the ranges that satisfy any of the given tags.
|
229
224
|
#
|
230
|
-
# @param
|
225
|
+
# @param properties [Contrast::Agent::Assess::Properties] the
|
231
226
|
# properties to check for the tags
|
232
227
|
# @param tags [Set<String>] the list of tags on which to match
|
233
228
|
# @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
|
234
229
|
# by the given conditions
|
235
|
-
def
|
230
|
+
def ranges_with_any_tag properties, tags
|
236
231
|
# if there aren't any all_tags or tags, break early
|
237
|
-
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless
|
232
|
+
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
|
238
233
|
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tags&.any?
|
239
234
|
|
240
235
|
ranges = []
|
241
236
|
tags.each do |desired|
|
242
|
-
found =
|
237
|
+
found = properties.fetch_tag(desired)
|
243
238
|
next unless found
|
244
239
|
|
245
240
|
# we need to dup here so that we don't change the tags if target is
|
@@ -38,7 +38,8 @@ module Contrast
|
|
38
38
|
finish = match.begin(:path)
|
39
39
|
finish ||= url.length
|
40
40
|
|
41
|
-
args[0]
|
41
|
+
properties = Contrast::Agent::Assess::Tracker.properties(args[0])
|
42
|
+
properties.any_tags_between?(start, finish)
|
42
43
|
end
|
43
44
|
end
|
44
45
|
end
|
@@ -5,6 +5,7 @@ require 'base64'
|
|
5
5
|
require 'set'
|
6
6
|
require 'contrast/agent/assess/property/evented'
|
7
7
|
require 'contrast/agent/assess/property/tagged'
|
8
|
+
require 'contrast/agent/assess/property/updated'
|
8
9
|
require 'contrast/utils/prevent_serialization'
|
9
10
|
|
10
11
|
module Contrast
|
@@ -20,6 +21,7 @@ module Contrast
|
|
20
21
|
include Contrast::Utils::PreventSerialization
|
21
22
|
include Contrast::Agent::Assess::Property::Evented
|
22
23
|
include Contrast::Agent::Assess::Property::Tagged
|
24
|
+
include Contrast::Agent::Assess::Property::Updated
|
23
25
|
|
24
26
|
attr_accessor :dupped_from
|
25
27
|
|
@@ -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
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/utils/duck_utils'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Assess
|
9
|
+
module Property
|
10
|
+
# This module serves to hold the functionality required for the
|
11
|
+
# update of properties as they go through dataflow.
|
12
|
+
module Updated
|
13
|
+
# copy tags and info from source's properties to self
|
14
|
+
# @param source [Object] the object from which existing properties
|
15
|
+
# should be copied.
|
16
|
+
# @param owner [Object] the object to which these properties apply.
|
17
|
+
# @param shift [Integer] (0) how far to shift the tags during copy,
|
18
|
+
# useful for insert and append operations.
|
19
|
+
# @param skip_tags [Set<String>] (nil) the tags to not copy over,
|
20
|
+
# useful for propagation events that have 'untags'.
|
21
|
+
def copy_from source, owner, shift = 0, skip_tags = nil
|
22
|
+
return if owner.equal?(source)
|
23
|
+
return unless Contrast::Agent::Assess::Tracker.tracked?(source)
|
24
|
+
|
25
|
+
original = Contrast::Agent::Assess::Tracker.properties(source)
|
26
|
+
return unless original
|
27
|
+
|
28
|
+
adjust_duplicate(original)
|
29
|
+
original.tag_keys.each do |key|
|
30
|
+
next if skip_tags&.include?(key)
|
31
|
+
|
32
|
+
existing = tags[key]
|
33
|
+
had_existing = existing.any?
|
34
|
+
value = original.fetch_tag(key)
|
35
|
+
value.each do |tag|
|
36
|
+
existing << tag.copy_modified(shift)
|
37
|
+
end
|
38
|
+
Contrast::Utils::TagUtil.size_aware_merge(owner, existing) if had_existing
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Some propagation occurred, but we're not sure what the
|
43
|
+
# exact transformation was. To be safe, we just explode
|
44
|
+
# all the tags from the source to the return.
|
45
|
+
#
|
46
|
+
# If the return already had that tag, the existing tag
|
47
|
+
# range is recycled to save us an object.
|
48
|
+
#
|
49
|
+
# @param source [Object] the object from which existing properties
|
50
|
+
# should be copied.
|
51
|
+
# @param owner [Object] the object to which these properties apply.
|
52
|
+
def splat_from source, owner
|
53
|
+
splat_length = Contrast::Utils::StringUtils.ret_length(owner)
|
54
|
+
return if splat_length.zero?
|
55
|
+
|
56
|
+
splat_from_ret(splat_length)
|
57
|
+
splat_from_source(source, splat_length)
|
58
|
+
cleanup_tags
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Because of how our tracking works now, sometimes the Source and
|
64
|
+
# Target are the same, but their IDs in our map will be different due
|
65
|
+
# to PreShift duplication. To account for this, we have to ensure that
|
66
|
+
# the Object we're copying from does not have the same Properties
|
67
|
+
# that the Object we're copying to does. If they are the same, wipe the
|
68
|
+
# Target so that the copy method can update events and ranges as
|
69
|
+
# necessary.
|
70
|
+
# DO NOT TAKE THIS OUT!
|
71
|
+
def adjust_duplicate original
|
72
|
+
reset_properties if original == self
|
73
|
+
reset_properties if original.__id__ == dupped_from
|
74
|
+
reset_properties if original.dupped_from == __id__
|
75
|
+
end
|
76
|
+
|
77
|
+
# Wipe out the instance variables on this Properties instance,
|
78
|
+
# allowing them to be rebuilt.
|
79
|
+
def reset_properties
|
80
|
+
@_tags = nil
|
81
|
+
@_events = nil
|
82
|
+
@_properties = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Splat all the tags from the source to this set of Properties
|
86
|
+
#
|
87
|
+
# @param source [Object] the object from which tags will be copied
|
88
|
+
# and splatted.
|
89
|
+
# @param splat_length [Integer] the length to which to to set all
|
90
|
+
# tags.
|
91
|
+
def splat_from_source source, splat_length
|
92
|
+
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
93
|
+
return unless properties
|
94
|
+
|
95
|
+
properties.tag_keys.each do |key|
|
96
|
+
existing = fetch_tag(key)
|
97
|
+
# if the tag already exists, drop all but the first range
|
98
|
+
# then change that range to cover the entire return
|
99
|
+
if existing
|
100
|
+
existing.drop(existing.length - 1)
|
101
|
+
range = existing[0]
|
102
|
+
range.repurpose(0, splat_length)
|
103
|
+
else
|
104
|
+
add_tag(key, 0...splat_length)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Splat all the tags existing on this set of Properties
|
110
|
+
#
|
111
|
+
# @param splat_length [Integer] the length to which to to set all
|
112
|
+
# tags.
|
113
|
+
def splat_from_ret splat_length
|
114
|
+
return unless tracked?
|
115
|
+
|
116
|
+
tag_keys.each do |key|
|
117
|
+
next unless key
|
118
|
+
|
119
|
+
existing = fetch_tag(key)
|
120
|
+
next unless existing
|
121
|
+
|
122
|
+
existing.each do |range|
|
123
|
+
range.repurpose(0, splat_length)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|