contrast-agent 4.2.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +4 -4
- 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 +3 -3
- 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 +33 -13
- 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 +1 -1
- data/lib/contrast/components/assess.rb +36 -0
- data/lib/contrast/components/interface.rb +5 -3
- data/lib/contrast/components/scope.rb +23 -0
- data/lib/contrast/components/settings.rb +3 -3
- data/lib/contrast/config/assess_configuration.rb +2 -1
- data/lib/contrast/extension/assess/array.rb +1 -2
- data/lib/contrast/extension/assess/erb.rb +1 -3
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
- 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 +1 -1
- 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/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 +156 -0
- data/resources/protect/policy.json +12 -0
- data/ruby-agent.gemspec +9 -6
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +68 -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
|
@@ -366,17 +366,17 @@ module Contrast
|
|
366
366
|
# alias_method may be private
|
367
367
|
target_module.send(:alias_method, underlying_method_name, method_name)
|
368
368
|
# TODO: RUBY-1052
|
369
|
-
# rubocop:disable Kernel/DefineMethod
|
369
|
+
# rubocop:disable Performance/Kernel/DefineMethod
|
370
370
|
target_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
371
|
-
# rubocop:enable Kernel/DefineMethod
|
371
|
+
# rubocop:enable Performance/Kernel/DefineMethod
|
372
372
|
end
|
373
373
|
target_module.send(visibility, method_name) # e.g., module.private(:my_method)
|
374
374
|
when :prepend
|
375
375
|
prepending_module = Module.new
|
376
376
|
# TODO: RUBY-1052
|
377
|
-
# rubocop:disable Kernel/DefineMethod
|
377
|
+
# rubocop:disable Performance/Kernel/DefineMethod
|
378
378
|
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
379
|
-
# rubocop:enable Kernel/DefineMethod
|
379
|
+
# rubocop:enable Performance/Kernel/DefineMethod
|
380
380
|
prepending_module.send(visibility, method_name)
|
381
381
|
# This prepends to the singleton class (it patches a class method)
|
382
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
|