contrast-agent 6.0.0 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/cs__assess_regexp/cs__assess_regexp.c +15 -2
- data/ext/cs__assess_regexp/cs__assess_regexp.h +2 -0
- data/ext/cs__assess_string/cs__assess_string.c +8 -0
- data/ext/cs__assess_test/cs__assess_test.h +9 -0
- data/ext/cs__assess_test/cs__assess_tests.c +22 -0
- data/ext/cs__assess_test/extconf.rb +5 -0
- data/ext/cs__common/cs__common.c +101 -0
- data/ext/cs__common/cs__common.h +29 -5
- data/ext/cs__contrast_patch/cs__contrast_patch.c +1 -1
- data/ext/cs__tests/cs__tests.c +12 -0
- data/ext/cs__tests/cs__tests.h +3 -0
- data/ext/cs__tests/extconf.rb +5 -0
- data/lib/contrast/agent/assess/contrast_object.rb +16 -16
- data/lib/contrast/agent/assess/events/source_event.rb +17 -19
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -16
- data/lib/contrast/agent/assess/policy/propagator/split.rb +15 -19
- data/lib/contrast/agent/assess/policy/trigger_method.rb +3 -11
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +7 -2
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -3
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +60 -36
- data/lib/contrast/agent/at_exit_hook.rb +1 -1
- data/lib/contrast/agent/inventory/database_config.rb +10 -3
- data/lib/contrast/agent/middleware.rb +3 -3
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +0 -2
- data/lib/contrast/agent/patching/policy/patch.rb +13 -12
- data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +6 -2
- data/lib/contrast/agent/reporting/masker/masker.rb +8 -11
- data/lib/contrast/agent/reporting/masker/masker_utils.rb +8 -4
- data/lib/contrast/agent/reporting/reporter.rb +11 -16
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +49 -0
- data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +6 -2
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +53 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +48 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +64 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +70 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +57 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +56 -0
- data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +5 -1
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +58 -0
- data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +27 -0
- data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +20 -10
- data/lib/contrast/agent/reporting/reporting_events/application_update.rb +7 -12
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +9 -3
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +2 -4
- data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +3 -3
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +6 -2
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +7 -3
- data/lib/contrast/agent/reporting/reporting_events/poll.rb +6 -2
- data/lib/contrast/agent/reporting/reporting_events/preflight.rb +10 -8
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +6 -10
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +12 -20
- data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +27 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +17 -27
- data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +38 -0
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +8 -0
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +9 -4
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +54 -67
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +17 -7
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +8 -5
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +10 -10
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +32 -17
- data/lib/contrast/agent/reporting/settings/protect.rb +1 -1
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
- data/lib/contrast/agent/request.rb +3 -3
- data/lib/contrast/agent/request_context_extend.rb +1 -1
- data/lib/contrast/agent/request_handler.rb +3 -3
- data/lib/contrast/agent/response.rb +2 -0
- data/lib/contrast/agent/service_heartbeat.rb +6 -48
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/telemetry/base.rb +151 -0
- data/lib/contrast/agent/telemetry/events/event.rb +35 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +44 -36
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +29 -21
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +91 -73
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +62 -44
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +50 -33
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +20 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +32 -0
- data/lib/contrast/agent/telemetry/events/metric_event.rb +28 -0
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +123 -0
- data/lib/contrast/agent/thread_watcher.rb +52 -68
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent/worker_thread.rb +8 -0
- data/lib/contrast/agent.rb +1 -3
- data/lib/contrast/api/communication/messaging_queue.rb +28 -11
- data/lib/contrast/api/communication/response_processor.rb +7 -10
- data/lib/contrast/api/communication/speedracer.rb +1 -1
- data/lib/contrast/api/decorators/activity.rb +33 -0
- data/lib/contrast/api/decorators/http_request.rb +1 -1
- data/lib/contrast/components/config.rb +13 -22
- data/lib/contrast/components/contrast_service.rb +9 -0
- data/lib/contrast/components/settings.rb +10 -0
- data/lib/contrast/config/agent_configuration.rb +21 -11
- data/lib/contrast/config/api_configuration.rb +12 -8
- data/lib/contrast/config/api_proxy_configuration.rb +7 -3
- data/lib/contrast/config/application_configuration.rb +15 -11
- data/lib/contrast/config/assess_configuration.rb +13 -9
- data/lib/contrast/config/assess_rules_configuration.rb +5 -1
- data/lib/contrast/config/base_configuration.rb +3 -35
- data/lib/contrast/config/certification_configuration.rb +9 -5
- data/lib/contrast/config/exception_configuration.rb +10 -7
- data/lib/contrast/config/heap_dump_configuration.rb +13 -9
- data/lib/contrast/config/inventory_configuration.rb +9 -6
- data/lib/contrast/config/logger_configuration.rb +9 -6
- data/lib/contrast/config/protect_configuration.rb +9 -6
- data/lib/contrast/config/protect_rule_configuration.rb +12 -8
- data/lib/contrast/config/protect_rules_configuration.rb +18 -17
- data/lib/contrast/config/request_audit_configuration.rb +10 -7
- data/lib/contrast/config/root_configuration.rb +28 -11
- data/lib/contrast/config/ruby_configuration.rb +14 -11
- data/lib/contrast/config/sampling_configuration.rb +11 -8
- data/lib/contrast/config/server_configuration.rb +13 -9
- data/lib/contrast/config/service_configuration.rb +14 -11
- data/lib/contrast/configuration.rb +19 -10
- data/lib/contrast/framework/rails/patch/support.rb +13 -45
- data/lib/contrast/logger/aliased_logging.rb +87 -0
- data/lib/contrast/logger/application.rb +0 -4
- data/lib/contrast/tasks/config.rb +22 -13
- data/lib/contrast/utils/class_util.rb +2 -6
- data/lib/contrast/utils/invalid_configuration_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +2 -0
- data/lib/contrast/utils/middleware_utils.rb +1 -1
- data/lib/contrast/utils/object_share.rb +1 -1
- data/lib/contrast/utils/telemetry.rb +20 -2
- data/lib/contrast/utils/telemetry_client.rb +22 -10
- data/lib/contrast/utils/telemetry_hash.rb +41 -0
- data/lib/contrast/utils/telemetry_identifier.rb +16 -1
- data/lib/contrast.rb +9 -0
- data/ruby-agent.gemspec +1 -1
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +39 -16
- data/lib/contrast/agent/telemetry/events/metric_telemetry_event.rb +0 -26
- data/lib/contrast/agent/telemetry/events/startup_metrics_telemetry_event.rb +0 -121
- data/lib/contrast/agent/telemetry/events/telemetry_event.rb +0 -33
- data/lib/contrast/agent/telemetry/telemetry.rb +0 -150
- data/lib/contrast/utils/exclude_key.rb +0 -20
@@ -36,50 +36,64 @@ module Contrast
|
|
36
36
|
# @param response [Contrast::Agent::Response] the response of the application
|
37
37
|
# @return [Hash, nil] the evidence required to prove the violation of the rule
|
38
38
|
def violated? response
|
39
|
-
|
40
|
-
return evidence unless evidence.nil?
|
39
|
+
return unless header?(response) && meta_tag?(response)
|
41
40
|
|
42
|
-
|
43
|
-
return
|
44
|
-
return {} if !has_header && !has_tag
|
41
|
+
header_evidence = header_evidence(response)
|
42
|
+
return if header_evidence.nil?
|
45
43
|
|
46
|
-
|
44
|
+
tag_evidence = tag_evidence(response)
|
45
|
+
return if tag_evidence.nil?
|
46
|
+
|
47
|
+
{ DATA => [header_evidence, tag_evidence] }
|
47
48
|
end
|
48
49
|
|
49
|
-
#
|
50
|
+
# Is a cache-control header available?
|
50
51
|
# @param response [Contrast::Agent::Response] the response of the application
|
51
|
-
# @return [
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
has_header = !!cache_control
|
66
|
-
not_valid = has_header && !valid_header?(cache_control)
|
67
|
-
return has_header, evidence(HEADER_TYPE, NAME, cache_control) if not_valid
|
52
|
+
# @return [Boolean]
|
53
|
+
def header? response
|
54
|
+
cache_control = cache_control_from(response)
|
55
|
+
framework_supported? ? !cache_control.blank? : !!cache_control
|
56
|
+
end
|
57
|
+
|
58
|
+
# Is a cache-control meta tag available?
|
59
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
60
|
+
# @return [Boolean]
|
61
|
+
def meta_tag? response
|
62
|
+
return false if meta_tags(response).empty?
|
63
|
+
|
64
|
+
meta_tags(response).each do |tag|
|
65
|
+
return true if meta_cache_tag? tag[HTML_PROP]
|
68
66
|
end
|
69
|
-
|
67
|
+
|
68
|
+
false
|
70
69
|
end
|
71
70
|
|
72
|
-
|
71
|
+
def meta_tags response
|
72
|
+
return @_meta_tags if defined? @meta_tags
|
73
|
+
|
74
|
+
@_meta_tags = html_elements(response.body&.split(HEAD_TAG)&.last, META_START_STR)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Process Header value to determine if it violates rule
|
78
|
+
# @param response [Contrast::Agent::Response] the response of the application
|
79
|
+
# @return [Hash, nil] the evidence hash or nil
|
80
|
+
def header_evidence response
|
81
|
+
cache_control = cache_control_from(response)
|
82
|
+
value = framework_supported? ? cache_control : cache_control_to_s(cache_control)
|
83
|
+
# If header is valid, then this portion of the rule isn't violated.
|
84
|
+
return if valid_header?(value)
|
85
|
+
|
86
|
+
# evidence requires header value string, pull directly instead of rebuilding from hash
|
87
|
+
evidence(HEADER_TYPE, NAME, value)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Process Body to determine if cache control meta tag violates rule
|
73
91
|
# @param response [Contrast::Agent::Response] the response of the application
|
74
|
-
# @return [
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
meta_tags = html_elements(body&.split(HEAD_TAG)&.last, META_START_STR)
|
79
|
-
meta_tags.each do |tag|
|
80
|
-
return true, evidence(META_TYPE, PRAGMA, tag[HTML_PROP]) if meta_cache_tag? tag[HTML_PROP]
|
92
|
+
# @return [Hash, nil] the evidence hash or nil
|
93
|
+
def tag_evidence response
|
94
|
+
meta_tags(response).each do |tag|
|
95
|
+
return evidence(META_TYPE, PRAGMA, tag[HTML_PROP]) if meta_cache_tag? tag[HTML_PROP]
|
81
96
|
end
|
82
|
-
[!meta_tags.empty?, nil]
|
83
97
|
end
|
84
98
|
|
85
99
|
def potential_elements section, element_start
|
@@ -121,13 +135,23 @@ module Contrast
|
|
121
135
|
end
|
122
136
|
|
123
137
|
# This method accepts the violation and transforms it to the proper hash
|
124
|
-
# before
|
138
|
+
# before returning a violation
|
125
139
|
#
|
126
140
|
# @param type [String] String of Header or META of the type
|
127
141
|
# @param name [String] String of either cache-control or pragma
|
128
142
|
# @param value [String] String of the violated value
|
129
143
|
def evidence type, name, value
|
130
|
-
{
|
144
|
+
{ type: type, name: name, value: value }.to_json
|
145
|
+
end
|
146
|
+
|
147
|
+
def cache_control_from response
|
148
|
+
# Rails 7 adds support for the cache_control header directly in the
|
149
|
+
# rack response, we should use that value
|
150
|
+
if framework_supported? && response.rack_response.cs__is_a?(Rack::Response)
|
151
|
+
response.rack_response.cache_control
|
152
|
+
else
|
153
|
+
get_header_value(response)
|
154
|
+
end
|
131
155
|
end
|
132
156
|
|
133
157
|
# Rebuilds the String value of the Cache-Control Header
|
@@ -31,7 +31,7 @@ module Contrast
|
|
31
31
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
32
32
|
return unless context
|
33
33
|
|
34
|
-
Contrast::Agent.messaging_queue
|
34
|
+
Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -44,20 +44,27 @@ module Contrast
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
rescue StandardError => e
|
47
|
-
logger.
|
47
|
+
logger.warn('Unable to append db config', e)
|
48
48
|
nil
|
49
49
|
end
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
53
|
# We capture the active record configuration used by this application, as reported by
|
54
|
-
# ActiveRecord::Base.
|
54
|
+
# ActiveRecord::Base.connection_db_config, so that we can record it once and report it as needed.
|
55
55
|
#
|
56
56
|
# @return [Hash]
|
57
57
|
def active_record_config
|
58
58
|
return @_active_record_config if instance_variable_defined?(:@_active_record_config)
|
59
59
|
|
60
|
-
@_active_record_config = ActiveRecord::Base.
|
60
|
+
@_active_record_config = if ActiveRecord::Base.cs__respond_to?(:connection_db_config)
|
61
|
+
ActiveRecord::Base.connection_db_config
|
62
|
+
else
|
63
|
+
# TODO: RUBY-99999 - Remove when Rails 6.0 is not supported
|
64
|
+
ActiveRecord::Base.connection_config
|
65
|
+
end
|
66
|
+
rescue StandardError
|
67
|
+
nil
|
61
68
|
end
|
62
69
|
|
63
70
|
# The classes we instrument in order to determine which, if any, database(s) an application connects to take
|
@@ -13,7 +13,7 @@ require 'contrast/utils/heap_dump_util'
|
|
13
13
|
require 'contrast/utils/telemetry'
|
14
14
|
require 'contrast/agent/request_handler'
|
15
15
|
require 'contrast/agent/static_analysis'
|
16
|
-
require 'contrast/agent/telemetry/events/
|
16
|
+
require 'contrast/agent/telemetry/events/startup_metrics_event'
|
17
17
|
require 'contrast/utils/middleware_utils'
|
18
18
|
|
19
19
|
require 'contrast/utils/timer'
|
@@ -78,9 +78,9 @@ module Contrast
|
|
78
78
|
Contrast::Agent.thread_watcher.ensure_running?
|
79
79
|
end
|
80
80
|
|
81
|
-
if Contrast::Agent::Telemetry.enabled?
|
81
|
+
if Contrast::Agent::Telemetry::Base.enabled?
|
82
82
|
logger.debug_with_time('middleware: sending startup metrics telemetry event') do
|
83
|
-
event = Contrast::Agent::
|
83
|
+
event = Contrast::Agent::Telemetry::StartupMetricsEvent.new
|
84
84
|
Contrast::Agent.thread_watcher.telemetry_queue.send_event(event)
|
85
85
|
end
|
86
86
|
end
|
@@ -114,16 +114,15 @@ module Contrast
|
|
114
114
|
# (equivalent to :alias, where `module = module.singleton class`)
|
115
115
|
# (this is a.k.a. "class-method patch")
|
116
116
|
# :prepend -> prepend instance method of module
|
117
|
-
#
|
117
|
+
# :prepending singleton -> prepend singleton method of module
|
118
118
|
# @return [Symbol] new alias for the underlying method (presumably, so the patched method can call it)
|
119
119
|
def register_c_patch target_module_name, unbound_method, impl = :alias_instance
|
120
120
|
# These could be set as AfterLoadPatches.
|
121
121
|
method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
|
122
|
-
underlying_method_name =
|
122
|
+
underlying_method_name = underlying_method_name(method_name, impl)
|
123
123
|
|
124
124
|
target_module = Module.cs__const_get(target_module_name)
|
125
|
-
target_module = target_module.cs__singleton_class if %i[prepend_singleton
|
126
|
-
target_module = target_module.cs__singleton_class if %i[alias_singleton prepend].include? impl
|
125
|
+
target_module = target_module.cs__singleton_class if %i[prepend_singleton alias_singleton].include? impl
|
127
126
|
|
128
127
|
visibility = if target_module.private_instance_methods(false).include?(method_name)
|
129
128
|
:private
|
@@ -161,7 +160,7 @@ module Contrast
|
|
161
160
|
# @param visibility [Symbol] method visibility
|
162
161
|
def reflect_implementation impl, target_module, unbound_method, visibility
|
163
162
|
method_name = unbound_method.name.to_sym # rubocop:disable Security/Module/Name -- ruby built in attribute.
|
164
|
-
underlying_method_name =
|
163
|
+
underlying_method_name = underlying_method_name(method_name, impl)
|
165
164
|
|
166
165
|
case impl
|
167
166
|
when :alias_instance, :alias_singleton
|
@@ -174,14 +173,10 @@ module Contrast
|
|
174
173
|
end
|
175
174
|
target_module.send(visibility, method_name) # e.g., module.private(:my_method)
|
176
175
|
when :prepend_instance, :prepend_singleton
|
176
|
+
prepending_module = Module.new
|
177
|
+
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
178
|
+
prepending_module.send(visibility, method_name)
|
177
179
|
|
178
|
-
unless target_module.instance_methods(false).include? underlying_method_name
|
179
|
-
|
180
|
-
prepending_module = Module.new
|
181
|
-
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
182
|
-
prepending_module.send(visibility, method_name)
|
183
|
-
|
184
|
-
end
|
185
180
|
# This prepends to the singleton class (it patches a class method)
|
186
181
|
target_module.prepend prepending_module
|
187
182
|
# rubocop:enable Performance/Kernel/DefineMethod
|
@@ -207,6 +202,12 @@ module Contrast
|
|
207
202
|
|
208
203
|
!ASSESS&.enabled?
|
209
204
|
end
|
205
|
+
|
206
|
+
def underlying_method_name method_name, impl
|
207
|
+
return method_name.to_sym if %i[prepend_instance prepend_singleton].include? impl
|
208
|
+
|
209
|
+
build_unbound_method_name(method_name).to_sym
|
210
|
+
end
|
210
211
|
end
|
211
212
|
end
|
212
213
|
end
|
@@ -207,7 +207,7 @@ module Contrast
|
|
207
207
|
def patch_into_instance_methods module_data, module_policy
|
208
208
|
mod = module_data.mod
|
209
209
|
methods = all_instance_methods(mod, private: true)
|
210
|
-
methods.delete(:initialize) if mod.to_s.
|
210
|
+
methods.delete(:initialize) if mod.to_s.start_with?('RSpec') && mod.to_s.include?('Matchers')
|
211
211
|
patch_into_methods(mod, methods, module_policy, true)
|
212
212
|
end
|
213
213
|
|
@@ -8,6 +8,7 @@ require 'contrast/agent/protect/rule/no_sqli/no_sqli_input_classification'
|
|
8
8
|
require 'contrast/agent/protect/rule/sqli/sqli_input_classification'
|
9
9
|
require 'contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification'
|
10
10
|
require 'contrast/agent/protect/rule/unsafe_file_upload'
|
11
|
+
require 'contrast/components/logger'
|
11
12
|
require 'contrast/utils/object_share'
|
12
13
|
require 'contrast/agent/protect/rule/cmdi/cmdi_input_classification'
|
13
14
|
require 'contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification'
|
@@ -59,7 +60,7 @@ module Contrast
|
|
59
60
|
# @param request [Contrast::Agent::Request] current request context.
|
60
61
|
# @return input_analysis [Contrast::Agent::Reporting::InputAnalysis, nil]
|
61
62
|
def analyse request
|
62
|
-
return unless Contrast::
|
63
|
+
return unless Contrast::PROTECT.enabled?
|
63
64
|
return if request.nil?
|
64
65
|
|
65
66
|
inputs = extract_input request
|
@@ -86,8 +87,11 @@ module Contrast
|
|
86
87
|
next if value.nil? || value.empty?
|
87
88
|
|
88
89
|
PROTECT_RULES.each do |_key, rule|
|
90
|
+
protect_rule = Contrast::PROTECT.rule(rule[:rule_name])
|
91
|
+
logger.debug("Rule #{ rule[:rule_name] } not recognised in Protect rules") if protect_rule.nil?
|
92
|
+
|
89
93
|
# check if rule is enabled
|
90
|
-
next unless
|
94
|
+
next unless protect_rule&.enabled?
|
91
95
|
|
92
96
|
# method tampering doesn't take value
|
93
97
|
if rule[:rule_name] == Contrast::Agent::Protect::Rule::HttpMethodTampering::NAME
|
@@ -33,21 +33,18 @@ module Contrast
|
|
33
33
|
# @param [Contrast::Api::Dtm::Activity]
|
34
34
|
def mask activity
|
35
35
|
return unless Contrast::Agent::Reporter.enabled?
|
36
|
-
return unless activity
|
36
|
+
return unless activity
|
37
37
|
|
38
38
|
logger.debug('Searching for sensitive data',
|
39
39
|
activity: activity.__id__,
|
40
40
|
request: activity.http_request&.uuid)
|
41
|
-
mask_body
|
42
|
-
mask_query_string
|
43
|
-
mask_request_params
|
44
|
-
mask_request_cookies
|
45
|
-
mask_request_headers
|
46
|
-
rescue StandardError =>
|
47
|
-
logger.debug('Could not mask activity!',
|
48
|
-
activity: activity.__id__,
|
49
|
-
request: activity.http_request&.uuid,
|
50
|
-
error_msg: e.message)
|
41
|
+
mask_body(activity)
|
42
|
+
mask_query_string(activity)
|
43
|
+
mask_request_params(activity)
|
44
|
+
mask_request_cookies(activity)
|
45
|
+
mask_request_headers(activity)
|
46
|
+
rescue StandardError => _e
|
47
|
+
logger.debug('Could not mask activity!', activity: activity.__id__, request: activity.http_request&.uuid)
|
51
48
|
end
|
52
49
|
|
53
50
|
private
|
@@ -14,10 +14,14 @@ module Contrast
|
|
14
14
|
# @param field_hash [Protobuf::Field::FieldHash] hash to be masked
|
15
15
|
# @param results [Array<Contrast::Api::Dtm::AttackResults>]
|
16
16
|
# results to match against.
|
17
|
+
# @return [Hash]
|
17
18
|
def mask_field_hash field_hash, results
|
19
|
+
return {} unless field_hash&.any?
|
20
|
+
|
18
21
|
hash = {}
|
19
|
-
|
20
|
-
|
22
|
+
# Because this is the start of a built string, we have to be sure that it is not frozen.
|
23
|
+
masked = +''
|
24
|
+
field_hash.each do |entry|
|
21
25
|
# Protobuf::Field::FieldHash produces array, with the key as first param and value as second.
|
22
26
|
new_value = entry[1].delete(SEMICOLON).split(SPACE)
|
23
27
|
new_value.each do |value|
|
@@ -29,7 +33,7 @@ module Contrast
|
|
29
33
|
mask_with_dictionary results, hash
|
30
34
|
|
31
35
|
# Restore to original form.
|
32
|
-
hash.each { |k, v| masked += "#{ k
|
36
|
+
hash.each { |k, v| masked += "#{ k }=#{ v }; " }
|
33
37
|
masked.rstrip!
|
34
38
|
field_hash[entry[0]] = masked
|
35
39
|
end
|
@@ -48,7 +52,7 @@ module Contrast
|
|
48
52
|
hash = URI.decode_www_form(query).to_h
|
49
53
|
mask_with_dictionary results, hash
|
50
54
|
# Restore to string form.
|
51
|
-
hash.each { |k, v| masked += "#{ k
|
55
|
+
hash.each { |k, v| masked += "#{ k }=#{ v }&" }
|
52
56
|
query = masked
|
53
57
|
query.chomp!(masked[-1])
|
54
58
|
end
|
@@ -31,10 +31,6 @@ module Contrast
|
|
31
31
|
@_connection ||= client.initialize_connection
|
32
32
|
end
|
33
33
|
|
34
|
-
def audit
|
35
|
-
@_audit ||= Contrast::Agent::Reporting::Audit.new
|
36
|
-
end
|
37
|
-
|
38
34
|
def attempt_to_start?
|
39
35
|
unless cs__class.enabled?
|
40
36
|
logger.warn('Reporter service is disabled!')
|
@@ -65,7 +61,7 @@ module Contrast
|
|
65
61
|
# Suspend the Reporter and try sending the event after the timeout.
|
66
62
|
# The timeout is either default 15 min or received via TS response.
|
67
63
|
#
|
68
|
-
# @param event [Contrast::Agent::Reporting::
|
64
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent] Freshly pop-ed event.
|
69
65
|
def handle_resend event
|
70
66
|
sleep(client.timeout) if client.sleep?
|
71
67
|
# Retry once than discard the event. This is trigger on too many events of
|
@@ -75,6 +71,7 @@ module Contrast
|
|
75
71
|
client.wake_up
|
76
72
|
end
|
77
73
|
|
74
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
78
75
|
def send_event event
|
79
76
|
if ::Contrast::AGENT.disabled?
|
80
77
|
logger.warn('Attempted to queue event with Agent disabled', caller: caller, event: event)
|
@@ -84,23 +81,21 @@ module Contrast
|
|
84
81
|
return unless cs__class.enabled?
|
85
82
|
|
86
83
|
logger.debug('Enqueued event for sending', event_type: event.cs__class)
|
87
|
-
audit&.audit_event(event) if ::Contrast::API.request_audit_enable?
|
88
84
|
queue << event
|
89
85
|
end
|
90
86
|
|
91
87
|
# Use this to bypass the messaging queue and leave response processing to the caller
|
88
|
+
#
|
89
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
90
|
+
# @return [Net::HTTPResponse, nil]
|
92
91
|
def send_event_immediately event
|
93
92
|
if ::Contrast::AGENT.disabled?
|
94
93
|
logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
|
95
94
|
return
|
96
95
|
end
|
97
|
-
|
98
|
-
return unless response
|
99
|
-
|
100
|
-
client.handle_response(event, response, connection)
|
101
|
-
audit&.audit_event(event, response) if ::Contrast::API.request_audit_enable?
|
96
|
+
client.send_event(event, connection, send_immediately: true)
|
102
97
|
rescue StandardError => e
|
103
|
-
logger.error('Could not send message to
|
98
|
+
logger.error('Could not send message to TeamServer from Reporter queue.', e)
|
104
99
|
end
|
105
100
|
|
106
101
|
def delete_queue!
|
@@ -122,12 +117,12 @@ module Contrast
|
|
122
117
|
@_queue ||= Queue.new
|
123
118
|
end
|
124
119
|
|
120
|
+
# @param event [Contrast::Agent::Reporting::ReportingEvent]
|
125
121
|
def process_event event
|
126
|
-
|
127
|
-
|
128
|
-
handle_resend event
|
122
|
+
client.send_event(event, connection)
|
123
|
+
handle_resend(event) if client.mode.status == client.mode.resending
|
129
124
|
rescue StandardError => e
|
130
|
-
logger.error('Could not send message to
|
125
|
+
logger.error('Could not send message to TeamServer from Reporter queue.', e)
|
131
126
|
end
|
132
127
|
end
|
133
128
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/worker_thread'
|
5
|
+
require 'contrast/agent/reporting/report'
|
6
|
+
require 'contrast/components/logger'
|
7
|
+
require 'contrast/agent/reporting/reporting_events/agent_startup'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
# 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.
|
13
|
+
class ReporterHeartbeat < WorkerThread
|
14
|
+
include Contrast::Components::Logger::InstanceMethods
|
15
|
+
|
16
|
+
# TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
|
17
|
+
# to satisfy our goals.
|
18
|
+
REFRESH_INTERVAL_SEC = 60
|
19
|
+
|
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
|
+
def start_thread!
|
33
|
+
return if running?
|
34
|
+
|
35
|
+
@_thread = Contrast::Agent::Thread.new do
|
36
|
+
logger.info('Starting heartbeat thread.')
|
37
|
+
loop do
|
38
|
+
Contrast::Agent.reporter&.send_event(poll_message)
|
39
|
+
sleep(REFRESH_INTERVAL_SEC)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def poll_message
|
45
|
+
@_poll_message ||= Contrast::Agent::Reporting::Poll.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,7 +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/
|
4
|
+
require 'contrast/agent/reporting/reporting_events/server_reporting_event'
|
5
5
|
require 'contrast/config'
|
6
6
|
|
7
7
|
module Contrast
|
@@ -9,7 +9,7 @@ module Contrast
|
|
9
9
|
module Reporting
|
10
10
|
# AgentStartup Event which sends the agent data to TeamServer on the startup of a server or process,
|
11
11
|
# used to create a new Server entity there.
|
12
|
-
class AgentStartup < Contrast::Agent::Reporting::
|
12
|
+
class AgentStartup < Contrast::Agent::Reporting::ServerReportingEvent
|
13
13
|
def initialize
|
14
14
|
@event_method = :PUT
|
15
15
|
@event_endpoint = Contrast::Agent::Reporting::Endpoints::NG_ENDPOINTS[:agent_startup]
|
@@ -17,6 +17,10 @@ module Contrast
|
|
17
17
|
super
|
18
18
|
end
|
19
19
|
|
20
|
+
def file_name
|
21
|
+
'agent-startup'
|
22
|
+
end
|
23
|
+
|
20
24
|
def to_controlled_hash
|
21
25
|
{
|
22
26
|
environment: ::Contrast::CONFIG.root.server.environment,
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/components/logger'
|
5
|
+
require 'contrast/agent/reporting/reporting_events/application_reporting_event'
|
6
|
+
require 'contrast/agent/reporting/reporting_events/application_defend_activity'
|
7
|
+
require 'contrast/agent/reporting/reporting_events/application_inventory_activity'
|
8
|
+
|
9
|
+
module Contrast
|
10
|
+
module Agent
|
11
|
+
module Reporting
|
12
|
+
# This is the new ApplicationActivity class which will include all the needed information for the new reporting
|
13
|
+
# system to report
|
14
|
+
class ApplicationActivity < Contrast::Agent::Reporting::ApplicationReportingEvent
|
15
|
+
class << self
|
16
|
+
# @param app_activity_dtm [Contrast::Api::Dtm::Activity]
|
17
|
+
# @return [Contrast::Agent::Reporting::ApplicationActivity]
|
18
|
+
def convert app_activity_dtm
|
19
|
+
app_activity = new
|
20
|
+
app_activity.attach_data app_activity_dtm
|
21
|
+
app_activity
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@defend = []
|
27
|
+
@inventory = []
|
28
|
+
@event_type = :application_activity
|
29
|
+
@event_endpoint = Contrast::Agent::Reporting::Endpoints.application_activity
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def file_name
|
34
|
+
'activity-application'
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_controlled_hash
|
38
|
+
{
|
39
|
+
lastUpdate: since_last_update,
|
40
|
+
defend: @defend.map(&:to_controlled_hash),
|
41
|
+
inventory: @inventory.map(&:to_controlled_hash)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param activity_dtm [Contrast::Api::Dtm::ApplicationActivity]
|
46
|
+
def attach_data activity_dtm
|
47
|
+
@defend << Contrast::Agent::Reporting::ApplicationDefendActivity.convert(activity_dtm)
|
48
|
+
@inventory << Contrast::Agent::Reporting::ApplicationInventoryActivity.convert(activity_dtm)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/components/logger'
|
5
|
+
require 'contrast/agent/reporting/reporting_events/application_defend_attacker_activity'
|
6
|
+
|
7
|
+
module Contrast
|
8
|
+
module Agent
|
9
|
+
module Reporting
|
10
|
+
# This is the new ApplicationDefendActivity class which includes information about the defense of the application
|
11
|
+
# which was discovered during exercise of the application during this activity period.
|
12
|
+
class ApplicationDefendActivity
|
13
|
+
# @return [Array<Contrast::Agent::Reporting::ApplicationDefendAttackerActivity>]
|
14
|
+
attr_reader :attackers
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# @param activity_dtm [Contrast::Api::Dtm::ApplicationActivity]
|
18
|
+
# @return [Contrast::Agent::Reporting::ApplicationDefendActivity]
|
19
|
+
def convert activity_dtm
|
20
|
+
activity = new
|
21
|
+
activity.attach_data activity_dtm.results
|
22
|
+
activity
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@attackers = []
|
28
|
+
@event_type = :application_defend_activity
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_controlled_hash
|
33
|
+
{
|
34
|
+
attackers: attackers.map(&:to_controlled_hash)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param attack_dtms [Contrast::Api::Dtm::AttackResult]
|
39
|
+
# @return [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
|
40
|
+
def attach_data attack_dtms
|
41
|
+
attack_dtms.each do |attack|
|
42
|
+
@attackers << Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.convert(attack)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|