contrast-agent 4.0.0 → 4.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
- data/lib/contrast/agent/assess/contrast_event.rb +49 -130
- data/lib/contrast/agent/assess/contrast_object.rb +51 -0
- data/lib/contrast/agent/assess/events/source_event.rb +4 -9
- data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
- data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
- data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
- data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
- data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
- data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
- data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
- data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
- data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/tagged.rb +21 -15
- data/lib/contrast/agent/assess/rule/redos.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +16 -18
- data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
- data/lib/contrast/agent/middleware.rb +50 -1
- data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
- data/lib/contrast/agent/patching/policy/patch.rb +6 -0
- data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
- data/lib/contrast/agent/protect/rule/base.rb +63 -14
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
- data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
- data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
- data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
- data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/response.rb +5 -5
- data/lib/contrast/agent/rewriter.rb +3 -3
- data/lib/contrast/agent/scope.rb +81 -55
- data/lib/contrast/agent/static_analysis.rb +13 -7
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/decorators/library.rb +1 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +19 -31
- data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
- data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
- data/lib/contrast/api/decorators/user_input.rb +2 -1
- data/lib/contrast/common_agent_configuration.rb +2 -1
- data/lib/contrast/components/assess.rb +36 -0
- data/lib/contrast/components/interface.rb +5 -3
- data/lib/contrast/components/scope.rb +72 -6
- data/lib/contrast/components/settings.rb +6 -3
- data/lib/contrast/config/assess_configuration.rb +2 -1
- data/lib/contrast/extension/assess/array.rb +2 -3
- data/lib/contrast/extension/assess/erb.rb +1 -3
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
- data/lib/contrast/extension/assess/fiber.rb +2 -3
- data/lib/contrast/extension/assess/hash.rb +4 -2
- data/lib/contrast/extension/assess/kernel.rb +1 -2
- data/lib/contrast/extension/assess/marshal.rb +34 -26
- data/lib/contrast/extension/assess/regexp.rb +3 -8
- data/lib/contrast/extension/assess/string.rb +1 -2
- data/lib/contrast/framework/base_support.rb +51 -53
- data/lib/contrast/framework/manager.rb +3 -2
- data/lib/contrast/framework/rack/patch/session_cookie.rb +2 -2
- data/lib/contrast/framework/rack/support.rb +2 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
- data/lib/contrast/framework/rails/support.rb +2 -1
- data/lib/contrast/framework/sinatra/support.rb +3 -2
- data/lib/contrast/logger/application.rb +0 -3
- data/lib/contrast/utils/duck_utils.rb +1 -1
- data/lib/contrast/utils/heap_dump_util.rb +1 -1
- data/lib/contrast/utils/object_share.rb +3 -3
- data/lib/contrast/utils/preflight_util.rb +1 -1
- data/lib/contrast/utils/prevent_serialization.rb +1 -1
- data/lib/contrast/utils/resource_loader.rb +1 -1
- data/lib/contrast/utils/sha256_builder.rb +2 -2
- data/lib/contrast/utils/string_utils.rb +1 -1
- data/lib/contrast/utils/tag_util.rb +9 -13
- data/resources/assess/policy.json +9 -9
- data/resources/deadzone/policy.json +150 -0
- data/resources/protect/policy.json +12 -0
- data/ruby-agent.gemspec +10 -6
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +69 -25
|
@@ -92,8 +92,7 @@ module Contrast
|
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
|
|
95
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
96
|
-
return unless properties
|
|
95
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
97
96
|
|
|
98
97
|
incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
|
|
99
98
|
parent_event = incoming_properties&.event
|
|
@@ -141,22 +140,23 @@ module Contrast
|
|
|
141
140
|
end
|
|
142
141
|
|
|
143
142
|
def block_sub self_tracked, source, ret
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
return unless self_tracked
|
|
144
|
+
|
|
145
|
+
properties = Contrast::Agent::Assess::Tracker.properties!(ret)
|
|
146
|
+
properties&.splat_from(source, ret)
|
|
146
147
|
end
|
|
147
148
|
|
|
148
149
|
def hash_sub self_tracked, source, ret
|
|
149
|
-
|
|
150
|
-
|
|
150
|
+
return unless self_tracked
|
|
151
|
+
|
|
152
|
+
properties = Contrast::Agent::Assess::Tracker.properties!(ret)
|
|
153
|
+
properties&.splat_from(source, ret)
|
|
151
154
|
end
|
|
152
155
|
|
|
153
156
|
def pattern_gsub parent_events, preshift, ret
|
|
154
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
155
|
-
return unless properties
|
|
156
|
-
|
|
157
157
|
source = preshift.object
|
|
158
|
-
source_properties = Contrast::Agent::Assess::Tracker.properties(source)
|
|
159
|
-
return unless
|
|
158
|
+
return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
|
|
159
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
160
160
|
|
|
161
161
|
source_properties.tag_keys.each do |key|
|
|
162
162
|
properties.add_tag(key, 0...1)
|
|
@@ -11,13 +11,11 @@ module Contrast
|
|
|
11
11
|
# Disclaimer: there may be a better way, but we're
|
|
12
12
|
# in a 'get it work' state. hopefully, we'll be in
|
|
13
13
|
# a 'get it right' state soon.
|
|
14
|
-
|
|
14
|
+
module Trim
|
|
15
15
|
class << self
|
|
16
16
|
def tr_tagger patcher, preshift, ret, _block
|
|
17
17
|
return ret unless ret && !ret.empty?
|
|
18
|
-
|
|
19
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
20
|
-
return unless properties
|
|
18
|
+
return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
21
19
|
|
|
22
20
|
source = preshift.object
|
|
23
21
|
args = preshift.args
|
|
@@ -59,9 +57,7 @@ module Contrast
|
|
|
59
57
|
|
|
60
58
|
def tr_s_tagger patcher, preshift, ret, _block
|
|
61
59
|
return unless ret && !ret.empty?
|
|
62
|
-
|
|
63
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
64
|
-
return unless properties
|
|
60
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
65
61
|
|
|
66
62
|
source = preshift.object
|
|
67
63
|
args = preshift.args
|
|
@@ -178,8 +178,8 @@ module Contrast
|
|
|
178
178
|
# don't apply second source -- probably needs tuning later if we
|
|
179
179
|
# use more than 'UNTRUSTED' in our sources
|
|
180
180
|
return if Contrast::Agent::Assess::Tracker.tracked?(target)
|
|
181
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
|
181
182
|
|
|
182
|
-
properties = Contrast::Agent::Assess::Tracker.properties(target)
|
|
183
183
|
# otherwise for each tag this source_node applies, create a tag range
|
|
184
184
|
# on the target object
|
|
185
185
|
# I realize this looping is counter-intuitive from the above
|
|
@@ -243,19 +243,7 @@ module Contrast
|
|
|
243
243
|
when Contrast::Utils::ObjectShare::OBJECT_KEY
|
|
244
244
|
object
|
|
245
245
|
else
|
|
246
|
-
|
|
247
|
-
args[source_target]
|
|
248
|
-
# If this isn't an index param, it's a named one. R.I.P.
|
|
249
|
-
else
|
|
250
|
-
arg = nil
|
|
251
|
-
args.each do |search|
|
|
252
|
-
next unless search.is_a?(Hash)
|
|
253
|
-
|
|
254
|
-
arg = search[source_target]
|
|
255
|
-
break if arg
|
|
256
|
-
end
|
|
257
|
-
arg
|
|
258
|
-
end
|
|
246
|
+
args[source_target]
|
|
259
247
|
end
|
|
260
248
|
end
|
|
261
249
|
|
|
@@ -25,14 +25,13 @@ 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
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
29
|
-
return unless properties
|
|
28
|
+
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
|
30
29
|
|
|
31
30
|
scope = args[0]
|
|
32
31
|
erb_template_prerender = object.instance_variable_get(:@data)
|
|
33
32
|
interpolated_inputs = []
|
|
34
|
-
handle_binding_variables(scope, erb_template_prerender, ret, interpolated_inputs)
|
|
35
|
-
handle_local_variables(args, erb_template_prerender, ret, interpolated_inputs)
|
|
33
|
+
handle_binding_variables(scope, erb_template_prerender, ret, properties, interpolated_inputs)
|
|
34
|
+
handle_local_variables(args, erb_template_prerender, ret, properties, interpolated_inputs)
|
|
36
35
|
properties.build_event(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs)
|
|
37
36
|
unless interpolated_inputs.empty?
|
|
38
37
|
current_event = properties.event
|
|
@@ -53,8 +52,7 @@ module Contrast
|
|
|
53
52
|
|
|
54
53
|
private
|
|
55
54
|
|
|
56
|
-
def handle_binding_variables scope, erb_template_prerender, ret, interpolated_inputs
|
|
57
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
55
|
+
def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs
|
|
58
56
|
binding_variables = scope.instance_variables
|
|
59
57
|
|
|
60
58
|
binding_variables.each do |bound_variable_sym|
|
|
@@ -71,8 +69,7 @@ module Contrast
|
|
|
71
69
|
end
|
|
72
70
|
end
|
|
73
71
|
|
|
74
|
-
def handle_local_variables args, erb_template_prerender, ret, interpolated_inputs
|
|
75
|
-
properties = Contrast::Agent::Assess::Tracker.properties(ret)
|
|
72
|
+
def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs
|
|
76
73
|
locals = args[1]
|
|
77
74
|
locals.each do |local_name, local_value|
|
|
78
75
|
next unless Contrast::Agent::Assess::Tracker.tracked?(local_value)
|
|
@@ -79,6 +79,10 @@ module Contrast
|
|
|
79
79
|
# range : 5-10
|
|
80
80
|
# result : 0-05
|
|
81
81
|
#
|
|
82
|
+
# Note that we disable Lint/DuplicateBranch in this branch in order
|
|
83
|
+
# list out all tag range cases in the proper order to make this
|
|
84
|
+
# easier to understand
|
|
85
|
+
#
|
|
82
86
|
# @param range [Range] the span to check, inclusive to exclusive
|
|
83
87
|
# @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
|
|
84
88
|
# key to tags
|
|
@@ -101,10 +105,10 @@ module Contrast
|
|
|
101
105
|
finish = range.size - start
|
|
102
106
|
add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
|
|
103
107
|
# the tag spans the requested range.
|
|
104
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
108
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
|
105
109
|
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
|
106
110
|
# part of the tag is being selected
|
|
107
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
|
111
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
|
|
108
112
|
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
|
109
113
|
end
|
|
110
114
|
end
|
|
@@ -173,14 +177,6 @@ module Contrast
|
|
|
173
177
|
tags.fetch(label, nil) if tracked?
|
|
174
178
|
end
|
|
175
179
|
|
|
176
|
-
# Convert the tags of this object into the TraceTaintRange required
|
|
177
|
-
# to be sent to the service
|
|
178
|
-
#
|
|
179
|
-
# @return [Array<Contrast::Api::Dtm::TraceTaintRange>]
|
|
180
|
-
def tags_to_dtm
|
|
181
|
-
Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
180
|
# Remove all tags within the given ranges.
|
|
185
181
|
# This does not delete an entire tag if part of that tag is
|
|
186
182
|
# outside this range, meaning we may reduce sizes of tags
|
|
@@ -301,9 +297,19 @@ module Contrast
|
|
|
301
297
|
end
|
|
302
298
|
end
|
|
303
299
|
|
|
304
|
-
# Shift the tag ranges covering the given range
|
|
305
|
-
# We assume this is for a insertion, meaning we
|
|
306
|
-
# have to move tags to the right
|
|
300
|
+
# Shift the tag ranges covering the given range to account for new
|
|
301
|
+
# data being added. We assume this is for a insertion, meaning we
|
|
302
|
+
# have to move tags out of the range and to the right. For example,
|
|
303
|
+
# given current tags: 0-15
|
|
304
|
+
# when we insert a range: 5-10
|
|
305
|
+
# then the result is: 0-5, 10-20
|
|
306
|
+
#
|
|
307
|
+
# Note that we disable Lint/DuplicateBranch in this branch in order
|
|
308
|
+
# list out all tag range cases in the proper order to make this
|
|
309
|
+
# easier to understand
|
|
310
|
+
#
|
|
311
|
+
# @param range [Range] the range of new information that's been
|
|
312
|
+
# inserted
|
|
307
313
|
def shift_tags_for_insertion range
|
|
308
314
|
return unless tracked?
|
|
309
315
|
|
|
@@ -325,13 +331,13 @@ module Contrast
|
|
|
325
331
|
when Contrast::Agent::Assess::Tag::WITHIN
|
|
326
332
|
tag.shift(length)
|
|
327
333
|
# the tag spans the range. leave the part below alone
|
|
328
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
|
334
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
|
329
335
|
new_tag = tag.clone
|
|
330
336
|
new_tag.update_start(range.begin)
|
|
331
337
|
new_tag.shift(length)
|
|
332
338
|
add << new_tag
|
|
333
339
|
tag.update_end(range.begin)
|
|
334
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
|
|
340
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
|
|
335
341
|
tag.shift(length)
|
|
336
342
|
end
|
|
337
343
|
end
|
|
@@ -33,7 +33,7 @@ module Contrast
|
|
|
33
33
|
# (2) regexp must evaluate against user input
|
|
34
34
|
return unless trigger_node.violated?(string)
|
|
35
35
|
|
|
36
|
-
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, args)
|
|
36
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, *args)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
protected
|
|
@@ -14,7 +14,20 @@ module Contrast
|
|
|
14
14
|
PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
+
# Retrieve the properties of the given Object, iff they exist.
|
|
18
|
+
#
|
|
19
|
+
# @param source [Object] the thing for which to look up properties.
|
|
20
|
+
# @return [Contrast::Agent::Assess::Properties, nil]
|
|
17
21
|
def properties source
|
|
22
|
+
PROPERTIES_HASH[source]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Retrieve the properties of the given Object, returning a new
|
|
26
|
+
# instance if one does not already exist and the source is trackable.
|
|
27
|
+
#
|
|
28
|
+
# @param source [Object] the thing for which to look up properties.
|
|
29
|
+
# @return [Contrast::Agent::Assess::Properties, nil]
|
|
30
|
+
def properties! source
|
|
18
31
|
return unless trackable?(source)
|
|
19
32
|
|
|
20
33
|
PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
|
|
@@ -33,30 +46,15 @@ module Contrast
|
|
|
33
46
|
end
|
|
34
47
|
|
|
35
48
|
# Copy the properties from one object to the next, assuming the
|
|
36
|
-
# target does not already have its own properties.
|
|
49
|
+
# target does not already have its own properties. This should only
|
|
50
|
+
# ever be called when building PreShift for those objects sources
|
|
51
|
+
# which are already tracked.
|
|
37
52
|
#
|
|
38
53
|
# @param source [Object] the instance from which to copy properties
|
|
39
54
|
# @param target [Object] the instance to which to copy properties
|
|
40
55
|
def copy source, target
|
|
41
56
|
PROPERTIES_HASH[target] ||= properties(source).dup
|
|
42
57
|
end
|
|
43
|
-
|
|
44
|
-
# Duplicate the given object, returning the duplicate after copying
|
|
45
|
-
# the properties of the original and storing them as the properties
|
|
46
|
-
# of the duplicate.
|
|
47
|
-
#
|
|
48
|
-
# @param source [Object] the thing to duplicate
|
|
49
|
-
# @return [Object] the duplicate of the original, or the original if
|
|
50
|
-
# it does not respond to duplication
|
|
51
|
-
def duplicate source
|
|
52
|
-
return source unless source
|
|
53
|
-
|
|
54
|
-
duplicate = source.dup
|
|
55
|
-
PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
|
|
56
|
-
duplicate
|
|
57
|
-
rescue StandardError
|
|
58
|
-
source
|
|
59
|
-
end
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
end
|
|
@@ -19,6 +19,13 @@ module Contrast
|
|
|
19
19
|
def feature
|
|
20
20
|
'Deadzone'
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
# Deadzone means place the code inside of the contrast scope so that
|
|
24
|
+
# none of our code executes. As such, all DeadZone nodes have that as
|
|
25
|
+
# their method scope
|
|
26
|
+
def method_scope
|
|
27
|
+
:contrast
|
|
28
|
+
end
|
|
22
29
|
end
|
|
23
30
|
end
|
|
24
31
|
end
|
|
@@ -79,7 +79,7 @@ module Contrast
|
|
|
79
79
|
Contrast::Utils::HeapDumpUtil.run
|
|
80
80
|
|
|
81
81
|
if AGENT.enabled?
|
|
82
|
-
|
|
82
|
+
handle_first_request
|
|
83
83
|
call_with_agent(env)
|
|
84
84
|
else
|
|
85
85
|
app.call(env)
|
|
@@ -91,6 +91,8 @@ module Contrast
|
|
|
91
91
|
def setup_agent
|
|
92
92
|
SETTINGS.reset_state
|
|
93
93
|
|
|
94
|
+
inform_deprecations
|
|
95
|
+
|
|
94
96
|
if CONFIG.invalid?
|
|
95
97
|
AGENT.disable!
|
|
96
98
|
logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
|
|
@@ -101,6 +103,35 @@ module Contrast
|
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
106
|
+
# Some things have to wait until first request to happen, either because
|
|
107
|
+
# resolution is not complete or because the framework will preload
|
|
108
|
+
# classes, which confuses some of our instrumentation.
|
|
109
|
+
def handle_first_request
|
|
110
|
+
@_handle_first_request ||= begin
|
|
111
|
+
Contrast::Agent::StaticAnalysis.catchup
|
|
112
|
+
force_patching
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# TODO: RUBY-1090 remove this method and those it calls.
|
|
118
|
+
#
|
|
119
|
+
# These modules are auto-loaded by Rails, meaning they are defined at
|
|
120
|
+
# startup, but that they don't actually exist. We account for this in
|
|
121
|
+
# most cases by using the ClassUtil.truly_defined? method, but it appears
|
|
122
|
+
# to fail for these Modules. In the short term, we can forcing a re-patch
|
|
123
|
+
# so that their dead zones apply.
|
|
124
|
+
def force_patching
|
|
125
|
+
force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
|
|
126
|
+
force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
|
|
127
|
+
force_patch(ActionView::Template) if defined?(ActionView::Template)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def force_patch mod
|
|
131
|
+
data = Contrast::Agent::ModuleData.new(mod)
|
|
132
|
+
Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
|
|
133
|
+
end
|
|
134
|
+
|
|
104
135
|
# This is where we process each request we intercept as a middleware. We make the request context
|
|
105
136
|
# available globally so that it can be accessed from anywhere. A RequestHandler object is made
|
|
106
137
|
# for each request, which handles prefilter and postfilter operations.
|
|
@@ -173,6 +204,24 @@ module Contrast
|
|
|
173
204
|
raise exception
|
|
174
205
|
end
|
|
175
206
|
end
|
|
207
|
+
|
|
208
|
+
# As we deprecate support to prepare to remove dead code, we need to
|
|
209
|
+
# inform our users still relying on the now deprecated and soon to be
|
|
210
|
+
# removed functionality. This method handles doing that by leveraging the
|
|
211
|
+
# standard Kernel#warn approach
|
|
212
|
+
def inform_deprecations
|
|
213
|
+
# Ruby 2.5 is currently in security maintenance, meaning int is only
|
|
214
|
+
# receiving updates for security issues. It will move to eol on 31
|
|
215
|
+
# March 2021. As such, we can remove support for it in Q2. We'll begin
|
|
216
|
+
# the deprecation warnings now so that customers have time to reach out
|
|
217
|
+
# if they'll be impacted.
|
|
218
|
+
# TODO: RUBY-715 remove this part of the method, leaving it empty if
|
|
219
|
+
# there are no other deprecations, when we drop 2.5 support.
|
|
220
|
+
return unless RUBY_VERSION < '2.6.0'
|
|
221
|
+
|
|
222
|
+
Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
|
|
223
|
+
'Please contact Customer Support prior if you require continued support.')
|
|
224
|
+
end
|
|
176
225
|
end
|
|
177
226
|
end
|
|
178
227
|
end
|
|
@@ -365,12 +365,18 @@ module Contrast
|
|
|
365
365
|
unless target_module.instance_methods(false).include? underlying_method_name
|
|
366
366
|
# alias_method may be private
|
|
367
367
|
target_module.send(:alias_method, underlying_method_name, method_name)
|
|
368
|
+
# TODO: RUBY-1052
|
|
369
|
+
# rubocop:disable Performance/Kernel/DefineMethod
|
|
368
370
|
target_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
|
371
|
+
# rubocop:enable Performance/Kernel/DefineMethod
|
|
369
372
|
end
|
|
370
373
|
target_module.send(visibility, method_name) # e.g., module.private(:my_method)
|
|
371
374
|
when :prepend
|
|
372
375
|
prepending_module = Module.new
|
|
376
|
+
# TODO: RUBY-1052
|
|
377
|
+
# rubocop:disable Performance/Kernel/DefineMethod
|
|
373
378
|
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
|
379
|
+
# rubocop:enable Performance/Kernel/DefineMethod
|
|
374
380
|
prepending_module.send(visibility, method_name)
|
|
375
381
|
# This prepends to the singleton class (it patches a class method)
|
|
376
382
|
target_module.prepend prepending_module
|
|
@@ -16,6 +16,23 @@ module Contrast
|
|
|
16
16
|
extend Contrast::Agent::Protect::Policy::RuleApplicator
|
|
17
17
|
|
|
18
18
|
class << self
|
|
19
|
+
# Calls the actual rule for this applicator, if required. Most rules
|
|
20
|
+
# invoke this from within their apply_rule method after doing
|
|
21
|
+
# whatever transformations they need to get into this common format.
|
|
22
|
+
#
|
|
23
|
+
# @param _method [Symbol] the name of the method for which this rule
|
|
24
|
+
# is invoked
|
|
25
|
+
# @param _exception [Exception] any exception raised; used for rules
|
|
26
|
+
# like Padding Oracle Attack (now defunct), which determine if the
|
|
27
|
+
# number and type of exceptions are an attack
|
|
28
|
+
# @param _properties [Hash] set of extra information provided by the
|
|
29
|
+
# applicator in an attempt to build a better story for the user
|
|
30
|
+
# @param _object [Object] the thing on which the triggering method
|
|
31
|
+
# was invoked
|
|
32
|
+
# @param args [Array<Object>] the arguments passed to the triggering
|
|
33
|
+
# method at invocation
|
|
34
|
+
# @raise [Contrast::SecurityException] on block, will pass the
|
|
35
|
+
# exception from the rule
|
|
19
36
|
def invoke _method, _exception, _properties, _object, args
|
|
20
37
|
return unless valid_input?(args)
|
|
21
38
|
return if skip_analysis?
|
|
@@ -23,6 +40,28 @@ module Contrast
|
|
|
23
40
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
|
|
24
41
|
end
|
|
25
42
|
|
|
43
|
+
# Calls the actual rule for this applicator, if required, when the
|
|
44
|
+
# triggering method is called from Marshal.load when it has been
|
|
45
|
+
# prepended.
|
|
46
|
+
#
|
|
47
|
+
# @param arg [Object] the argument passed to the triggering method
|
|
48
|
+
# at invocation
|
|
49
|
+
# @raise [Contrast::SecurityException] on block, will pass the
|
|
50
|
+
# exception from the rule
|
|
51
|
+
def prepended_invoke arg
|
|
52
|
+
return unless arg&.cs__is_a?(String)
|
|
53
|
+
return if skip_analysis?
|
|
54
|
+
|
|
55
|
+
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Allow the rule to check if the given input is an attempt to
|
|
59
|
+
# deserialize something in a way that will result in a command
|
|
60
|
+
# execution
|
|
61
|
+
#
|
|
62
|
+
# @param command [String] user input that potentially contains a
|
|
63
|
+
# Gadget, a Module that results in code execution on
|
|
64
|
+
# deserialization, and some form of command.
|
|
26
65
|
def apply_deserialization_command_check command
|
|
27
66
|
return unless command
|
|
28
67
|
return if skip_analysis?
|
|
@@ -38,11 +77,18 @@ module Contrast
|
|
|
38
77
|
|
|
39
78
|
private
|
|
40
79
|
|
|
80
|
+
# Determine if the rule would care about the arguments passed in
|
|
81
|
+
# this method invocation. Required b/c Ruby doesn't do type
|
|
82
|
+
# checking on its method signatures.
|
|
83
|
+
#
|
|
84
|
+
# @param args [Array<Object>] the arguments provided to the
|
|
85
|
+
# instrumented method
|
|
86
|
+
# @return [Boolean]
|
|
41
87
|
def valid_input? args
|
|
42
88
|
return false unless args&.any?
|
|
43
89
|
|
|
44
90
|
input = args[0]
|
|
45
|
-
input.
|
|
91
|
+
input.cs__is_a?(String)
|
|
46
92
|
end
|
|
47
93
|
end
|
|
48
94
|
end
|