contrast-agent 6.9.0 → 6.10.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/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -1
- data/lib/contrast/agent/middleware.rb +4 -2
- 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 +3 -0
- 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 +6 -0
- 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_base_rule.rb +8 -0
- 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/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 +11 -10
- 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_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 +97 -63
- 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 +11 -10
- 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/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/logger/aliased_logging.rb +48 -15
- data/lib/contrast/utils/input_classification_base.rb +21 -4
- data/lib/contrast/utils/routes_sent.rb +2 -2
- data/lib/contrast/utils/telemetry.rb +1 -1
- data/lib/contrast/utils/telemetry_client.rb +1 -1
- data/resources/protect/policy.json +8 -0
- data/ruby-agent.gemspec +1 -1
- metadata +28 -18
- 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
@@ -1,46 +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-agent-lib'
|
5
|
+
require 'contrast/agent/reporting/settings/helpers'
|
6
|
+
|
4
7
|
module Contrast
|
5
8
|
module AgentLib
|
6
9
|
# Base class to set basic rule sets and input list.
|
7
10
|
class InterfaceBase
|
8
|
-
# This could be changed to regular Hash.
|
9
|
-
# This format is used to supports this kind of log_level assignment:
|
10
|
-
# via Contrast::Api::Settings::LogLevel::WARN => 3
|
11
|
-
# ( note -1 is not supported in the protobuf LogLevel)
|
12
11
|
LOG_LEVEL = { -1 => 'OFF', 0 => 'TRACE', 1 => 'DEBUG', 2 => 'INFO', 3 => 'WARN', 4 => 'ERROR' }.cs__freeze
|
13
12
|
LOG_DIR = File.join(Dir.pwd).cs__freeze
|
14
|
-
#
|
15
|
-
# Corresponding to Rust's ulong enum
|
16
|
-
RULE_SET = {
|
17
|
-
'unsafe-file-upload' => 1 << 0,
|
18
|
-
'path-traversal' => 1 << 1,
|
19
|
-
'reflected-xss' => 1 << 2,
|
20
|
-
'sql-injection' => 1 << 3,
|
21
|
-
'cmd-injection' => 1 << 4,
|
22
|
-
'nosql-injection' => 1 << 5,
|
23
|
-
'bot-blocker' => 1 << 6,
|
24
|
-
'ssjs-injection' => 1 << 7,
|
25
|
-
'method-tampering' => 1 << 8
|
26
|
-
}.cs__freeze
|
27
|
-
# Named same as Contrast::Agent::Reporting::InputTypes
|
28
|
-
INPUT_SET = {
|
29
|
-
COOKIE_NAME: 1,
|
30
|
-
COOKIE_VALUE: 2,
|
31
|
-
HEADER_NAME: 3,
|
32
|
-
HEADER_VALUE: 4,
|
33
|
-
JSON_NAME: 5,
|
34
|
-
JSON_VALUE: 6,
|
35
|
-
METHOD: 7,
|
36
|
-
PARAMETER_NAME: 8,
|
37
|
-
PARAMETER_VALUE: 9,
|
38
|
-
URI: 10,
|
39
|
-
URL_PARAMETER: 11,
|
40
|
-
MULTIPART_NAME: 12,
|
41
|
-
XML_VALUE: 13
|
42
|
-
}.cs__freeze
|
43
|
-
EVAL_OPTIONS = { NONE: 0, WORTHWATCHING: 1 }.cs__freeze
|
13
|
+
# Parameters passed to AgentLib. WorthWatching is used for those rules only that support it.
|
44
14
|
|
45
15
|
# Initializes the Agent lib.
|
46
16
|
#
|
@@ -54,18 +24,33 @@ module Contrast
|
|
54
24
|
# Override
|
55
25
|
end
|
56
26
|
|
57
|
-
# Return list of available rules
|
27
|
+
# Return list of available rules. On first call the constants are extracted from the gem and set
|
28
|
+
# to resemble the protect rules ids used across the agent, e.g. CMD_INJECTIONS => cmd-injection.
|
29
|
+
#
|
30
|
+
# *** Note that the NoSQLI rules is used within the agent, but in future need arise to be used with the
|
31
|
+
# AgentLib, the rule_id for that rule atm is 'nosql-injection-mongo' ***
|
58
32
|
#
|
33
|
+
# All the rules types extracted from the AgentLib gem will have value corresponding to an ulong enum in
|
34
|
+
# Rust land.
|
59
35
|
# @return [Hash]
|
36
|
+
# @raise [NameError] If Gem is loaded but not constants are generated there could be not defined
|
37
|
+
# module name, or if module was renamed.
|
60
38
|
def rule_set
|
61
|
-
|
39
|
+
@_rule_set ||= extract_constants(ContrastAgentLib::RuleType, to_rule_id: true)
|
62
40
|
end
|
63
41
|
|
64
|
-
# Returns list of available input types
|
42
|
+
# Returns list of available input types
|
65
43
|
#
|
66
44
|
# @return [Hash]
|
67
45
|
def input_set
|
68
|
-
|
46
|
+
@_input_set ||= extract_constants(ContrastAgentLib::InputType)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the AgentLib supported Database types.
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def db_set
|
53
|
+
@_db_set ||= extract_constants(ContrastAgentLib::DbType)
|
69
54
|
end
|
70
55
|
|
71
56
|
# Return list of input evaluation options:
|
@@ -73,7 +58,12 @@ module Contrast
|
|
73
58
|
#
|
74
59
|
# @return [Hash]
|
75
60
|
def eval_option
|
76
|
-
|
61
|
+
@_eval_option ||= begin
|
62
|
+
consts = extract_constants(ContrastAgentLib::EvalOptions).dup
|
63
|
+
# Adding the option to call the AgentLib without WorthWatching:
|
64
|
+
consts[:NONE] = 0
|
65
|
+
consts.cs__freeze
|
66
|
+
end
|
77
67
|
end
|
78
68
|
|
79
69
|
private
|
@@ -113,6 +103,29 @@ module Contrast
|
|
113
103
|
FileUtils.mkdir_p(LOG_DIR)
|
114
104
|
LOG_DIR
|
115
105
|
end
|
106
|
+
|
107
|
+
# This method extracts constats from the AgentLib gem, from specific module.
|
108
|
+
# If the to_rule_id flag is set the constant names would be transformed from
|
109
|
+
# Symbols to string protect rule ids: e.g. CMD_INJECTIONS => cmd-injection.
|
110
|
+
#
|
111
|
+
# @param module_name [Module] e.g. ContrastAgentLib::RuleType
|
112
|
+
# @param to_rule_id [Boolean] Flag to convert the keys of the hash to protect rule_id style
|
113
|
+
# @return [Hash] Frozen hash with the extracted constants
|
114
|
+
def extract_constants module_name, to_rule_id: false
|
115
|
+
hash = {}
|
116
|
+
return hash unless module_name.cs__is_a?(Module)
|
117
|
+
|
118
|
+
module_name.cs__constants.each do |constant|
|
119
|
+
key = to_rule_id ? Contrast::Agent::Reporting::Settings::Helpers.to_rule_id(constant) : constant
|
120
|
+
hash[key] = module_name.cs__const_get(constant)
|
121
|
+
end
|
122
|
+
hash.cs__freeze
|
123
|
+
rescue NameError => e
|
124
|
+
# This should be log as error since if there is no rule set there will be no ia happening,
|
125
|
+
# and something has gone wrong with enabling the gem for the Agent.
|
126
|
+
logger.error("[AgentLib] Could not extract #{ module_name } constants", error: e)
|
127
|
+
{}.cs__freeze
|
128
|
+
end
|
116
129
|
end
|
117
130
|
end
|
118
131
|
end
|
@@ -26,8 +26,8 @@ module Contrast
|
|
26
26
|
def initialize hsh
|
27
27
|
return unless hsh&.cs__is_a?(Hash)
|
28
28
|
|
29
|
-
@rule_id = Contrast::
|
30
|
-
@input_type = Contrast::
|
29
|
+
@rule_id = Contrast::AGENT_LIB.rule_set.key(hsh[:rule_id])
|
30
|
+
@input_type = Contrast::AGENT_LIB.input_set.key(hsh[:input_type])
|
31
31
|
@score = hsh[:score]
|
32
32
|
end
|
33
33
|
|
@@ -23,6 +23,16 @@ module Contrast
|
|
23
23
|
attr_reader :canon_name
|
24
24
|
# @return [Array]
|
25
25
|
attr_reader :config_values
|
26
|
+
# @return [Boolean]
|
27
|
+
attr_writer :enable_original_object
|
28
|
+
# @return [Integer]
|
29
|
+
attr_writer :max_context_source_events
|
30
|
+
# @return [Integer]
|
31
|
+
attr_writer :max_propagation_events
|
32
|
+
# @return [Integer]
|
33
|
+
attr_writer :max_rule_reported
|
34
|
+
# @return [Integer]
|
35
|
+
attr_writer :time_limit_threshold
|
26
36
|
|
27
37
|
DEFAULT_STACKTRACES = 'ALL'
|
28
38
|
DEFAULT_MAX_SOURCE_EVENTS = 50_000
|
@@ -55,10 +65,7 @@ module Contrast
|
|
55
65
|
@sampling = Contrast::Components::Sampling::Interface.new(hsh[:sampling])
|
56
66
|
@rules = Contrast::Components::AssessRules::Interface.new(hsh[:rules])
|
57
67
|
@stacktraces = hsh[:stacktraces]
|
58
|
-
|
59
|
-
@max_propagation_events = hsh[:max_propagation_events]
|
60
|
-
@max_rule_reported = hsh[:max_rule_reported]
|
61
|
-
@time_limit_threshold = hsh[:time_limit_threshold]
|
68
|
+
assign_limits(hsh)
|
62
69
|
end
|
63
70
|
|
64
71
|
# @return [Boolean, true]
|
@@ -224,6 +231,21 @@ module Contrast
|
|
224
231
|
@_forcibly_enabled = true?(::Contrast::CONFIG.assess.enable) if @_forcibly_enabled.nil?
|
225
232
|
@_forcibly_enabled
|
226
233
|
end
|
234
|
+
|
235
|
+
# Sets Event limits from configuration and converts string numbers to integers.
|
236
|
+
def assign_limits hsh
|
237
|
+
return unless hsh
|
238
|
+
|
239
|
+
source_limit = hsh[:max_context_source_events]&.to_i
|
240
|
+
propagation_limit = hsh[:max_propagation_events]&.to_i
|
241
|
+
max_rule_reporter = hsh[:max_rule_reported]&.to_i
|
242
|
+
time_limit = hsh[:time_limit_threshold]&.to_i
|
243
|
+
|
244
|
+
@max_context_source_events = source_limit if source_limit
|
245
|
+
@max_propagation_events = propagation_limit if propagation_limit
|
246
|
+
@max_rule_reported = max_rule_reporter if max_rule_reporter
|
247
|
+
@time_limit_threshold = time_limit if time_limit
|
248
|
+
end
|
227
249
|
end
|
228
250
|
end
|
229
251
|
end
|
@@ -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
|
|
@@ -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
|
@@ -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
|
class RoutesSent
|
13
13
|
# include Contrast::Components::Logger::InstanceMethods
|
14
14
|
ROUTES_LIMIT = 500
|
15
|
-
TIME_LIMIT_IN_SECONDS =
|
15
|
+
TIME_LIMIT_IN_SECONDS = 60
|
16
16
|
|
17
17
|
attr_accessor :cache
|
18
18
|
|
@@ -22,7 +22,7 @@ module Contrast
|
|
22
22
|
|
23
23
|
# Determine whether the provided route can be sent to TeamServer.
|
24
24
|
#
|
25
|
-
# @param [Contrast::Agent::Reporting::ObservedRoute] the route
|
25
|
+
# @param route [Contrast::Agent::Reporting::ObservedRoute] the route
|
26
26
|
# @return [boolean]
|
27
27
|
def sendable? route
|
28
28
|
route_hash = route.hash_id
|
@@ -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
|
@@ -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
@@ -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
|
|