contrast-agent 4.1.0 → 4.4.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.rb +5 -1
- data/lib/contrast/agent/assess.rb +0 -9
- data/lib/contrast/agent/assess/contrast_event.rb +49 -132
- data/lib/contrast/agent/assess/contrast_object.rb +54 -0
- data/lib/contrast/agent/assess/events/source_event.rb +4 -9
- data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
- 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 +41 -32
- data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
- data/lib/contrast/agent/assess/policy/propagator/append.rb +29 -15
- 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 +21 -18
- 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 +25 -17
- data/lib/contrast/agent/assess/policy/propagator/split.rb +83 -120
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +41 -25
- 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_method.rb +13 -8
- data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
- data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -3
- data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
- data/lib/contrast/agent/assess/properties.rb +0 -2
- data/lib/contrast/agent/assess/property/tagged.rb +56 -32
- 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 +134 -55
- 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/patching/policy/patch_status.rb +1 -1
- data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
- data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
- 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 +20 -14
- 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/request_context.rb +12 -0
- 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/thread.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +20 -5
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +18 -21
- data/lib/contrast/api/communication/response_processor.rb +8 -1
- data/lib/contrast/api/communication/socket_client.rb +22 -14
- data/lib/contrast/api/decorators.rb +2 -0
- data/lib/contrast/api/decorators/agent_startup.rb +58 -0
- data/lib/contrast/api/decorators/application_startup.rb +51 -0
- 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/route_coverage.rb +15 -5
- data/lib/contrast/api/decorators/trace_event.rb +58 -42
- 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/agent.rb +2 -0
- data/lib/contrast/components/app_context.rb +4 -22
- data/lib/contrast/components/assess.rb +36 -0
- data/lib/contrast/components/interface.rb +5 -3
- data/lib/contrast/components/sampling.rb +48 -6
- data/lib/contrast/components/scope.rb +72 -6
- data/lib/contrast/components/settings.rb +11 -7
- 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 +16 -14
- 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 +44 -44
- data/lib/contrast/framework/sinatra/support.rb +102 -42
- data/lib/contrast/logger/application.rb +0 -3
- data/lib/contrast/logger/log.rb +31 -15
- data/lib/contrast/utils/class_util.rb +3 -1
- data/lib/contrast/utils/duck_utils.rb +1 -1
- data/lib/contrast/utils/heap_dump_util.rb +103 -87
- data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
- data/lib/contrast/utils/object_share.rb +3 -3
- data/lib/contrast/utils/preflight_util.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 +12 -18
- data/resources/deadzone/policy.json +156 -0
- data/resources/protect/policy.json +12 -0
- data/ruby-agent.gemspec +61 -19
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +126 -113
- data/lib/contrast/agent/assess/rule.rb +0 -18
- data/lib/contrast/agent/assess/rule/base.rb +0 -52
- data/lib/contrast/agent/assess/rule/redos.rb +0 -67
- data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
- data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
- data/lib/contrast/utils/prevent_serialization.rb +0 -52
@@ -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
|
@@ -87,28 +91,15 @@ module Contrast
|
|
87
91
|
|
88
92
|
at = Hash.new { |h, k| h[k] = [] }
|
89
93
|
tags.each_pair do |key, value|
|
90
|
-
add =
|
94
|
+
add = nil
|
91
95
|
value.each do |tag|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
97
|
-
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
98
|
-
# the tag exists in the requested range, figure out the boundaries
|
99
|
-
when Contrast::Agent::Assess::Tag::WITHIN
|
100
|
-
start = tag.start_idx - range.begin
|
101
|
-
finish = range.size - start
|
102
|
-
add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
|
103
|
-
# the tag spans the requested range.
|
104
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
105
|
-
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
106
|
-
# part of the tag is being selected
|
107
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN
|
108
|
-
add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
96
|
+
within_range = resize_to_range(tag, range)
|
97
|
+
if within_range
|
98
|
+
add ||= []
|
99
|
+
add << within_range
|
109
100
|
end
|
110
101
|
end
|
111
|
-
next
|
102
|
+
next unless add&.any?
|
112
103
|
|
113
104
|
at[key] = add
|
114
105
|
end
|
@@ -173,14 +164,6 @@ module Contrast
|
|
173
164
|
tags.fetch(label, nil) if tracked?
|
174
165
|
end
|
175
166
|
|
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
167
|
# Remove all tags within the given ranges.
|
185
168
|
# This does not delete an entire tag if part of that tag is
|
186
169
|
# outside this range, meaning we may reduce sizes of tags
|
@@ -301,9 +284,19 @@ module Contrast
|
|
301
284
|
end
|
302
285
|
end
|
303
286
|
|
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
|
287
|
+
# Shift the tag ranges covering the given range to account for new
|
288
|
+
# data being added. We assume this is for a insertion, meaning we
|
289
|
+
# have to move tags out of the range and to the right. For example,
|
290
|
+
# given current tags: 0-15
|
291
|
+
# when we insert a range: 5-10
|
292
|
+
# then the result is: 0-5, 10-20
|
293
|
+
#
|
294
|
+
# Note that we disable Lint/DuplicateBranch in this branch in order
|
295
|
+
# list out all tag range cases in the proper order to make this
|
296
|
+
# easier to understand
|
297
|
+
#
|
298
|
+
# @param range [Range] the range of new information that's been
|
299
|
+
# inserted
|
307
300
|
def shift_tags_for_insertion range
|
308
301
|
return unless tracked?
|
309
302
|
|
@@ -325,19 +318,50 @@ module Contrast
|
|
325
318
|
when Contrast::Agent::Assess::Tag::WITHIN
|
326
319
|
tag.shift(length)
|
327
320
|
# the tag spans the range. leave the part below alone
|
328
|
-
when Contrast::Agent::Assess::Tag::WITHOUT
|
321
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
329
322
|
new_tag = tag.clone
|
330
323
|
new_tag.update_start(range.begin)
|
331
324
|
new_tag.shift(length)
|
332
325
|
add << new_tag
|
333
326
|
tag.update_end(range.begin)
|
334
|
-
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
|
327
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
|
335
328
|
tag.shift(length)
|
336
329
|
end
|
337
330
|
end
|
338
331
|
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
339
332
|
end
|
340
333
|
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
# Given a tag, compare it to a given range and, if any part of that tag is within the range, return a new tag
|
338
|
+
# covering the union of the original tag and the range. This new tag will start at the
|
339
|
+
# max(tag.start, range.start) and end at min(tag.end, range.end)
|
340
|
+
#
|
341
|
+
# @param tag [Contrast::Agent::Assess::Tag] the Tag that may be in this range
|
342
|
+
# @param range [Range] the span to check, inclusive to exclusive
|
343
|
+
# @return [Contrast::Agent::Assess::Tag, nil] a new tag, truncated to only span within the given range or nil
|
344
|
+
# if no overlap exists
|
345
|
+
def resize_to_range tag, range
|
346
|
+
comparison = tag.compare_range(range.begin, range.end)
|
347
|
+
# BELOW and ABOVE are not applicable to this check and result in nil
|
348
|
+
case comparison
|
349
|
+
# part of the tag is being selected
|
350
|
+
when Contrast::Agent::Assess::Tag::LOW_SPAN
|
351
|
+
Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
352
|
+
# the tag exists in the requested range, figure out the boundaries
|
353
|
+
when Contrast::Agent::Assess::Tag::WITHIN
|
354
|
+
start = tag.start_idx - range.begin
|
355
|
+
finish = range.size - start
|
356
|
+
Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
|
357
|
+
# the tag spans the requested range.
|
358
|
+
when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
|
359
|
+
Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
360
|
+
# part of the tag is being selected
|
361
|
+
when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
|
362
|
+
Contrast::Agent::Assess::Tag.new(tag.label, range.size)
|
363
|
+
end
|
364
|
+
end
|
341
365
|
end
|
342
366
|
end
|
343
367
|
end
|
@@ -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, nil] 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
|
@@ -46,27 +46,6 @@ module Contrast
|
|
46
46
|
agent_startup_routine
|
47
47
|
end
|
48
48
|
|
49
|
-
# Startup the Agent as part of the initialization process:
|
50
|
-
# - start the service sending thread, responsible for sending and
|
51
|
-
# processing messages
|
52
|
-
# - start the heartbeat thread, which triggers service startup
|
53
|
-
# - start instrumenting libraries and do a 'catchup' patch for everything
|
54
|
-
# we didn't see get loaded
|
55
|
-
# - enable TracePoint, which handles all class loads and required
|
56
|
-
# instrumentation going forward
|
57
|
-
def agent_startup_routine
|
58
|
-
logger.debug_with_time('middleware: starting service') do
|
59
|
-
Contrast::Agent.thread_watcher.ensure_running?
|
60
|
-
end
|
61
|
-
|
62
|
-
logger.debug_with_time('middleware: instrument shared libraries and patch') do
|
63
|
-
Contrast::Agent::Patching::Policy::Patcher.patch
|
64
|
-
end
|
65
|
-
|
66
|
-
AGENT.enable_tracepoint
|
67
|
-
Contrast::Agent::AtExitHook.exit_hook
|
68
|
-
end
|
69
|
-
|
70
49
|
# This is where we're hooked into the middleware stack. If the agent is enabled, we're ready
|
71
50
|
# to do some processing on a per request basis. If not, we just pass the request along to the
|
72
51
|
# next middleware in the stack.
|
@@ -76,14 +55,11 @@ module Contrast
|
|
76
55
|
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
|
77
56
|
# to the user up the Rack framework.
|
78
57
|
def call env
|
79
|
-
|
58
|
+
return app.call(env) unless AGENT.enabled?
|
80
59
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
app.call(env)
|
86
|
-
end
|
60
|
+
Contrast::Agent.heapdump_util.start_thread!
|
61
|
+
handle_first_request
|
62
|
+
call_with_agent(env)
|
87
63
|
end
|
88
64
|
|
89
65
|
private
|
@@ -91,6 +67,8 @@ module Contrast
|
|
91
67
|
def setup_agent
|
92
68
|
SETTINGS.reset_state
|
93
69
|
|
70
|
+
inform_deprecations
|
71
|
+
|
94
72
|
if CONFIG.invalid?
|
95
73
|
AGENT.disable!
|
96
74
|
logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
|
@@ -101,13 +79,67 @@ module Contrast
|
|
101
79
|
end
|
102
80
|
end
|
103
81
|
|
82
|
+
# Startup the Agent as part of the initialization process:
|
83
|
+
# - start the service sending thread, responsible for sending and
|
84
|
+
# processing messages
|
85
|
+
# - start the heartbeat thread, which triggers service startup
|
86
|
+
# - start instrumenting libraries and do a 'catchup' patch for everything
|
87
|
+
# we didn't see get loaded
|
88
|
+
# - enable TracePoint, which handles all class loads and required
|
89
|
+
# instrumentation going forward
|
90
|
+
def agent_startup_routine
|
91
|
+
logger.debug_with_time('middleware: starting service') do
|
92
|
+
Contrast::Agent.thread_watcher.ensure_running?
|
93
|
+
end
|
94
|
+
|
95
|
+
logger.debug_with_time('middleware: instrument shared libraries and patch') do
|
96
|
+
Contrast::Agent::Patching::Policy::Patcher.patch
|
97
|
+
end
|
98
|
+
|
99
|
+
logger.debug_with_time('middleware: enabling tracepoint') do
|
100
|
+
AGENT.enable_tracepoint
|
101
|
+
end
|
102
|
+
Contrast::Agent::AtExitHook.exit_hook
|
103
|
+
end
|
104
|
+
|
105
|
+
# Some things have to wait until first request to happen, either because
|
106
|
+
# resolution is not complete or because the framework will preload
|
107
|
+
# classes, which confuses some of our instrumentation.
|
108
|
+
def handle_first_request
|
109
|
+
@_handle_first_request ||= begin
|
110
|
+
Contrast::Agent::StaticAnalysis.catchup
|
111
|
+
force_patching
|
112
|
+
true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# TODO: RUBY-1090 remove this method and those it calls.
|
117
|
+
#
|
118
|
+
# These modules are auto-loaded by Rails, meaning they are defined at
|
119
|
+
# startup, but that they don't actually exist. We account for this in
|
120
|
+
# most cases by using the ClassUtil.truly_defined? method, but it appears
|
121
|
+
# to fail for these Modules. In the short term, we can forcing a re-patch
|
122
|
+
# so that their dead zones apply.
|
123
|
+
def force_patching
|
124
|
+
force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
|
125
|
+
force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
|
126
|
+
force_patch(ActionView::Template) if defined?(ActionView::Template)
|
127
|
+
end
|
128
|
+
|
129
|
+
def force_patch mod
|
130
|
+
data = Contrast::Agent::ModuleData.new(mod)
|
131
|
+
Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
|
132
|
+
end
|
133
|
+
|
104
134
|
# This is where we process each request we intercept as a middleware. We make the request context
|
105
135
|
# available globally so that it can be accessed from anywhere. A RequestHandler object is made
|
106
136
|
# for each request, which handles prefilter and postfilter operations.
|
137
|
+
# @param env [Hash] the various variables stored by this and other Middlewares to know the state
|
138
|
+
# and values of this Request
|
139
|
+
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
|
140
|
+
# to the user up the Rack framework.
|
107
141
|
def call_with_agent env
|
108
142
|
Contrast::Agent.thread_watcher.ensure_running?
|
109
|
-
return unless AGENT.enabled?
|
110
|
-
|
111
143
|
framework_request = Contrast::Agent.framework_manager.retrieve_request(env)
|
112
144
|
context = Contrast::Agent::RequestContext.new(framework_request)
|
113
145
|
response = nil
|
@@ -117,28 +149,9 @@ module Contrast
|
|
117
149
|
logger.request_start
|
118
150
|
request_handler = Contrast::Agent::RequestHandler.new(context)
|
119
151
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
request_handler.ruleset.prefilter
|
124
|
-
end
|
125
|
-
|
126
|
-
response = application_code(env) # pass request down the Rack chain with original env
|
127
|
-
|
128
|
-
# postfilter sequence
|
129
|
-
with_contrast_scope do
|
130
|
-
context.extract_after(response) # update context with final response information
|
131
|
-
|
132
|
-
if Contrast::Agent.framework_manager.streaming?(env)
|
133
|
-
context.reset_activity
|
134
|
-
request_handler.stream_safe_postfilter
|
135
|
-
else
|
136
|
-
request_handler.ruleset.postfilter
|
137
|
-
# return response stored in the context in case any postfilter rules updated the response data
|
138
|
-
response = context.response&.rack_response || response
|
139
|
-
request_handler.send_activity_messages
|
140
|
-
end
|
141
|
-
end
|
152
|
+
pre_call_with_agent(context, request_handler)
|
153
|
+
response = application_code(env)
|
154
|
+
post_call_with_agent(context, env, request_handler, response)
|
142
155
|
ensure
|
143
156
|
logger.request_end
|
144
157
|
end
|
@@ -148,6 +161,47 @@ module Contrast
|
|
148
161
|
handle_exception(e)
|
149
162
|
end
|
150
163
|
|
164
|
+
# Handle the operations the Agent needs to accomplish prior to the Application code executing during this
|
165
|
+
# request.
|
166
|
+
#
|
167
|
+
# @param context [Contrast::Agent::RequestContext]
|
168
|
+
# @param request_handler [Contrast::Agent::RequestHandler]
|
169
|
+
def pre_call_with_agent context, request_handler
|
170
|
+
with_contrast_scope do
|
171
|
+
context.service_extract_request
|
172
|
+
request_handler.ruleset.prefilter
|
173
|
+
end
|
174
|
+
rescue StandardError => e
|
175
|
+
raise e if security_exception?(e)
|
176
|
+
|
177
|
+
logger.error('Unable to execute agent pre_call', e)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Handle the operations the Agent needs to accomplish after the Application code executes during this request.
|
181
|
+
#
|
182
|
+
# @param context [Contrast::Agent::RequestContext]
|
183
|
+
# @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
|
184
|
+
# this Request
|
185
|
+
# @param request_handler [Contrast::Agent::RequestHandler]
|
186
|
+
# @param response [Array,Rack::Response]
|
187
|
+
def post_call_with_agent context, env, request_handler, response
|
188
|
+
with_contrast_scope do
|
189
|
+
context.extract_after(response) # update context with final response information
|
190
|
+
|
191
|
+
if Contrast::Agent.framework_manager.streaming?(env)
|
192
|
+
context.reset_activity
|
193
|
+
request_handler.stream_safe_postfilter
|
194
|
+
else
|
195
|
+
request_handler.ruleset.postfilter
|
196
|
+
request_handler.send_activity_messages
|
197
|
+
end
|
198
|
+
end
|
199
|
+
rescue StandardError => e
|
200
|
+
raise e if security_exception?(e)
|
201
|
+
|
202
|
+
logger.error('Unable to execute agent post_call', e)
|
203
|
+
end
|
204
|
+
|
151
205
|
def application_code env
|
152
206
|
logger.trace_with_time('application') do
|
153
207
|
app.call(env)
|
@@ -161,9 +215,7 @@ module Contrast
|
|
161
215
|
# We're only going to suppress SecurityExceptions indicating a blocked attack.
|
162
216
|
# And, only if the config.agent.ruby.exceptions.capture? is set
|
163
217
|
def handle_exception exception
|
164
|
-
if
|
165
|
-
exception.message&.include?(SECURITY_EXCEPTION_MARKER)
|
166
|
-
|
218
|
+
if security_exception?(exception)
|
167
219
|
exception_control = AGENT.exception_control
|
168
220
|
raise exception unless exception_control[:enable]
|
169
221
|
|
@@ -173,6 +225,33 @@ module Contrast
|
|
173
225
|
raise exception
|
174
226
|
end
|
175
227
|
end
|
228
|
+
|
229
|
+
# Is the given exception one raised by our Protect code?
|
230
|
+
#
|
231
|
+
# @param exception [Exception]
|
232
|
+
# @return [Boolean]
|
233
|
+
def security_exception? exception
|
234
|
+
exception.is_a?(Contrast::SecurityException) ||
|
235
|
+
exception.message&.include?(SECURITY_EXCEPTION_MARKER)
|
236
|
+
end
|
237
|
+
|
238
|
+
# As we deprecate support to prepare to remove dead code, we need to
|
239
|
+
# inform our users still relying on the now deprecated and soon to be
|
240
|
+
# removed functionality. This method handles doing that by leveraging the
|
241
|
+
# standard Kernel#warn approach
|
242
|
+
def inform_deprecations
|
243
|
+
# Ruby 2.5 is currently in security maintenance, meaning int is only
|
244
|
+
# receiving updates for security issues. It will move to eol on 31
|
245
|
+
# March 2021. As such, we can remove support for it in Q2. We'll begin
|
246
|
+
# the deprecation warnings now so that customers have time to reach out
|
247
|
+
# if they'll be impacted.
|
248
|
+
# TODO: RUBY-715 remove this part of the method, leaving it empty if
|
249
|
+
# there are no other deprecations, when we drop 2.5 support.
|
250
|
+
return unless RUBY_VERSION < '2.6.0'
|
251
|
+
|
252
|
+
Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
|
253
|
+
'Please contact Customer Support prior if you require continued support.')
|
254
|
+
end
|
176
255
|
end
|
177
256
|
end
|
178
257
|
end
|