contrast-agent 6.9.0 → 6.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/ext/build_funchook.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/split.rb +1 -4
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -1
- data/lib/contrast/agent/middleware.rb +5 -3
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +6 -2
- data/lib/contrast/agent/patching/policy/trigger_node.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +76 -83
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +40 -35
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +2 -0
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +6 -3
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +5 -2
- data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +3 -0
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +12 -0
- data/lib/contrast/agent/protect/rule/base.rb +19 -5
- data/lib/contrast/agent/protect/rule/base_service.rb +7 -2
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/bot_blocker.rb +8 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +9 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +1 -1
- data/lib/contrast/agent/protect/rule/deserialization.rb +2 -2
- data/lib/contrast/agent/protect/rule/no_sqli.rb +24 -2
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/path_traversal.rb +8 -0
- data/lib/contrast/agent/protect/rule/sqli/postgres_sql_scanner.rb +0 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +0 -1
- data/lib/contrast/agent/protect/rule/sqli.rb +6 -10
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +6 -2
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +20 -0
- data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +1 -1
- data/lib/contrast/agent/protect/rule/xss.rb +8 -0
- data/lib/contrast/agent/protect/rule/xxe.rb +2 -2
- data/lib/contrast/agent/protect/rule.rb +0 -3
- data/lib/contrast/agent/reporting/attack_result/user_input.rb +0 -1
- data/lib/contrast/agent/reporting/details/details.rb +0 -1
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +12 -0
- data/lib/contrast/agent/reporting/report.rb +1 -0
- data/lib/contrast/agent/reporting/reporter.rb +12 -15
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -5
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +13 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +20 -5
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +5 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +10 -1
- data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +2 -1
- data/lib/contrast/agent/reporting/reporting_events/application_reporting_event.rb +10 -0
- data/lib/contrast/agent/reporting/reporting_events/application_settings.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +9 -5
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +8 -5
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +7 -7
- data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +137 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +12 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +100 -107
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +5 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +101 -67
- data/lib/contrast/agent/reporting/reporting_workers/application_server_worker.rb +46 -0
- data/lib/contrast/agent/reporting/reporting_workers/reporter_heartbeat.rb +51 -0
- data/lib/contrast/agent/reporting/reporting_workers/reporting_workers.rb +14 -0
- data/lib/contrast/agent/reporting/reporting_workers/server_settings_worker.rb +46 -0
- data/lib/contrast/agent/reporting/settings/assess.rb +14 -1
- data/lib/contrast/agent/reporting/settings/assess_rule.rb +18 -0
- data/lib/contrast/agent/reporting/settings/helpers.rb +4 -2
- data/lib/contrast/agent/reporting/settings/protect.rb +17 -12
- data/lib/contrast/agent/reporting/settings/protect_rule.rb +18 -0
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
- data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +1 -1
- data/lib/contrast/agent/reporting/settings/virtual_patch.rb +56 -0
- data/lib/contrast/agent/reporting/settings/virtual_patch_condition.rb +47 -0
- data/lib/contrast/agent/request_context_extend.rb +20 -0
- data/lib/contrast/agent/telemetry/base.rb +13 -15
- data/lib/contrast/agent/telemetry/events/exceptions/obfuscate.rb +108 -103
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +16 -10
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +12 -0
- data/lib/contrast/agent_lib/api/init.rb +1 -7
- data/lib/contrast/agent_lib/api/input_tracing.rb +2 -4
- data/lib/contrast/agent_lib/interface.rb +1 -16
- data/lib/contrast/agent_lib/interface_base.rb +52 -39
- data/lib/contrast/agent_lib/return_types/eval_result.rb +2 -2
- data/lib/contrast/components/assess.rb +26 -4
- data/lib/contrast/components/config.rb +1 -1
- data/lib/contrast/components/polling.rb +4 -1
- data/lib/contrast/components/settings.rb +46 -3
- data/lib/contrast/config/config.rb +2 -2
- data/lib/contrast/config/protect_rule_configuration.rb +1 -1
- data/lib/contrast/config/protect_rules_configuration.rb +1 -1
- data/lib/contrast/extension/assess/array.rb +3 -3
- data/lib/contrast/extension/assess/regexp.rb +2 -2
- data/lib/contrast/framework/rack/patch/session_cookie.rb +2 -1
- data/lib/contrast/logger/aliased_logging.rb +48 -15
- data/lib/contrast/utils/duck_utils.rb +18 -0
- data/lib/contrast/utils/heap_dump_util.rb +1 -1
- data/lib/contrast/utils/input_classification_base.rb +21 -4
- data/lib/contrast/utils/log_utils.rb +1 -1
- data/lib/contrast/utils/middleware_utils.rb +1 -1
- data/lib/contrast/utils/patching/policy/patch_utils.rb +2 -2
- data/lib/contrast/utils/routes_sent.rb +6 -2
- data/lib/contrast/utils/telemetry.rb +2 -2
- data/lib/contrast/utils/telemetry_client.rb +1 -1
- data/resources/protect/policy.json +8 -0
- data/ruby-agent.gemspec +6 -6
- metadata +40 -30
- data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +0 -96
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +0 -83
- data/lib/contrast/agent/reporting/details/http_method_tempering_details.rb +0 -27
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +0 -47
- data/lib/contrast/agent/reporting/server_settings_worker.rb +0 -44
- data/lib/contrast/agent_lib/api/method_tempering.rb +0 -29
@@ -30,7 +30,7 @@ module Contrast
|
|
30
30
|
DATE_TIME = '%Y-%m-%dT%H:%M:%S.%L%z'
|
31
31
|
|
32
32
|
class Interface # :nodoc: # rubocop:disable Metrics/ClassLength
|
33
|
-
SESSION_VARIABLES = 'Invalid configuration. '\
|
33
|
+
SESSION_VARIABLES = 'Invalid configuration. ' \
|
34
34
|
"Setting both application.session_id and application.session_metadata is not allowed.\n"
|
35
35
|
API_URL = "Invalid configuration. Missing a required connection value 'url' is not set."
|
36
36
|
API_KEY = "Invalid configuration. Missing a required connection value 'api_key' is not set."
|
@@ -12,6 +12,8 @@ module Contrast
|
|
12
12
|
|
13
13
|
# @return [Integer, nil]
|
14
14
|
attr_reader :server_settings_ms
|
15
|
+
# @return [Integer, nil]
|
16
|
+
attr_reader :app_settings_ms
|
15
17
|
# @return [String]
|
16
18
|
attr_reader :canon_name
|
17
19
|
# @return [Array]
|
@@ -20,7 +22,7 @@ module Contrast
|
|
20
22
|
attr_reader :batch_reporting_interval_ms
|
21
23
|
|
22
24
|
CANON_NAME = 'agent.polling'
|
23
|
-
CONFIG_VALUES = %w[server_settings_ms batch_reporting_interval_ms].cs__freeze
|
25
|
+
CONFIG_VALUES = %w[server_settings_ms app_settings_ms batch_reporting_interval_ms].cs__freeze
|
24
26
|
|
25
27
|
def initialize hsh = {}
|
26
28
|
@config_values = CONFIG_VALUES
|
@@ -29,6 +31,7 @@ module Contrast
|
|
29
31
|
|
30
32
|
@server_settings_ms = hsh[:server_settings_ms]
|
31
33
|
@batch_reporting_interval_ms = hsh[:batch_reporting_interval_ms]
|
34
|
+
@app_settings_ms = hsh[:app_settings_ms]
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
@@ -75,6 +75,11 @@ module Contrast
|
|
75
75
|
# two dates are the same.
|
76
76
|
# format: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
|
77
77
|
attr_reader :server_settings_last_httpdate
|
78
|
+
# @return [String] The last update but in string format used to build request header.
|
79
|
+
# This value should be sent be TS in the Last-Modified header to sync and save resources if the
|
80
|
+
# two dates are the same.
|
81
|
+
# format: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
|
82
|
+
attr_reader :app_settings_last_httpdate
|
78
83
|
|
79
84
|
def initialize
|
80
85
|
reset_state
|
@@ -85,13 +90,15 @@ module Contrast
|
|
85
90
|
end
|
86
91
|
|
87
92
|
# @param features_response [Contrast::Agent::Reporting::Response]
|
88
|
-
def update_from_server_features features_response
|
93
|
+
def update_from_server_features features_response # rubocop:disable Metrics/AbcSize
|
89
94
|
return unless (server_features = features_response&.server_features)
|
90
95
|
|
91
96
|
log_file = server_features.log_file
|
92
97
|
log_level = server_features.log_level
|
93
98
|
# Update logger:
|
94
99
|
Contrast::Logger::Log.instance.update(log_file, log_level) if log_file || log_level
|
100
|
+
# Update AgentLib Logger
|
101
|
+
update_agent_lib_log(log_level.to_s)
|
95
102
|
# Update CEFlogger:
|
96
103
|
unless server_features.security_logger.settings_blank?
|
97
104
|
cef_logger.build_logger(server_features.security_logger.log_level, server_features.security_logger.log_file)
|
@@ -110,6 +117,25 @@ module Contrast
|
|
110
117
|
# next request's header with the same time will save needless update of settings if there
|
111
118
|
# are no new server features updates after the said time.
|
112
119
|
@server_settings_last_httpdate = header_last_update
|
120
|
+
rescue StandardError => e
|
121
|
+
logger.warn('The following error occurred from server update: ', e: e)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Update AgentLib log level
|
125
|
+
def update_agent_lib_log new_log_level
|
126
|
+
agent_lib_log_level = Contrast::AgentLib::InterfaceBase::LOG_LEVEL[0] if new_log_level.empty?
|
127
|
+
agent_lib_log_level ||= Contrast::AgentLib::InterfaceBase::LOG_LEVEL.key(new_log_level.upcase)
|
128
|
+
|
129
|
+
# detect if the provided level is invalid and log if it is
|
130
|
+
# by default if we pass invalid log level - it will leave the last active
|
131
|
+
unless Contrast::AgentLib::InterfaceBase::LOG_LEVEL.value?(new_log_level.upcase)
|
132
|
+
cur_active = Contrast::AGENT_LIB.log_level
|
133
|
+
logger.debug('The provided level was invalid, so the logger stays to the last active: ',
|
134
|
+
active: cur_active,
|
135
|
+
provided_level: new_log_level)
|
136
|
+
end
|
137
|
+
|
138
|
+
Contrast::AGENT_LIB.change_log_options(true, agent_lib_log_level)
|
113
139
|
end
|
114
140
|
|
115
141
|
# Update Assess server features
|
@@ -135,23 +161,41 @@ module Contrast
|
|
135
161
|
|
136
162
|
@application_state.modes_by_id = app_settings.protect.protection_rules_to_settings_hash
|
137
163
|
update_exclusion_matchers(app_settings.exclusions)
|
164
|
+
app_settings.protect.virtual_patches = app_settings.protect.virtual_patches unless
|
165
|
+
settings_empty?(app_settings.protect.virtual_patches)
|
138
166
|
update_sensitive_data_policy(app_settings.sensitive_data_masking)
|
139
167
|
@assess_state.disabled_assess_rules = app_settings.assess.disabled_rules
|
140
168
|
new_session_id = app_settings.assess.session_id
|
141
169
|
@assess_state.session_id = new_session_id if new_session_id && !new_session_id.blank?
|
142
170
|
@last_app_update_ms = Contrast::Utils::Timer.now_ms
|
171
|
+
@app_settings_last_httpdate = header_last_update
|
143
172
|
end
|
144
173
|
|
145
174
|
# Wipe state to zero.
|
146
175
|
def reset_state
|
147
176
|
@protect_state = PROTECT_STATE_BASE.dup
|
148
|
-
|
177
|
+
update_assess_state
|
149
178
|
@application_state = APPLICATION_STATE_BASE.dup
|
150
179
|
@tainted_columns = {}
|
151
180
|
@sensitive_data_masking = SENSITIVE_DATA_MASKING_BASE.dup
|
152
181
|
@excluder = Contrast::Agent::Excluder.new
|
153
182
|
end
|
154
183
|
|
184
|
+
# We save the session_id, reset and set it again if available.
|
185
|
+
# This done so that reporting between updates won't trigger argument error
|
186
|
+
# for missing session_id given one is already set and used with the first application
|
187
|
+
# create response received from TS.
|
188
|
+
#
|
189
|
+
# @return [Struct]
|
190
|
+
def update_assess_state
|
191
|
+
current_session_id = @assess_state&.session_id
|
192
|
+
@assess_state = ASSESS_STATE_BASE.dup
|
193
|
+
# There is application settings update for the session id if new is received.
|
194
|
+
# Here we make sure not to delete the already set one.
|
195
|
+
@assess_state&.session_id = current_session_id unless current_session_id&.empty?
|
196
|
+
@assess_state
|
197
|
+
end
|
198
|
+
|
155
199
|
def build_protect_rules
|
156
200
|
@protect_state.rules = {}
|
157
201
|
|
@@ -160,7 +204,6 @@ module Contrast
|
|
160
204
|
cmdi = Contrast::Agent::Protect::Rule::CmdInjection.new
|
161
205
|
cmdi.sub_rules
|
162
206
|
Contrast::Agent::Protect::Rule::Deserialization.new
|
163
|
-
Contrast::Agent::Protect::Rule::HttpMethodTampering.new
|
164
207
|
Contrast::Agent::Protect::Rule::NoSqli.new
|
165
208
|
path = Contrast::Agent::Protect::Rule::PathTraversal.new
|
166
209
|
path.sub_rules
|
@@ -25,10 +25,10 @@ module Contrast
|
|
25
25
|
def determine_config_status response
|
26
26
|
return unless response
|
27
27
|
# If we encounter for some of the startup events failure - always return failure
|
28
|
-
return if
|
28
|
+
return if [MESSAGE_FAIL, CONN_STATUS_MSG_FAILURE].include?(@config_status)
|
29
29
|
|
30
30
|
response_code = response.code.to_s
|
31
|
-
@config_status = response_code.
|
31
|
+
@config_status = response_code.start_with?('2') ? MESSAGE_SUCCESSFUL : MESSAGE_FAIL
|
32
32
|
nil
|
33
33
|
end
|
34
34
|
|
@@ -50,7 +50,7 @@ module Contrast
|
|
50
50
|
Contrast::Config::ProtectRuleConfiguration.new(hsh[:'sql-injection-semantic-dangerous-functions'])
|
51
51
|
@unsafe_file_upload = Contrast::Config::ProtectRuleConfiguration.new(hsh[:'unsafe-file-upload'])
|
52
52
|
@untrusted_deserialization = Contrast::Config::ProtectRuleConfiguration.new(hsh[:'untrusted-deserialization'])
|
53
|
-
@xxe = Contrast::Config::ProtectRuleConfiguration.new(hsh[
|
53
|
+
@xxe = Contrast::Config::ProtectRuleConfiguration.new(hsh[:xxe])
|
54
54
|
end
|
55
55
|
|
56
56
|
def []= key, value
|
@@ -13,10 +13,10 @@ module Contrast
|
|
13
13
|
# This is our patch of the Array class required to handle propagation
|
14
14
|
# Disclaimer: there may be a better way, but we're in a 'get it work' state.
|
15
15
|
# Hopefully, we'll be in a 'get it right' state soon.
|
16
|
-
class ArrayPropagator
|
16
|
+
class ArrayPropagator
|
17
17
|
extend Contrast::Components::Scope::InstanceMethods
|
18
18
|
|
19
|
-
ARRAY_JOIN_HASH
|
19
|
+
ARRAY_JOIN_HASH ||= { # rubocop:disable Lint/OrAssignmentToConstant
|
20
20
|
'class_name' => 'Array',
|
21
21
|
'instance_method' => true,
|
22
22
|
'method_visibility' => 'public',
|
@@ -27,7 +27,7 @@ module Contrast
|
|
27
27
|
'patch_class' => 'NOOP',
|
28
28
|
'patch_method' => 'cs__track_join'
|
29
29
|
}.cs__freeze
|
30
|
-
ARRAY_JOIN_NODE
|
30
|
+
ARRAY_JOIN_NODE ||= Contrast::Agent::Assess::Policy::PropagationNode.new(ARRAY_JOIN_HASH) # rubocop:disable Lint/OrAssignmentToConstant
|
31
31
|
|
32
32
|
class << self
|
33
33
|
# When you call join, they use an internal thing, so there's no good way to get at the thing being returned.
|
@@ -17,7 +17,7 @@ module Contrast
|
|
17
17
|
extend Contrast::Components::Logger::InstanceMethods
|
18
18
|
extend Contrast::Components::Scope::InstanceMethods
|
19
19
|
|
20
|
-
REGEXP_EQUAL_SQUIGGLE_HASH
|
20
|
+
REGEXP_EQUAL_SQUIGGLE_HASH ||= { # rubocop:disable Lint/OrAssignmentToConstant
|
21
21
|
'id' => 'regexp_100',
|
22
22
|
'class_name' => 'Regexp',
|
23
23
|
'instance_method' => true,
|
@@ -29,7 +29,7 @@ module Contrast
|
|
29
29
|
'patch_class' => 'Contrast::Extension::Assess::RegexpPropagator',
|
30
30
|
'patch_method' => 'track_equal_squiggle'
|
31
31
|
}.cs__freeze
|
32
|
-
REGEXP_EQUAL_SQUIGGLE_NODE
|
32
|
+
REGEXP_EQUAL_SQUIGGLE_NODE ||= Contrast::Agent::Assess::Policy::PropagationNode.new(REGEXP_EQUAL_SQUIGGLE_HASH) # rubocop:disable Lint/OrAssignmentToConstant
|
33
33
|
private_constant :REGEXP_EQUAL_SQUIGGLE_HASH
|
34
34
|
private_constant :REGEXP_EQUAL_SQUIGGLE_NODE
|
35
35
|
|
@@ -48,7 +48,8 @@ module Contrast
|
|
48
48
|
|
49
49
|
def vulnerable_setting?(setting_key,
|
50
50
|
safe_settings_value,
|
51
|
-
options,
|
51
|
+
options,
|
52
|
+
safe_default: true,
|
52
53
|
comparison_type: nil)
|
53
54
|
# In most cases, Rack is pretty nice and the default value is safe
|
54
55
|
return !safe_default unless options&.key?(setting_key)
|
@@ -41,31 +41,39 @@ module Contrast
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
+
# @param type [ALIASED_FATAL, ALIASED_ERROR, ALIASED_WARN] the type of error, used to indicate the function used
|
45
|
+
# for logging
|
46
|
+
# @param message [String] the exception message
|
47
|
+
# @param exception [Exception] The exception or error
|
48
|
+
# @param data [Object] Any structured data
|
44
49
|
def build_exception type, message = nil, exception = nil, data = nil
|
45
|
-
stack_trace =
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
stack_trace = wrapped_caller_locations
|
51
|
+
caller_idx = stack_trace&.find_index { |stack| stack.to_s.include?(type) } || 0
|
52
|
+
# The caller_stack is the method in which the error occurred, so has to be above this method
|
53
|
+
caller_idx += 1
|
54
|
+
caller_frame = stack_trace[caller_idx]
|
55
|
+
stack_frame_type = caller_frame.path.delete_prefix(Dir.pwd)
|
56
|
+
stack_frame_function = caller_frame.label
|
49
57
|
key = "#{ stack_frame_type }|#{ stack_frame_function }|#{ message }"
|
50
|
-
if TELEMETRY_EXCEPTIONS[key]
|
51
|
-
TELEMETRY_EXCEPTIONS.increment(key)
|
58
|
+
if Contrast::TELEMETRY_EXCEPTIONS[key]
|
59
|
+
Contrast::TELEMETRY_EXCEPTIONS.increment(key)
|
52
60
|
return
|
53
61
|
end
|
54
62
|
|
55
|
-
return if TELEMETRY_EXCEPTIONS.exception_limit?
|
56
|
-
|
57
|
-
message_exception_type = Contrast::Agent::Telemetry::TelemetryException::Obfuscate.obfuscate_exception_type(
|
58
|
-
exception ? exception.cs__class.to_s : stack_frame_type.split('/').last)
|
63
|
+
return if Contrast::TELEMETRY_EXCEPTIONS.exception_limit?
|
59
64
|
|
65
|
+
message_exception_type = exception ? exception.cs__class.to_s : stack_frame_type.split('/').last
|
60
66
|
event_message = create_message(stack_frame_function,
|
61
67
|
stack_frame_type, message_exception_type,
|
62
68
|
data, exception,
|
63
69
|
message)
|
70
|
+
build_stack(event_message, stack_trace, caller_idx)
|
64
71
|
TELEMETRY_EXCEPTIONS[key] = event_message
|
65
72
|
rescue StandardError => e
|
66
|
-
debug('Unable to report exception
|
73
|
+
debug('[Telemetry] Unable to report exception', e)
|
67
74
|
end
|
68
75
|
|
76
|
+
# @return [Contrast::Agent::Telemetry::TelemetryException::Event]
|
69
77
|
def create_message stack_frame_function, stack_frame_type, message_exception_type, data, exception, message
|
70
78
|
message_for_exception = if exception
|
71
79
|
exception.cs__respond_to?(:message) ? exception.message : exception
|
@@ -89,12 +97,37 @@ module Contrast
|
|
89
97
|
message = Contrast::Agent::Telemetry::TelemetryException::Message.build(tags, [message_exception])
|
90
98
|
Contrast::Agent::Telemetry::TelemetryException::Event.new(message)
|
91
99
|
rescue ArgumentError => e
|
92
|
-
debug('TelemetryException failed from aliased logging with: ', e)
|
100
|
+
debug('[Telemetry] TelemetryException failed from aliased logging with: ', e)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Convert the given caller_stack from the event into the exception StackFrame format for reporting, appending it
|
104
|
+
# to the Exception wrapped in the Event.
|
105
|
+
#
|
106
|
+
# @param event_message [Contrast::Agent::Telemetry::TelemetryException::Event]
|
107
|
+
# @param caller_stack [(Array<Thread::Backtrace::Location>, nil]
|
108
|
+
# @param caller_idx [Integer] the starting location for the exception, which allows filtering out the logger code
|
109
|
+
# from the stack
|
110
|
+
def build_stack event_message, caller_stack, caller_idx = 0
|
111
|
+
return unless caller_stack
|
112
|
+
|
113
|
+
event_exception = event_message.exceptions[0]
|
114
|
+
event_exception_message = event_exception.exceptions[0]
|
115
|
+
|
116
|
+
caller_stack.each_with_index do |caller, idx|
|
117
|
+
next unless idx > caller_idx
|
118
|
+
|
119
|
+
stack_frame =
|
120
|
+
Contrast::Agent::Telemetry::TelemetryException::StackFrame.build(caller.label,
|
121
|
+
caller.path.delete_prefix(Dir.pwd))
|
122
|
+
event_exception_message.push(stack_frame)
|
123
|
+
end
|
93
124
|
end
|
94
125
|
|
95
|
-
|
96
|
-
|
97
|
-
|
126
|
+
# This is purely a wrapper around caller_locations used for testing
|
127
|
+
#
|
128
|
+
# @return [Array<Thread::Backtrace::Location>, nil]
|
129
|
+
def wrapped_caller_locations
|
130
|
+
caller_locations
|
98
131
|
end
|
99
132
|
end
|
100
133
|
end
|
@@ -63,6 +63,24 @@ module Contrast
|
|
63
63
|
# otherwise, don't risk it
|
64
64
|
false
|
65
65
|
end
|
66
|
+
|
67
|
+
# Every duck quacks to nil, we need to check if it is empty?
|
68
|
+
# Utils method to check for blank ( nil or empty object ).
|
69
|
+
#
|
70
|
+
# @param [Object] object to test.
|
71
|
+
# @return [Boolean]
|
72
|
+
def empty_duck? object
|
73
|
+
# If not quacking to empty it is True/False class, Integer, Regexp or Time instance.
|
74
|
+
return false if object.instance_of?(TrueClass) || object.instance_of?(FalseClass)
|
75
|
+
|
76
|
+
if object.cs__respond_to?(:empty?)
|
77
|
+
return true if object.empty?
|
78
|
+
elsif object.nil?
|
79
|
+
return true
|
80
|
+
end
|
81
|
+
|
82
|
+
false
|
83
|
+
end
|
66
84
|
end
|
67
85
|
end
|
68
86
|
end
|
@@ -43,7 +43,7 @@ module Contrast
|
|
43
43
|
next unless v
|
44
44
|
|
45
45
|
result = create_new_input_result(input_analysis.request, rule.rule_name, input_type, v)
|
46
|
-
input_analysis
|
46
|
+
append_result(input_analysis, result)
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -51,6 +51,7 @@ module Contrast
|
|
51
51
|
rescue StandardError => e
|
52
52
|
logger.debug("An Error was recorded in the input classification of the #{ rule_id }")
|
53
53
|
logger.debug(e)
|
54
|
+
nil
|
54
55
|
end
|
55
56
|
|
56
57
|
# Creates new isntance of InputAnalysisResult with basic info.
|
@@ -103,16 +104,24 @@ module Contrast
|
|
103
104
|
# @return [Integer<Contrast::AgentLib::Interface::INPUT_SET>]
|
104
105
|
def convert_input_type input_type
|
105
106
|
case input_type
|
106
|
-
when
|
107
|
+
when URI, URL_PARAMETER
|
108
|
+
Contrast::AGENT_LIB.input_set[:URI_PATH]
|
109
|
+
when BODY, DWR_VALUE, SOCKET, UNDEFINED_TYPE, UNKNOWN, REQUEST, QUERYSTRING
|
107
110
|
Contrast::AGENT_LIB.input_set[:PARAMETER_VALUE]
|
108
111
|
when HEADER
|
109
112
|
Contrast::AGENT_LIB.input_set[:HEADER_VALUE]
|
110
113
|
when MULTIPART_VALUE, MULTIPART_FIELD_NAME
|
111
114
|
Contrast::AGENT_LIB.input_set[:MULTIPART_NAME]
|
115
|
+
when JSON_ARRAYED_VALUE
|
116
|
+
Contrast::AGENT_LIB.input_set[:JSON_KEY]
|
117
|
+
when PARAMETER_NAME
|
118
|
+
Contrast::AGENT_LIB.input_set[:PARAMETER_KEY]
|
112
119
|
else
|
113
120
|
Contrast::AGENT_LIB.input_set[input_type]
|
114
121
|
end
|
115
|
-
rescue StandardError
|
122
|
+
rescue StandardError => e
|
123
|
+
logger.debug('Protect Input classification could not determine input type, falling back to default',
|
124
|
+
error: e)
|
116
125
|
Contrast::AGENT_LIB.input_set[:PARAMETER_VALUE]
|
117
126
|
end
|
118
127
|
|
@@ -133,7 +142,7 @@ module Contrast
|
|
133
142
|
input_eval = Contrast::AGENT_LIB.eval_input(value,
|
134
143
|
convert_input_type(input_type),
|
135
144
|
Contrast::AGENT_LIB.rule_set[rule_id],
|
136
|
-
Contrast::AGENT_LIB.eval_option[:
|
145
|
+
Contrast::AGENT_LIB.eval_option[:PREFER_WORTH_WATCHING])
|
137
146
|
|
138
147
|
ia_result = new_ia_result(rule_id, input_type, request.path, value)
|
139
148
|
score = input_eval&.score || 0
|
@@ -148,6 +157,14 @@ module Contrast
|
|
148
157
|
add_needed_key(request, ia_result, input_type, value) if KEYS_NEEDED.include?(input_type)
|
149
158
|
ia_result
|
150
159
|
end
|
160
|
+
|
161
|
+
def append_result ia_analysis, result
|
162
|
+
unless result.nil?
|
163
|
+
ia_analysis.results << result
|
164
|
+
ia_analysis.triggered_rules << result.rule_id unless ia_analysis.triggered_rules.include?(result.rule_id)
|
165
|
+
end
|
166
|
+
ia_analysis
|
167
|
+
end
|
151
168
|
end
|
152
169
|
end
|
153
170
|
end
|
@@ -12,7 +12,7 @@ module Contrast
|
|
12
12
|
LANGUAGE_DEPRECATION_VERSION = '2.7'
|
13
13
|
LANGUAGE_DEPRECATION_YEAR = '2023'
|
14
14
|
LANGUAGE_DEPRECATION_WARNING =
|
15
|
-
"[Contrast Security] [DEPRECATION] Support for Ruby #{ LANGUAGE_DEPRECATION_VERSION } will be removed in "\
|
15
|
+
"[Contrast Security] [DEPRECATION] Support for Ruby #{ LANGUAGE_DEPRECATION_VERSION } will be removed in " \
|
16
16
|
"April #{ LANGUAGE_DEPRECATION_YEAR }. Please contact Customer Support prior if you require continued support."
|
17
17
|
|
18
18
|
def setup_agent
|
@@ -23,8 +23,8 @@ module Contrast
|
|
23
23
|
# Given a method, return a symbol in the format
|
24
24
|
# <method_start>_unbound_<method_name>
|
25
25
|
def build_unbound_method_name patcher_method
|
26
|
-
"#{ Contrast::Utils::ObjectShare::CONTRAST_PATCHED_METHOD_START }unbound"\
|
27
|
-
"#{ Contrast::Utils::ObjectShare::UNDERSCORE }"\
|
26
|
+
"#{ Contrast::Utils::ObjectShare::CONTRAST_PATCHED_METHOD_START }unbound" \
|
27
|
+
"#{ Contrast::Utils::ObjectShare::UNDERSCORE }" \
|
28
28
|
"#{ patcher_method }".to_sym
|
29
29
|
end
|
30
30
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
# require 'contrast/components/logger'
|
5
5
|
# require 'contrast/agent/telemetry/events/exceptions/telemetry_exception_event'
|
6
|
+
require 'contrast/utils/duck_utils'
|
6
7
|
|
7
8
|
module Contrast
|
8
9
|
module Utils
|
@@ -12,7 +13,7 @@ module Contrast
|
|
12
13
|
class RoutesSent
|
13
14
|
# include Contrast::Components::Logger::InstanceMethods
|
14
15
|
ROUTES_LIMIT = 500
|
15
|
-
TIME_LIMIT_IN_SECONDS =
|
16
|
+
TIME_LIMIT_IN_SECONDS = 60
|
16
17
|
|
17
18
|
attr_accessor :cache
|
18
19
|
|
@@ -22,9 +23,12 @@ module Contrast
|
|
22
23
|
|
23
24
|
# Determine whether the provided route can be sent to TeamServer.
|
24
25
|
#
|
25
|
-
# @param [Contrast::Agent::Reporting::ObservedRoute] the route
|
26
|
+
# @param route [Contrast::Agent::Reporting::ObservedRoute] the route
|
26
27
|
# @return [boolean]
|
27
28
|
def sendable? route
|
29
|
+
return false if Contrast::Utils::DuckUtils.empty_duck?(route.signature)
|
30
|
+
return false if Contrast::Utils::DuckUtils.empty_duck?(route.url)
|
31
|
+
|
28
32
|
route_hash = route.hash_id
|
29
33
|
|
30
34
|
# If hash doesn't exist in @cache...
|
@@ -9,7 +9,7 @@ module Contrast
|
|
9
9
|
module Utils
|
10
10
|
# Tools for supporting the Telemetry feature
|
11
11
|
module Telemetry
|
12
|
-
DIR = '/
|
12
|
+
DIR = '/etc/contrast/ruby-agent/'.cs__freeze
|
13
13
|
FILE = '.telemetry'.cs__freeze
|
14
14
|
CURRENT_DIR = Dir.pwd.cs__freeze
|
15
15
|
CONFIG_DIR = CURRENT_DIR + '/config/contrast/'.cs__freeze
|
@@ -81,7 +81,7 @@ module Contrast
|
|
81
81
|
#
|
82
82
|
# @return[Boolean] true if success, false if fails
|
83
83
|
def touch_marker dir, file
|
84
|
-
FileUtils.mkdir_p(dir)
|
84
|
+
FileUtils.mkdir_p(dir)
|
85
85
|
FileUtils.touch(dir + file)
|
86
86
|
File.file?(dir + file)
|
87
87
|
rescue StandardError => _e
|
@@ -99,7 +99,7 @@ module Contrast
|
|
99
99
|
def get_event_json event
|
100
100
|
Array(event.to_controlled_hash).to_json
|
101
101
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
102
|
-
logger.error('Unable to convert TelemetryEvent to JSON string', e, hsh)
|
102
|
+
logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
|
103
103
|
raise(e)
|
104
104
|
end
|
105
105
|
end
|
@@ -128,6 +128,14 @@
|
|
128
128
|
"database": "MongoDB"
|
129
129
|
}
|
130
130
|
},{
|
131
|
+
"class_name": "Mongo::Collection",
|
132
|
+
"instance_method": true,
|
133
|
+
"method_visibility": "public",
|
134
|
+
"method_name": "find",
|
135
|
+
"properties": {
|
136
|
+
"database": "MongoDB"
|
137
|
+
}
|
138
|
+
},{
|
131
139
|
"class_name": "Moped::Node",
|
132
140
|
"method_name": "read",
|
133
141
|
"instance_method": true,
|
data/ruby-agent.gemspec
CHANGED
@@ -75,7 +75,7 @@ def self.add_specs spec
|
|
75
75
|
spec.add_development_dependency 'factory_bot'
|
76
76
|
spec.add_development_dependency 'fake_ftp'
|
77
77
|
spec.add_development_dependency 'openssl'
|
78
|
-
spec.add_development_dependency 'parallel_tests'
|
78
|
+
spec.add_development_dependency 'parallel_tests', '~> 3.0'
|
79
79
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
80
80
|
spec.add_development_dependency 'rspec-benchmark'
|
81
81
|
spec.add_development_dependency 'rspec_junit_formatter', '0.3.0'
|
@@ -92,11 +92,11 @@ end
|
|
92
92
|
|
93
93
|
# Dependencies used to run all of our Rubocop during the linting phase.
|
94
94
|
def self.add_rubocop spec
|
95
|
-
spec.add_development_dependency 'rubocop', '1.
|
96
|
-
spec.add_development_dependency 'rubocop-performance', '1.
|
97
|
-
spec.add_development_dependency 'rubocop-rails', '2.
|
95
|
+
spec.add_development_dependency 'rubocop', '1.37.1'
|
96
|
+
spec.add_development_dependency 'rubocop-performance', '1.15.0'
|
97
|
+
spec.add_development_dependency 'rubocop-rails', '2.17.2'
|
98
98
|
spec.add_development_dependency 'rubocop-rake', '0.6.0'
|
99
|
-
spec.add_development_dependency 'rubocop-rspec', '2.
|
99
|
+
spec.add_development_dependency 'rubocop-rspec', '2.14.2'
|
100
100
|
end
|
101
101
|
|
102
102
|
# Dependencies not mocked out during RSpec that we test real code of, beyond just frameworks.
|
@@ -121,7 +121,7 @@ end
|
|
121
121
|
def self.add_dependencies spec
|
122
122
|
spec.add_dependency 'ougai', '>= 1.8', '< 3.0.0'
|
123
123
|
spec.add_dependency 'rack', '~> 2.0'
|
124
|
-
spec.add_dependency 'contrast-agent-lib',
|
124
|
+
spec.add_dependency 'contrast-agent-lib', '~> 0.1.0', '>= 0.1.3'
|
125
125
|
spec.add_dependency 'ffi', '~> 1.0'
|
126
126
|
end
|
127
127
|
|