contrast-agent 6.2.0 → 6.3.0
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/ext/cs__assess_basic_object/cs__assess_basic_object.c +7 -5
- data/ext/cs__assess_kernel/cs__assess_kernel.c +14 -3
- data/ext/cs__assess_kernel/cs__assess_kernel.h +2 -0
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +10 -3
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +2 -1
- data/ext/cs__assess_regexp/cs__assess_regexp.c +9 -7
- data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.c → cs__assess_string_interpolation/cs__assess_string_interpolation.c} +14 -3
- data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.h → cs__assess_string_interpolation/cs__assess_string_interpolation.h} +1 -1
- data/ext/{cs__assess_string_interpolation26 → cs__assess_string_interpolation}/extconf.rb +0 -0
- data/ext/cs__common/cs__common.c +5 -4
- data/ext/cs__contrast_patch/cs__contrast_patch.c +3 -10
- data/lib/contrast/agent/assess/events/source_event.rb +16 -12
- data/lib/contrast/agent/assess/policy/policy_node.rb +6 -0
- data/lib/contrast/agent/assess/policy/propagation_method.rb +3 -39
- data/lib/contrast/agent/assess/policy/propagation_node.rb +8 -0
- data/lib/contrast/agent/assess/policy/propagator/base.rb +2 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +2 -47
- data/lib/contrast/agent/assess/policy/source_node.rb +1 -0
- data/lib/contrast/agent/assess/policy/trigger_node.rb +8 -0
- data/lib/contrast/agent/assess/property/evented.rb +4 -18
- data/lib/contrast/agent/assess/tag.rb +19 -0
- data/lib/contrast/agent/at_exit_hook.rb +8 -8
- data/lib/contrast/agent/inventory/database_config.rb +6 -3
- data/lib/contrast/agent/inventory/dependency_analysis.rb +3 -2
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +10 -10
- data/lib/contrast/agent/middleware.rb +4 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +27 -2
- data/lib/contrast/agent/patching/policy/policy.rb +5 -0
- data/lib/contrast/agent/patching/policy/policy_node.rb +6 -0
- data/lib/contrast/agent/patching/policy/trigger_node.rb +3 -0
- data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +3 -4
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -0
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +2 -2
- data/lib/contrast/agent/protect/rule/base.rb +1 -0
- data/lib/contrast/agent/protect/rule/no_sqli.rb +2 -0
- data/lib/contrast/agent/reporting/reporter.rb +32 -7
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +21 -15
- data/lib/contrast/agent/reporting/reporting_events/application_update.rb +5 -24
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +7 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +12 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +11 -1
- data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +29 -32
- data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +13 -1
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +11 -8
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -5
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +8 -1
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +9 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +11 -4
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -8
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -4
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -22
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -3
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -11
- data/lib/contrast/agent/request.rb +5 -7
- data/lib/contrast/agent/request_context.rb +8 -17
- data/lib/contrast/agent/request_context_extend.rb +8 -9
- data/lib/contrast/agent/request_handler.rb +9 -38
- data/lib/contrast/agent/rule_set.rb +4 -0
- data/lib/contrast/agent/service_heartbeat.rb +1 -1
- data/lib/contrast/agent/static_analysis.rb +6 -11
- data/lib/contrast/agent/telemetry/base.rb +35 -35
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +2 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +5 -2
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +3 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +3 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +0 -1
- data/lib/contrast/agent/thread_watcher.rb +1 -4
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/socket.rb +1 -0
- data/lib/contrast/api/decorators/message.rb +0 -6
- data/lib/contrast/api/decorators.rb +0 -2
- data/lib/contrast/components/assess.rb +0 -6
- data/lib/contrast/components/config.rb +18 -2
- data/lib/contrast/config/base_configuration.rb +0 -13
- data/lib/contrast/config/root_configuration.rb +1 -0
- data/lib/contrast/config/ruby_configuration.rb +2 -9
- data/lib/contrast/configuration.rb +0 -2
- data/lib/contrast/extension/assess/eval_trigger.rb +0 -4
- data/lib/contrast/extension/assess/hash.rb +3 -2
- data/lib/contrast/extension/assess/kernel.rb +22 -0
- data/lib/contrast/extension/assess/marshal.rb +16 -0
- data/lib/contrast/extension/assess/string.rb +21 -20
- data/lib/contrast/framework/base_support.rb +8 -0
- data/lib/contrast/framework/manager.rb +6 -20
- data/lib/contrast/framework/manager_extend.rb +0 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +11 -16
- data/lib/contrast/logger/aliased_logging.rb +2 -0
- data/lib/contrast/utils/assess/source_method_utils.rb +0 -9
- data/lib/contrast/utils/lru_cache.rb +3 -0
- data/lib/contrast/utils/middleware_utils.rb +2 -0
- data/lib/contrast/utils/telemetry_client.rb +7 -7
- data/resources/assess/policy.json +2 -11
- data/ruby-agent.gemspec +1 -1
- metadata +22 -20
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +0 -30
- data/lib/contrast/api/decorators/application_update.rb +0 -44
- data/lib/contrast/api/decorators/library.rb +0 -56
- data/lib/contrast/framework/platform_version.rb +0 -22
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'contrast/components/logger'
|
|
5
|
+
|
|
4
6
|
module Contrast
|
|
5
7
|
module Agent
|
|
6
8
|
module Assess
|
|
7
9
|
# A Tag represents a range in a given piece of data. It is used by the
|
|
8
10
|
# Agent to determine if a vulnerable dataflow has occurred.
|
|
9
11
|
class Tag
|
|
12
|
+
include Contrast::Components::Logger::InstanceMethods
|
|
13
|
+
|
|
10
14
|
attr_reader :label, # the label of this tag
|
|
11
15
|
:length, # length of tagged text within string
|
|
12
16
|
:start_idx, # start of range
|
|
@@ -35,6 +39,8 @@ module Contrast
|
|
|
35
39
|
def initialize label, length, start_idx = 0
|
|
36
40
|
@label = label
|
|
37
41
|
update_range(start_idx, start_idx + length)
|
|
42
|
+
rescue ArgumentError => e
|
|
43
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
# Return true if the tag covers the given position in the string
|
|
@@ -74,22 +80,32 @@ module Contrast
|
|
|
74
80
|
|
|
75
81
|
def shift idx
|
|
76
82
|
update_range(@start_idx + idx, @end_idx + idx)
|
|
83
|
+
rescue ArgumentError => e
|
|
84
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
77
85
|
end
|
|
78
86
|
|
|
79
87
|
def shift_end idx
|
|
80
88
|
update_range(@start_idx, @end_idx + idx)
|
|
89
|
+
rescue ArgumentError => e
|
|
90
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
def update_start start_idx
|
|
84
94
|
update_range(start_idx, @end_idx)
|
|
95
|
+
rescue ArgumentError => e
|
|
96
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
85
97
|
end
|
|
86
98
|
|
|
87
99
|
def update_end end_idx
|
|
88
100
|
update_range(@start_idx, end_idx)
|
|
101
|
+
rescue ArgumentError => e
|
|
102
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
89
103
|
end
|
|
90
104
|
|
|
91
105
|
def repurpose start_idx, end_idx
|
|
92
106
|
update_range(start_idx, end_idx)
|
|
107
|
+
rescue ArgumentError => e
|
|
108
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
93
109
|
end
|
|
94
110
|
|
|
95
111
|
# Given a tag, merge its ranges with this one
|
|
@@ -104,6 +120,8 @@ module Contrast
|
|
|
104
120
|
start = other.start_idx < @start_idx ? other.start_idx : @start_idx
|
|
105
121
|
finish = other.end_idx > @end_idx ? other.end_idx : @end_idx
|
|
106
122
|
update_range(start, finish)
|
|
123
|
+
rescue ArgumentError => e
|
|
124
|
+
logger.error('Range update for Tag failed with: ', e)
|
|
107
125
|
end
|
|
108
126
|
|
|
109
127
|
# Modification to tracked String can change the position and length of the tracked tag
|
|
@@ -160,6 +178,7 @@ module Contrast
|
|
|
160
178
|
|
|
161
179
|
private
|
|
162
180
|
|
|
181
|
+
# @raise[ArgumentError] raises if start_idx is negative or the end_idx is smaller than the start_idx
|
|
163
182
|
def update_range start_idx, end_idx
|
|
164
183
|
raise(ArgumentError, ERROR_NEGATIVE_START) if start_idx.negative?
|
|
165
184
|
raise(ArgumentError, ERROR_END_BEFORE_START) if end_idx < start_idx
|
|
@@ -31,16 +31,16 @@ module Contrast
|
|
|
31
31
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
32
32
|
return unless context
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
[
|
|
35
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
|
|
36
|
+
context.observed_route
|
|
37
|
+
].compact.each do |event|
|
|
38
|
+
Contrast::Agent.reporter&.send_event_immediately(event)
|
|
39
|
+
end
|
|
35
40
|
|
|
36
41
|
if Contrast::Agent::Reporter.enabled?
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.server_activity),
|
|
40
|
-
Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
|
|
41
|
-
].each do |event|
|
|
42
|
-
Contrast::Agent.reporter&.send_event_immediately(event)
|
|
43
|
-
end
|
|
42
|
+
event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
|
|
43
|
+
Contrast::Agent.reporter&.send_event_immediately(event)
|
|
44
44
|
else
|
|
45
45
|
Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
|
|
46
46
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'contrast/agent/reporting/reporting_events/architecture_component'
|
|
4
5
|
require 'contrast/api/decorators/architecture_component'
|
|
5
6
|
require 'contrast/components/logger'
|
|
6
7
|
require 'contrast/utils/object_share'
|
|
@@ -25,10 +26,11 @@ module Contrast
|
|
|
25
26
|
|
|
26
27
|
class << self
|
|
27
28
|
# Append the available database connection information to the message being sent to TeamServer. This message
|
|
28
|
-
# may be a Contrast::Api::Dtm::Activity or Contrast::
|
|
29
|
+
# may be a Contrast::Api::Dtm::Activity or Contrast::Agent::Reporting::ApplicationUpdate.
|
|
30
|
+
# Both report the same
|
|
29
31
|
# Contrast::Api::Dtm::ArchitectureComponent, but have different names for their fields.
|
|
30
32
|
#
|
|
31
|
-
# @param activity_or_update [Contrast::Api::Dtm::Activity, Contrast::
|
|
33
|
+
# @param activity_or_update [Contrast::Api::Dtm::Activity, Contrast::Agent::Reporting::ApplicationUpdate]
|
|
32
34
|
# @param hash_or_str [Hash, String] the database connection information
|
|
33
35
|
def append_db_config activity_or_update, hash_or_str = active_record_config
|
|
34
36
|
arr = build_from_db_config(hash_or_str)
|
|
@@ -40,7 +42,8 @@ module Contrast
|
|
|
40
42
|
if activity_or_update.is_a?(Contrast::Api::Dtm::Activity)
|
|
41
43
|
activity_or_update.architectures << a
|
|
42
44
|
else
|
|
43
|
-
|
|
45
|
+
converted_comp = Contrast::Agent::Reporting::ArchitectureComponent.convert(a)
|
|
46
|
+
activity_or_update.components << converted_comp
|
|
44
47
|
end
|
|
45
48
|
end
|
|
46
49
|
rescue StandardError => e
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require 'contrast/agent/reporting/reporting_events/library_discovery'
|
|
4
5
|
require 'contrast/agent/inventory/dependencies'
|
|
5
6
|
require 'contrast/utils/object_share'
|
|
6
7
|
|
|
@@ -14,7 +15,7 @@ module Contrast
|
|
|
14
15
|
|
|
15
16
|
# Report the dependencies of this application
|
|
16
17
|
#
|
|
17
|
-
# @return [Array<Contrast::
|
|
18
|
+
# @return [Array<Contrast::Agent::Reporting::LibraryDiscovery>] direct report form of
|
|
18
19
|
# Gem::Specification that have been loaded for this application.
|
|
19
20
|
def library_pb_list
|
|
20
21
|
return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.enabled?
|
|
@@ -24,7 +25,7 @@ module Contrast
|
|
|
24
25
|
next unless spec
|
|
25
26
|
next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
|
|
26
27
|
|
|
27
|
-
reported_lib_list << Contrast::
|
|
28
|
+
reported_lib_list << Contrast::Agent::Reporting::LibraryDiscovery.new(digest, spec)
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
end
|
|
@@ -52,7 +52,6 @@ module Contrast
|
|
|
52
52
|
return unless enabled?
|
|
53
53
|
|
|
54
54
|
spec_lookup_path = adjust_path_for_spec_lookup(path)
|
|
55
|
-
|
|
56
55
|
spec = Gem::Specification.find_by_path(spec_lookup_path)
|
|
57
56
|
unless spec
|
|
58
57
|
logger.debug('Unable to resolve gem spec for path', path: path)
|
|
@@ -70,15 +69,13 @@ module Contrast
|
|
|
70
69
|
logger.error('Unable to inventory file path', e, path: path)
|
|
71
70
|
end
|
|
72
71
|
|
|
73
|
-
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
|
|
72
|
+
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache. If
|
|
73
|
+
# no libraries had files loaded, or inventory analysis is disabled, return nil instead.
|
|
74
74
|
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
# @param observed_library_usage [Contrast::Agent::Reporting::ObservedLibraryUsage] the message to
|
|
78
|
-
# which to append the usage data
|
|
79
|
-
def generate_library_usage observed_library_usage
|
|
75
|
+
# @return [Contrast::Agent::Reporting::ObservedLibraryUsage, nil]
|
|
76
|
+
def generate_library_usage
|
|
80
77
|
return unless enabled?
|
|
81
|
-
return unless
|
|
78
|
+
return unless @gemdigest_cache.any?
|
|
82
79
|
|
|
83
80
|
# Disconnect gemdigest_cache and replace it with an empty one; synch so new libs cannot be added between the
|
|
84
81
|
# assignment and the replace
|
|
@@ -87,12 +84,15 @@ module Contrast
|
|
|
87
84
|
@gemdigest_cache = Hash.new { |hash, key| hash[key] = Set.new }
|
|
88
85
|
hold
|
|
89
86
|
end
|
|
87
|
+
|
|
88
|
+
observed_library_usage = Contrast::Agent::Reporting::ObservedLibraryUsage.new
|
|
90
89
|
gem_spec_digest_to_files.each_pair do |digest, files|
|
|
91
|
-
|
|
92
|
-
next if usage.names.empty?
|
|
90
|
+
next unless files.any?
|
|
93
91
|
|
|
92
|
+
usage = Contrast::Agent::Reporting::LibraryUsageObservation.new(digest, files)
|
|
94
93
|
observed_library_usage.observations << usage
|
|
95
94
|
end
|
|
95
|
+
observed_library_usage.observations.any? ? observed_library_usage : nil
|
|
96
96
|
rescue StandardError => e
|
|
97
97
|
logger.error('Unable to generate library usage.', e)
|
|
98
98
|
end
|
|
@@ -142,6 +142,8 @@ module Contrast
|
|
|
142
142
|
#
|
|
143
143
|
# @param context [Contrast::Agent::RequestContext]
|
|
144
144
|
# @param request_handler [Contrast::Agent::RequestHandler]
|
|
145
|
+
# @raise [StandardError] raises an error if the exception is security concern
|
|
146
|
+
# which is being triggered when there is a failure within the pre-call with the agent
|
|
145
147
|
def pre_call_with_agent context, request_handler
|
|
146
148
|
with_contrast_scope do
|
|
147
149
|
context.service_extract_request
|
|
@@ -160,6 +162,8 @@ module Contrast
|
|
|
160
162
|
# this Request
|
|
161
163
|
# @param request_handler [Contrast::Agent::RequestHandler]
|
|
162
164
|
# @param response [Array,Rack::Response]
|
|
165
|
+
# @raise [StandardError] raises an error if the exception is security concern
|
|
166
|
+
# which is being triggered when there is a failure within the post-call with the agent
|
|
163
167
|
def post_call_with_agent context, env, request_handler, response
|
|
164
168
|
with_contrast_scope do
|
|
165
169
|
context.extract_after(response) # update context with final response information
|
|
@@ -5,6 +5,8 @@ require 'contrast/agent/patching/policy/after_load_patch'
|
|
|
5
5
|
require 'contrast/components/logger'
|
|
6
6
|
require 'contrast/framework/manager'
|
|
7
7
|
require 'contrast/extension/extension'
|
|
8
|
+
require 'contrast/extension/assess/kernel'
|
|
9
|
+
require 'contrast/extension/assess/marshal'
|
|
8
10
|
|
|
9
11
|
module Contrast
|
|
10
12
|
module Agent
|
|
@@ -29,22 +31,31 @@ module Contrast
|
|
|
29
31
|
# there are no require time side effects of loading our core
|
|
30
32
|
# extensions.
|
|
31
33
|
def apply_direct_patches!
|
|
32
|
-
@_apply_direct_patches ||= begin
|
|
34
|
+
@_apply_direct_patches ||= begin
|
|
33
35
|
paths = %w[
|
|
34
36
|
array
|
|
35
37
|
basic_object
|
|
36
38
|
module
|
|
37
39
|
fiber_track
|
|
38
40
|
hash
|
|
41
|
+
kernel
|
|
39
42
|
marshal_module
|
|
40
43
|
regexp
|
|
41
44
|
string
|
|
42
|
-
|
|
45
|
+
string_interpolation
|
|
43
46
|
].cs__freeze
|
|
44
47
|
paths.each do |p|
|
|
45
48
|
path_part = "cs__assess_#{ p }"
|
|
46
49
|
Contrast::Extension::Assess::InstrumentHelper.instrument("#{ path_part }/#{ path_part }")
|
|
47
50
|
end
|
|
51
|
+
# apply Kernel#exec alias patch:
|
|
52
|
+
unless Contrast::Agent::Assess.cs__object_method_prepended?(Kernel, :exec, false)
|
|
53
|
+
apply_kernel_exec_alias_patch
|
|
54
|
+
end
|
|
55
|
+
# apply Marshal load alias patch:
|
|
56
|
+
unless Contrast::Agent::Assess.cs__object_method_prepended?(Marshal, :load, false)
|
|
57
|
+
apply_marshal_load_alias_patch
|
|
58
|
+
end
|
|
48
59
|
true
|
|
49
60
|
end
|
|
50
61
|
end
|
|
@@ -96,6 +107,20 @@ module Contrast
|
|
|
96
107
|
patch.instrument!
|
|
97
108
|
after_load_patches.delete_if(&:applied?)
|
|
98
109
|
end
|
|
110
|
+
|
|
111
|
+
# Applies the Kernel#exec alias patch
|
|
112
|
+
def apply_kernel_exec_alias_patch
|
|
113
|
+
Kernel.extend(Contrast::Extension::Assess::ContrastKernel)
|
|
114
|
+
Marshal.alias_method(:cs__kernel_exec, :exec)
|
|
115
|
+
Marshal.alias_method(:exec, :cs__kernel_exec)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Applies the Marshal#load alias patch
|
|
119
|
+
def apply_marshal_load_alias_patch
|
|
120
|
+
Marshal.extend(Contrast::Extension::Assess::ContrastMarshal)
|
|
121
|
+
Marshal.alias_method(:cs__marshal_load, :load)
|
|
122
|
+
Marshal.alias_method(:load, :cs__marshal_load)
|
|
123
|
+
end
|
|
99
124
|
end
|
|
100
125
|
end
|
|
101
126
|
end
|
|
@@ -35,16 +35,21 @@ module Contrast
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Indicates the folder in `resources` where this policy lives.
|
|
38
|
+
#
|
|
39
|
+
# @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
|
|
38
40
|
def self.policy_folder
|
|
39
41
|
raise(NoMethodError, 'specify policy_folder for patching')
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
# Indicates is this feature has been disabled by the configuration, read at startup, and therefore can never
|
|
43
45
|
# be enabled.
|
|
46
|
+
#
|
|
47
|
+
# @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
|
|
44
48
|
def disabled_globally?
|
|
45
49
|
raise(NoMethodError, 'specify disabled_globally? conditions for patching')
|
|
46
50
|
end
|
|
47
51
|
|
|
52
|
+
# @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
|
|
48
53
|
def node_type
|
|
49
54
|
raise(NoMethodError, 'specify the concrete node type for this poilcy')
|
|
50
55
|
end
|
|
@@ -47,10 +47,14 @@ module Contrast
|
|
|
47
47
|
# Scope of the method parsed from our JSON policy.
|
|
48
48
|
attr_reader :method_scope
|
|
49
49
|
|
|
50
|
+
# @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
|
|
51
|
+
# that method implemented, but is being called on.
|
|
50
52
|
def node_class
|
|
51
53
|
raise(NoMethodError, 'specify the type of the feature for which this node patches')
|
|
52
54
|
end
|
|
53
55
|
|
|
56
|
+
# @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
|
|
57
|
+
# that method implemented, but is being called on.
|
|
54
58
|
def feature
|
|
55
59
|
raise(NoMethodError, 'specify the name of the feature for which this node patches')
|
|
56
60
|
end
|
|
@@ -62,6 +66,8 @@ module Contrast
|
|
|
62
66
|
# Don't let nodes be created that will be missing things we need
|
|
63
67
|
# later on. Really, if they don't have these things, they couldn't have
|
|
64
68
|
# done their jobs anyway.
|
|
69
|
+
#
|
|
70
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
|
65
71
|
def validate
|
|
66
72
|
unless class_name
|
|
67
73
|
raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
|
|
@@ -41,6 +41,7 @@ module Contrast
|
|
|
41
41
|
NODE
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
|
44
45
|
def validate
|
|
45
46
|
super
|
|
46
47
|
unless applicator.public_methods(false).any?(applicator_method)
|
|
@@ -52,6 +53,7 @@ module Contrast
|
|
|
52
53
|
validate_rule
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
|
55
57
|
def validate_properties
|
|
56
58
|
if (required_properties & optional_properties).any?
|
|
57
59
|
raise(ArgumentError,
|
|
@@ -66,6 +68,7 @@ module Contrast
|
|
|
66
68
|
raise(ArgumentError, "#{ id } did not have a required property. Unable to create.")
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
# @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
|
|
69
72
|
def validate_rule
|
|
70
73
|
raise(ArgumentError, 'Unknown rule did not have a proper name. Unable to create.') unless rule_id
|
|
71
74
|
raise(ArgumentError, "#{ id } did not have a proper applicator. Unable to create.") unless applicator
|
|
@@ -31,13 +31,12 @@ module Contrast
|
|
|
31
31
|
# was invoked
|
|
32
32
|
# @param args [Array<Object>] the arguments passed to the triggering
|
|
33
33
|
# method at invocation
|
|
34
|
-
# @raise [Contrast::SecurityException] on block, will pass the
|
|
35
|
-
# exception from the rule
|
|
36
34
|
def invoke _method, _exception, _properties, _object, args
|
|
37
35
|
return unless valid_input?(args)
|
|
38
36
|
return if skip_analysis?
|
|
39
37
|
|
|
40
38
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
|
|
39
|
+
# add rescue here
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
# Calls the actual rule for this applicator, if required, when the
|
|
@@ -46,13 +45,12 @@ module Contrast
|
|
|
46
45
|
#
|
|
47
46
|
# @param arg [Object] the argument passed to the triggering method
|
|
48
47
|
# at invocation
|
|
49
|
-
# @raise [Contrast::SecurityException] on block, will pass the
|
|
50
|
-
# exception from the rule
|
|
51
48
|
def prepended_invoke arg
|
|
52
49
|
return unless arg&.cs__is_a?(String)
|
|
53
50
|
return if skip_analysis?
|
|
54
51
|
|
|
55
52
|
rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
|
|
53
|
+
# add rescue here
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
# Allow the rule to check if the given input is an attempt to
|
|
@@ -67,6 +65,7 @@ module Contrast
|
|
|
67
65
|
return if skip_analysis?
|
|
68
66
|
|
|
69
67
|
rule.check_command_scope(command)
|
|
68
|
+
# add rescue here
|
|
70
69
|
end
|
|
71
70
|
|
|
72
71
|
protected
|
|
@@ -64,6 +64,7 @@ module Contrast
|
|
|
64
64
|
write_marker && possible_write?(write_marker)
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# @raise [Contrast::SecurityException] re-raises if some attack is blocked and raised from the infilter
|
|
67
68
|
def path_traversal_rule path, possible_write, object, method
|
|
68
69
|
return unless applies_to?(path, possible_write: possible_write)
|
|
69
70
|
|
|
@@ -61,8 +61,7 @@ module Contrast
|
|
|
61
61
|
# was invoked
|
|
62
62
|
# @param _args [Array<Object>] the arguments passed to the triggering
|
|
63
63
|
# method at invocation
|
|
64
|
-
# @raise [
|
|
65
|
-
# exception from the rule
|
|
64
|
+
# @raise [NoMethodError] This is abstract method
|
|
66
65
|
def invoke _method, _exception, _properties, _object, _args
|
|
67
66
|
raise(NoMethodError, 'This is abstract, override it.')
|
|
68
67
|
end
|
|
@@ -70,6 +69,7 @@ module Contrast
|
|
|
70
69
|
# The name of the rule, as expected by the Contrast Service and Contrast UI.
|
|
71
70
|
#
|
|
72
71
|
# @return [String]
|
|
72
|
+
# @raise [NoMethodError] This is abstract method
|
|
73
73
|
def rule_name
|
|
74
74
|
raise(NoMethodError, 'This is abstract, override it.')
|
|
75
75
|
end
|
|
@@ -230,6 +230,7 @@ module Contrast
|
|
|
230
230
|
# the rule and matched the attack detection logic
|
|
231
231
|
# @param _kwargs [Hash] key-value pairs used by the rule to build a
|
|
232
232
|
# report.
|
|
233
|
+
# @raise[NoMethodError] raises if subclass did not implement this method on extend
|
|
233
234
|
def find_attacker _context, _potential_attack_string, **_kwargs
|
|
234
235
|
raise(NoMethodError, "Rule #{ rule_name } did not implement find_attack")
|
|
235
236
|
end
|
|
@@ -38,14 +38,12 @@ module Contrast
|
|
|
38
38
|
@_thread = Contrast::Agent::Thread.new do
|
|
39
39
|
logger.debug('Starting background Reporter thread.')
|
|
40
40
|
loop do
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# to figure out why that is and lock it so that it isn't.
|
|
44
|
-
next unless client && connection
|
|
41
|
+
next unless connected?
|
|
42
|
+
next unless app_create_complete?
|
|
45
43
|
|
|
46
44
|
process_event(queue.pop)
|
|
47
45
|
rescue StandardError => e
|
|
48
|
-
logger.debug('Reporter thread could not process because of:',
|
|
46
|
+
logger.debug('Reporter thread could not process because of:', e)
|
|
49
47
|
end
|
|
50
48
|
end
|
|
51
49
|
end
|
|
@@ -71,7 +69,6 @@ module Contrast
|
|
|
71
69
|
end
|
|
72
70
|
return unless event
|
|
73
71
|
|
|
74
|
-
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
|
75
72
|
queue << event
|
|
76
73
|
end
|
|
77
74
|
|
|
@@ -84,7 +81,9 @@ module Contrast
|
|
|
84
81
|
logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
|
|
85
82
|
return
|
|
86
83
|
end
|
|
87
|
-
|
|
84
|
+
return unless event
|
|
85
|
+
|
|
86
|
+
client.send_event(event, connection)
|
|
88
87
|
rescue StandardError => e
|
|
89
88
|
logger.error('Could not send message to TeamServer from Reporter queue.', e)
|
|
90
89
|
end
|
|
@@ -108,6 +107,32 @@ module Contrast
|
|
|
108
107
|
@_queue ||= Queue.new
|
|
109
108
|
end
|
|
110
109
|
|
|
110
|
+
# TODO: RUBY-99999
|
|
111
|
+
# The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
|
|
112
|
+
# to figure out why that is and lock it so that it isn't.
|
|
113
|
+
#
|
|
114
|
+
# @return [Boolean]
|
|
115
|
+
def connected?
|
|
116
|
+
return true if client && connection
|
|
117
|
+
|
|
118
|
+
logger.debug('No client/connection; sleeping', client: client, connection: connection)
|
|
119
|
+
sleep(5)
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Unless we're in bypass mode, we need to make sure the service has started and built the application on
|
|
124
|
+
# TeamServer since we're doing a split style here.
|
|
125
|
+
#
|
|
126
|
+
# @return [Boolean]
|
|
127
|
+
def app_create_complete?
|
|
128
|
+
return true if Contrast::CONTRAST_SERVICE.use_agent_communication?
|
|
129
|
+
return true if Contrast::Agent.messaging_queue&.speedracer&.status&.startup_messages_sent?
|
|
130
|
+
|
|
131
|
+
logger.debug('Service startup incomplete; Application may not be created; sleeping')
|
|
132
|
+
sleep(5)
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
|
|
111
136
|
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
|
112
137
|
def process_event event
|
|
113
138
|
client.send_event(event, connection)
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
require 'contrast/agent/worker_thread'
|
|
5
5
|
require 'contrast/agent/reporting/report'
|
|
6
6
|
require 'contrast/components/logger'
|
|
7
|
-
require 'contrast/agent/
|
|
7
|
+
require 'contrast/agent/inventory/dependency_usage_analysis'
|
|
8
|
+
require 'contrast/agent/reporting/reporting_events/poll'
|
|
9
|
+
require 'contrast/agent/reporting/reporting_events/server_activity'
|
|
8
10
|
|
|
9
11
|
module Contrast
|
|
10
12
|
module Agent
|
|
11
13
|
# The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
|
|
12
|
-
# reach out to get the latest settings for this application.
|
|
14
|
+
# reach out to get the latest settings for this application. It also sends out those messages which do not need to
|
|
15
|
+
# be associated directly with a request, such as Server Activity and Library Observation.
|
|
13
16
|
class ReporterHeartbeat < WorkerThread
|
|
14
17
|
include Contrast::Components::Logger::InstanceMethods
|
|
15
18
|
|
|
@@ -17,33 +20,36 @@ module Contrast
|
|
|
17
20
|
# to satisfy our goals.
|
|
18
21
|
REFRESH_INTERVAL_SEC = 60
|
|
19
22
|
|
|
20
|
-
# check if we can report to TS
|
|
21
|
-
#
|
|
22
|
-
# @return[Boolean] true if bypass is enabled, or false if bypass disabled
|
|
23
|
-
def enabled?
|
|
24
|
-
@_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
|
|
25
|
-
@_enabled
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def connection
|
|
29
|
-
@_connection ||= client.initialize_connection
|
|
30
|
-
end
|
|
31
|
-
|
|
32
23
|
def start_thread!
|
|
33
24
|
return if running?
|
|
34
25
|
|
|
35
26
|
@_thread = Contrast::Agent::Thread.new do
|
|
36
27
|
logger.info('Starting heartbeat thread.')
|
|
37
28
|
loop do
|
|
38
|
-
|
|
29
|
+
polling_events.each do |event|
|
|
30
|
+
Contrast::Agent.reporter&.send_event(event)
|
|
31
|
+
end
|
|
39
32
|
sleep(REFRESH_INTERVAL_SEC)
|
|
40
33
|
end
|
|
41
34
|
end
|
|
42
35
|
end
|
|
43
36
|
|
|
37
|
+
private
|
|
38
|
+
|
|
44
39
|
def poll_message
|
|
45
40
|
@_poll_message ||= Contrast::Agent::Reporting::Poll.new
|
|
46
41
|
end
|
|
42
|
+
|
|
43
|
+
# Those events which should be sent periodically, rather than on event or request.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
|
|
46
|
+
def polling_events
|
|
47
|
+
[
|
|
48
|
+
Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
|
|
49
|
+
Contrast::Agent::Reporting::ServerActivity.new,
|
|
50
|
+
poll_message
|
|
51
|
+
].compact
|
|
52
|
+
end
|
|
47
53
|
end
|
|
48
54
|
end
|
|
49
55
|
end
|
|
@@ -17,23 +17,16 @@ module Contrast
|
|
|
17
17
|
# system. Contains data used by TeamServer to render the Flow Map and SCA features.
|
|
18
18
|
#
|
|
19
19
|
# @attr_reader components [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
|
|
20
|
-
# @
|
|
20
|
+
# @attr_accessor libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
|
|
21
21
|
class ApplicationUpdate < Contrast::Agent::Reporting::ApplicationReportingEvent
|
|
22
|
-
attr_reader :components
|
|
23
|
-
|
|
24
|
-
class << self
|
|
25
|
-
# @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
|
|
26
|
-
# @return [Contrast::Agent::Reporting::ApplicationUpdate]
|
|
27
|
-
def convert app_update_dtm
|
|
28
|
-
report = new
|
|
29
|
-
report.attach_data(app_update_dtm)
|
|
30
|
-
report
|
|
31
|
-
end
|
|
32
|
-
end
|
|
22
|
+
attr_reader :components
|
|
23
|
+
attr_accessor :libraries
|
|
33
24
|
|
|
34
25
|
def initialize
|
|
35
26
|
@event_method = :PUT
|
|
36
27
|
@event_endpoint = "#{ Contrast::API.api_url }/api/ng/update/application"
|
|
28
|
+
@components = []
|
|
29
|
+
@libraries = []
|
|
37
30
|
super
|
|
38
31
|
end
|
|
39
32
|
|
|
@@ -41,18 +34,6 @@ module Contrast
|
|
|
41
34
|
'update-application'
|
|
42
35
|
end
|
|
43
36
|
|
|
44
|
-
# Attach the data from the protobuf models to this reporter so that it can be sent to TeamServer directly.
|
|
45
|
-
#
|
|
46
|
-
# @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
|
|
47
|
-
def attach_data app_update_dtm
|
|
48
|
-
@components = app_update_dtm.components.map do |component|
|
|
49
|
-
Contrast::Agent::Reporting::ArchitectureComponent.convert(component)
|
|
50
|
-
end
|
|
51
|
-
@libraries = app_update_dtm.libraries.values.map do |library|
|
|
52
|
-
Contrast::Agent::Reporting::LibraryDiscovery.convert(library)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
37
|
# Convert the instance variables on the class, and other information, into the identifiers required for
|
|
57
38
|
# TeamServer to process the JSON form of this message.
|
|
58
39
|
#
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require 'contrast/api/dtm.pb'
|
|
5
|
+
require 'contrast/components/logger'
|
|
5
6
|
|
|
6
7
|
module Contrast
|
|
7
8
|
module Agent
|
|
@@ -17,6 +18,7 @@ module Contrast
|
|
|
17
18
|
# @attr_reader url [String] the url used to connect to the component. Required for reporting.
|
|
18
19
|
# @attr_reader vendor [String] the publisher of the component, like MySQL.
|
|
19
20
|
class ArchitectureComponent
|
|
21
|
+
include Contrast::Components::Logger::InstanceMethods
|
|
20
22
|
# required attributes
|
|
21
23
|
attr_reader :type, :url
|
|
22
24
|
# optional attributes
|
|
@@ -55,7 +57,12 @@ module Contrast
|
|
|
55
57
|
# @return [Hash]
|
|
56
58
|
# @raise [ArgumentError]
|
|
57
59
|
def to_controlled_hash
|
|
58
|
-
|
|
60
|
+
begin
|
|
61
|
+
validate
|
|
62
|
+
rescue ArgumentError => e
|
|
63
|
+
logger.error('ArchitectureComponent validation failed with: ', e)
|
|
64
|
+
return
|
|
65
|
+
end
|
|
59
66
|
{
|
|
60
67
|
remoteHost: remote_host,
|
|
61
68
|
remotePort: remote_port,
|