contrast-agent 7.3.2 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/contrast/agent/middleware/middleware.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +9 -11
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +55 -20
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -4
- data/lib/contrast/agent/protect/rule/base.rb +61 -26
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +12 -4
- data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +19 -15
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +2 -4
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +2 -1
- data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +4 -4
- data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
- data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli.rb +5 -2
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +20 -8
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
- data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
- data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
- data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
- data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
- data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
- data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
- data/lib/contrast/agent/protect/state.rb +110 -0
- data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -4
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -8
- data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -5
- data/lib/contrast/agent/reporting/settings/protect.rb +3 -3
- data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
- data/lib/contrast/agent/request/request_context_extend.rb +0 -2
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/components/agent.rb +3 -5
- data/lib/contrast/components/api.rb +3 -3
- data/lib/contrast/components/assess.rb +4 -0
- data/lib/contrast/components/assess_rules.rb +1 -2
- data/lib/contrast/components/base.rb +1 -2
- data/lib/contrast/components/config/sources.rb +23 -0
- data/lib/contrast/components/logger.rb +19 -0
- data/lib/contrast/components/protect.rb +55 -14
- data/lib/contrast/components/sampling.rb +5 -12
- data/lib/contrast/components/security_logger.rb +17 -0
- data/lib/contrast/components/settings.rb +110 -76
- data/lib/contrast/config/certification_configuration.rb +1 -1
- data/lib/contrast/config/configuration_files.rb +0 -2
- data/lib/contrast/config/diagnostics/config.rb +3 -3
- data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
- data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
- data/lib/contrast/config/diagnostics/monitor.rb +1 -1
- data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
- data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
- data/lib/contrast/config/diagnostics/tools.rb +23 -84
- data/lib/contrast/config/request_audit_configuration.rb +1 -1
- data/lib/contrast/config/server_configuration.rb +3 -15
- data/lib/contrast/configuration.rb +5 -2
- data/lib/contrast/framework/manager.rb +4 -3
- data/lib/contrast/framework/manager_extend.rb +3 -1
- data/lib/contrast/framework/rack/support.rb +11 -2
- data/lib/contrast/framework/rails/support.rb +2 -2
- data/lib/contrast/logger/cef_log.rb +30 -4
- data/lib/contrast/utils/io_util.rb +3 -0
- data/lib/contrast/utils/log_utils.rb +22 -11
- data/lib/contrast/utils/request_utils.rb +1 -1
- data/lib/contrast/utils/timer.rb +1 -1
- metadata +4 -2
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'contrast/utils/object_share'
|
5
5
|
require 'contrast/config/diagnostics/effective_config_value'
|
6
|
+
require 'contrast/config/diagnostics/singleton_tools'
|
6
7
|
require 'contrast/utils/duck_utils'
|
7
8
|
|
8
9
|
module Contrast
|
@@ -11,79 +12,11 @@ module Contrast
|
|
11
12
|
# Diagnostics tools to be included in config components.
|
12
13
|
module Tools
|
13
14
|
CHECK = 'd'
|
14
|
-
CONTRAST_MARK = 'CONTRAST_'
|
15
|
-
class << self
|
16
|
-
# Creates new config instances for each read config entry from the flat generated configs.
|
17
|
-
#
|
18
|
-
# @param flats [Array] of flatten configs produced by #flatten_settings
|
19
|
-
# @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
|
20
|
-
# @param cli [Boolean] flag to check if the value comes from cli.
|
21
|
-
# @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
|
22
|
-
def to_config_values flats, source: false, cli: false
|
23
|
-
config_value_klass = if source
|
24
|
-
Contrast::Config::Diagnostics::SourceConfigValue
|
25
|
-
else
|
26
|
-
Contrast::Config::Diagnostics::EffectiveConfigValue
|
27
|
-
end
|
28
|
-
settings = []
|
29
|
-
flats.each do |entry|
|
30
|
-
entry.each do |key, value|
|
31
|
-
efc_value = config_value_klass.new.tap do |config_value|
|
32
|
-
config_value.canonical_name = Contrast::Utils::ObjectShare::CONTRAST_DOT + key unless cli
|
33
|
-
if cli && key.to_s.include?(CONTRAST_MARK)
|
34
|
-
config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
|
35
|
-
Contrast::Utils::ObjectShare::PERIOD).downcase
|
36
|
-
end
|
37
|
-
config_value.key = key
|
38
|
-
config_value.value = value_to_s(value)
|
39
|
-
end
|
40
|
-
settings << efc_value if efc_value
|
41
|
-
end
|
42
|
-
end
|
43
|
-
settings
|
44
|
-
end
|
45
|
-
|
46
|
-
# Flattens out the read settings from file, env or contrast ui.
|
47
|
-
# example: {"agent.polling.server_settings_ms"=>"50000"}
|
48
|
-
#
|
49
|
-
# If cli is set we avoid adding the path and additional '.' to the key.
|
50
|
-
#
|
51
|
-
# @param data [Hash, nil]
|
52
|
-
# @param path [String] where to look for settings.
|
53
|
-
# @param config [Hash] symbolized config to fetch keys from.
|
54
|
-
# @param cli [Boolean] does the config come from cli.
|
55
|
-
def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
|
56
|
-
return [] unless data
|
57
|
-
|
58
|
-
data.each_with_object([]) do |(k, v), entries|
|
59
|
-
if v.cs__is_a?(Hash)
|
60
|
-
entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
|
61
|
-
else
|
62
|
-
entries << { k.to_s => config.dig(*path, k).to_s } if cli
|
63
|
-
entries << { "#{ path.join('.') }.#{ k }" => config.dig(*path, k).to_s } unless cli
|
64
|
-
end
|
65
|
-
end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
|
66
|
-
end
|
67
15
|
|
68
|
-
|
69
|
-
#
|
70
|
-
# @param value [Hash, nil]
|
71
|
-
def value_to_s value
|
72
|
-
return if value.nil?
|
73
|
-
return value if value.cs__is_a?(String)
|
74
|
-
|
75
|
-
value&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
|
76
|
-
m[k] = if v.cs__is_a?(Hash)
|
77
|
-
value_to_s(v)
|
78
|
-
elsif v.cs__is_a?(Array)
|
79
|
-
v.map(&:to_s)
|
80
|
-
else
|
81
|
-
v.to_s
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
16
|
+
extend Contrast::Config::Diagnostics::SingletonTools
|
86
17
|
|
18
|
+
# TODO: RUBY-2113 deprecate name_prefix
|
19
|
+
#
|
87
20
|
# Converts current configuration from array of values to effective config values class and appends them to
|
88
21
|
# EffectiveConfig class. Must be used inside Config Components only.
|
89
22
|
#
|
@@ -91,13 +24,15 @@ module Contrast
|
|
91
24
|
# @param config_values [] array of the names of values.
|
92
25
|
# @param canonical_prefix [String] starting of the path to config => api.proxy...
|
93
26
|
# @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
|
94
|
-
def add_effective_config_values
|
27
|
+
def add_effective_config_values(effective_config,
|
28
|
+
config_values,
|
29
|
+
canonical_prefix,
|
30
|
+
name_prefix = canonical_prefix)
|
95
31
|
return if config_values.to_s.empty?
|
96
32
|
|
97
33
|
config_values.each do |config_value_name|
|
98
34
|
Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
|
99
|
-
|
100
|
-
|
35
|
+
config_value = send(config_value_name.to_sym)
|
101
36
|
fill_effective_value(new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix)
|
102
37
|
effective_config.values << new_effective_value
|
103
38
|
rescue StandardError => e
|
@@ -107,6 +42,8 @@ module Contrast
|
|
107
42
|
end
|
108
43
|
end
|
109
44
|
|
45
|
+
# TODO: RUBY-2113 deprecate name_prefix
|
46
|
+
#
|
110
47
|
# Converts current configuration from single value to effective config values class and appends them to
|
111
48
|
# EffectiveConfig class. Must be used inside Config Components only.
|
112
49
|
#
|
@@ -115,10 +52,12 @@ module Contrast
|
|
115
52
|
# @param config_value [String, Boolean] value of the config.
|
116
53
|
# @param canonical_prefix [String] starting of the path to config => api.proxy...
|
117
54
|
# @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
|
118
|
-
def add_single_effective_value
|
55
|
+
def add_single_effective_value(effective_config,
|
56
|
+
config_name,
|
57
|
+
config_value,
|
58
|
+
canonical_prefix,
|
59
|
+
name_prefix = canonical_prefix)
|
119
60
|
Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
|
120
|
-
break if Contrast::Utils::DuckUtils.empty_duck?(config_value)
|
121
|
-
|
122
61
|
fill_effective_value(new_effective_value, config_value, config_name, canonical_prefix, name_prefix)
|
123
62
|
effective_config.values << new_effective_value
|
124
63
|
rescue StandardError => e
|
@@ -139,7 +78,11 @@ module Contrast
|
|
139
78
|
# @return filled_new_effective_config [Contrast::Config::Diagnostics::EffectiveConfigValue]
|
140
79
|
def fill_effective_value new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix
|
141
80
|
find_source(new_effective_value, canonical_prefix, assign_name(config_value_name), name_prefix)
|
142
|
-
|
81
|
+
if Contrast::Config::Diagnostics::SingletonTools::API_CREDENTIALS.include?(config_value_name.to_s)
|
82
|
+
new_effective_value.value = Contrast::Configuration::EFFECTIVE_REDACTED
|
83
|
+
return new_effective_value
|
84
|
+
end
|
85
|
+
new_effective_value.value = config_value.cs__is_a?(Array) ? config_value.join(',') : config_value.to_s
|
143
86
|
new_effective_value
|
144
87
|
end
|
145
88
|
|
@@ -174,14 +117,10 @@ module Contrast
|
|
174
117
|
# For files we keep the whole path as source.
|
175
118
|
source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
|
176
119
|
new_effective_value.assign_filename(source)
|
177
|
-
new_source = if
|
178
|
-
source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
|
179
|
-
|
120
|
+
new_source = if Contrast::CONFIG.sources.configuration_file_source?(new_effective_value.canonical_name)
|
180
121
|
Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
|
181
|
-
else
|
182
|
-
Contrast::Components::Config::Sources::DEFAULT_VALUE
|
183
122
|
end
|
184
|
-
new_effective_value.source = new_source
|
123
|
+
new_effective_value.source = new_source || source
|
185
124
|
new_effective_value
|
186
125
|
end
|
187
126
|
|
@@ -50,7 +50,7 @@ module Contrast
|
|
50
50
|
#
|
51
51
|
# @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
|
52
52
|
def to_effective_config effective_config
|
53
|
-
add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME
|
53
|
+
add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -54,21 +54,9 @@ module Contrast
|
|
54
54
|
# @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
|
55
55
|
def to_effective_config effective_config
|
56
56
|
super
|
57
|
-
add_single_effective_value(effective_config,
|
58
|
-
|
59
|
-
|
60
|
-
CANON_NAME,
|
61
|
-
"#{ CONTRAST }.#{ CANON_NAME }")
|
62
|
-
add_single_effective_value(effective_config,
|
63
|
-
'name',
|
64
|
-
Contrast::APP_CONTEXT.server_name,
|
65
|
-
CANON_NAME,
|
66
|
-
"#{ CONTRAST }.#{ CANON_NAME }")
|
67
|
-
add_single_effective_value(effective_config,
|
68
|
-
'path',
|
69
|
-
Contrast::APP_CONTEXT.server_path,
|
70
|
-
CANON_NAME,
|
71
|
-
"#{ CONTRAST }.#{ CANON_NAME }")
|
57
|
+
add_single_effective_value(effective_config, 'type', Contrast::APP_CONTEXT.server_type, CANON_NAME)
|
58
|
+
add_single_effective_value(effective_config, 'name', Contrast::APP_CONTEXT.server_name, CANON_NAME)
|
59
|
+
add_single_effective_value(effective_config, 'path', Contrast::APP_CONTEXT.server_path, CANON_NAME)
|
72
60
|
end
|
73
61
|
end
|
74
62
|
end
|
@@ -63,6 +63,7 @@ module Contrast
|
|
63
63
|
CONFIG_BASE_PATHS = %w[./ config/ /etc/contrast/ruby/ /etc/contrast/ /etc/].cs__freeze
|
64
64
|
KEYS_TO_REDACT = %i[api_key url service_key user_name].cs__freeze
|
65
65
|
REDACTED = '**REDACTED**'
|
66
|
+
EFFECTIVE_REDACTED = '****'
|
66
67
|
|
67
68
|
DEPRECATED_PROPERTIES = %w[
|
68
69
|
CONTRAST__AGENT__SERVICE__ENABLE CONTRAST__AGENT__SERVICE__LOGGER__LEVEL
|
@@ -146,8 +147,10 @@ module Contrast
|
|
146
147
|
|
147
148
|
paths = []
|
148
149
|
# Environment paths takes precedence here. Look first through them.
|
149
|
-
|
150
|
-
|
150
|
+
config_path = ENV.fetch('CONTRAST_CONFIG_PATH', nil)
|
151
|
+
security_path = ENV.fetch('CONTRAST_SECURITY_CONFIG', nil)
|
152
|
+
paths << config_path if config_path
|
153
|
+
paths << security_path if security_path
|
151
154
|
|
152
155
|
extensions.each do |ext|
|
153
156
|
places = CONFIG_BASE_PATHS.product(["#{ basename }.#{ ext }"])
|
@@ -29,6 +29,7 @@ module Contrast
|
|
29
29
|
].cs__freeze
|
30
30
|
|
31
31
|
def initialize
|
32
|
+
@_frameworks = []
|
32
33
|
return if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
|
33
34
|
|
34
35
|
@_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
|
@@ -90,9 +91,7 @@ module Contrast
|
|
90
91
|
# this particular Request
|
91
92
|
# @return [::Rack::Request] either a rack request or subclass thereof.
|
92
93
|
def retrieve_request env
|
93
|
-
|
94
|
-
return Contrast::Framework::Rack::Support.retrieve_request(env)
|
95
|
-
end
|
94
|
+
return Contrast::Framework::Rack::Support.retrieve_request(env) if @_frameworks.empty?
|
96
95
|
|
97
96
|
framework = @_frameworks[0]
|
98
97
|
|
@@ -115,6 +114,8 @@ module Contrast
|
|
115
114
|
# @return [Boolean] true if at least one framework is streaming the response; false if none are streaming
|
116
115
|
def streaming? env
|
117
116
|
result = false
|
117
|
+
return result if @_frameworks.empty?
|
118
|
+
|
118
119
|
@_frameworks.each do |framework|
|
119
120
|
result = framework.streaming?(env)
|
120
121
|
break if result
|
@@ -29,7 +29,7 @@ module Contrast
|
|
29
29
|
# @param method_name [Symbol] the method to call on each FrameworkSupport class
|
30
30
|
# @return [Array]
|
31
31
|
def data_for_all_frameworks method_name
|
32
|
-
@_frameworks
|
32
|
+
@_frameworks&.flat_map { |framework| framework.send(method_name) }&.
|
33
33
|
compact
|
34
34
|
end
|
35
35
|
|
@@ -39,6 +39,8 @@ module Contrast
|
|
39
39
|
# @return [Object] - Determined by method to be invoked
|
40
40
|
def first_framework_result method_name, default_value
|
41
41
|
result = nil
|
42
|
+
return default_value if @_frameworks.empty?
|
43
|
+
|
42
44
|
@_frameworks.each do |framework|
|
43
45
|
result = framework.send(method_name)
|
44
46
|
break if result
|
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'contrast/framework/base_support'
|
5
5
|
require 'contrast/framework/rack/patch/support'
|
6
|
+
require 'contrast/utils/duck_utils'
|
6
7
|
|
7
8
|
module Contrast
|
8
9
|
module Framework
|
@@ -10,6 +11,9 @@ module Contrast
|
|
10
11
|
# Used when Rack is present to define framework specific behavior. For
|
11
12
|
# now, the only part of this implemented is the Patch Support.
|
12
13
|
module Support
|
14
|
+
RACK_REQUEST_PATH = 'REQUEST_PATH'
|
15
|
+
RACK_SERVER_NAME = 'SERVER_NAME'
|
16
|
+
|
13
17
|
extend Contrast::Framework::BaseSupport
|
14
18
|
extend Contrast::Framework::Rack::Patch::Support
|
15
19
|
class << self
|
@@ -74,8 +78,13 @@ module Contrast
|
|
74
78
|
def current_route_coverage request, _controller = nil, full_route = nil
|
75
79
|
method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
|
76
80
|
|
77
|
-
full_route ||= request.env
|
78
|
-
|
81
|
+
full_route ||= request.env.fetch(::Rack::PATH_INFO, nil)
|
82
|
+
full_route = request.env.fetch(RACK_REQUEST_PATH, nil) if Contrast::Utils::DuckUtils.empty_duck?(full_route)
|
83
|
+
return unless method
|
84
|
+
|
85
|
+
# If we are here and have method but the route is "" we might be expecting the home page.
|
86
|
+
full_route = '/' if Contrast::Utils::DuckUtils.empty_duck?(full_route) &&
|
87
|
+
request.env.fetch(RACK_SERVER_NAME, nil)
|
79
88
|
|
80
89
|
route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
|
81
90
|
# We might not have controller, or even if there is defined one, it could not bare the name of the
|
@@ -59,7 +59,7 @@ module Contrast
|
|
59
59
|
# ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
|
60
60
|
match, _params, route, path = get_full_route(request.rack_request)
|
61
61
|
unless route
|
62
|
-
logger.
|
62
|
+
logger.debug("Unable to determine the current route of this request: #{ request.rack_request }")
|
63
63
|
return
|
64
64
|
end
|
65
65
|
|
@@ -77,7 +77,7 @@ module Contrast
|
|
77
77
|
new_route_coverage&.attach_rails_data(route, original_url)
|
78
78
|
new_route_coverage
|
79
79
|
rescue StandardError => e
|
80
|
-
logger.
|
80
|
+
logger.error('Unable to determine the current route of this request due to exception: ', e)
|
81
81
|
nil
|
82
82
|
end
|
83
83
|
|
@@ -131,7 +131,16 @@ module Contrast
|
|
131
131
|
log([message, block_entry, outcome], ::Logger::Severity::DEBUG)
|
132
132
|
end
|
133
133
|
|
134
|
-
|
134
|
+
# Log successful attack attack
|
135
|
+
#
|
136
|
+
# @param rule_id [String] the rule that was triggered
|
137
|
+
# @param outcome [String] the outcome of the rule
|
138
|
+
# @param input_type [String] the type of input that was detected
|
139
|
+
# @param input_value [String] the value of the input that was detected
|
140
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
141
|
+
def successful_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
142
|
+
# We may log from the worthwatching Queue with saved attack_context
|
143
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
135
144
|
if input_type.present? && input_value.present?
|
136
145
|
successful_attack_with_input = "#{ input_type } had a value that successfully exploited" \
|
137
146
|
"#{ rule_id } - #{ input_value }"
|
@@ -142,7 +151,16 @@ module Contrast
|
|
142
151
|
end
|
143
152
|
end
|
144
153
|
|
145
|
-
|
154
|
+
# Log ineffective attack attack
|
155
|
+
#
|
156
|
+
# @param rule_id [String] the rule that was triggered
|
157
|
+
# @param outcome [String] the outcome of the rule
|
158
|
+
# @param input_type [String] the type of input that was detected
|
159
|
+
# @param input_value [String] the value of the input that was detected
|
160
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
161
|
+
def ineffective_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
162
|
+
# We may log from the worthwatching Queue with saved attack_context
|
163
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
146
164
|
if input_type.present? && input_value.present?
|
147
165
|
ineffective_attack_with_input = "#{ input_type } had a value that matched a signature for, " \
|
148
166
|
"but did not successfully exploit #{ rule_id } - #{ input_value }"
|
@@ -153,8 +171,16 @@ module Contrast
|
|
153
171
|
end
|
154
172
|
end
|
155
173
|
|
156
|
-
#
|
157
|
-
|
174
|
+
# Log suspicious attack
|
175
|
+
#
|
176
|
+
# @param rule_id [String] the rule that was triggered
|
177
|
+
# @param outcome [String] the outcome of the rule
|
178
|
+
# @param input_type [String] the type of input that was detected
|
179
|
+
# @param input_value [String] the value of the input that was detected
|
180
|
+
# @param attack_context [Contrast::Agent::RequestContext] the request context of the attack
|
181
|
+
def suspicious_attack rule_id, outcome, input_type = nil, input_value = nil, attack_context = nil
|
182
|
+
# We may log from the worthwatching Queue with saved attack_context
|
183
|
+
update_logger_formatter(@_cef_logger, new_context: attack_context) if attack_context
|
158
184
|
if input_type.present? && input_value.present?
|
159
185
|
suspicious_attack_with = "#{ input_type } included a potential attack value that was detected" \
|
160
186
|
"as suspicious using #{ rule_id } - #{ input_value }"
|
@@ -7,6 +7,8 @@ module Contrast
|
|
7
7
|
module Utils
|
8
8
|
# Util for information about an IO
|
9
9
|
module IOUtil
|
10
|
+
UNKNOWN_IO = 'unknown'
|
11
|
+
|
10
12
|
extend Contrast::Components::Logger::InstanceMethods
|
11
13
|
|
12
14
|
class << self
|
@@ -48,6 +50,7 @@ module Contrast
|
|
48
50
|
return false unless status
|
49
51
|
return false if status.pipe?
|
50
52
|
return false if status.socket?
|
53
|
+
return false if status.ftype == UNKNOWN_IO
|
51
54
|
|
52
55
|
true
|
53
56
|
end
|
@@ -12,7 +12,7 @@ module Contrast
|
|
12
12
|
# Method utility used by Contrast::Logger::log
|
13
13
|
module LogUtils
|
14
14
|
DEFAULT_NAME = 'contrast.log'
|
15
|
-
DEFAULT_LEVEL =
|
15
|
+
DEFAULT_LEVEL = 'INFO'
|
16
16
|
VALID_LEVELS = ::Ougai::Logging::Severity::SEV_LABEL
|
17
17
|
STDOUT_STR = 'STDOUT'
|
18
18
|
STDERR_STR = 'STDERR'
|
@@ -146,6 +146,7 @@ module Contrast
|
|
146
146
|
private
|
147
147
|
|
148
148
|
def build path: STDOUT_STR, level_const: DEFAULT_LEVEL
|
149
|
+
context = Contrast::Agent::REQUEST_TRACKER.current
|
149
150
|
logger = case path
|
150
151
|
when STDOUT_STR, STDERR_STR
|
151
152
|
::Logger.new(Object.cs__const_get(path))
|
@@ -154,15 +155,23 @@ module Contrast
|
|
154
155
|
end
|
155
156
|
logger.progname = PROGNAME
|
156
157
|
logger.level = level_const
|
157
|
-
|
158
|
+
update_logger_formatter(logger, new_context: context)
|
158
159
|
logger
|
159
160
|
end
|
160
161
|
|
161
162
|
def context
|
162
|
-
Contrast::Agent::REQUEST_TRACKER.current
|
163
|
+
@_context ||= Contrast::Agent::REQUEST_TRACKER.current
|
163
164
|
end
|
164
165
|
|
165
|
-
def
|
166
|
+
def context_update new_context
|
167
|
+
@_context = new_context unless new_context.nil?
|
168
|
+
end
|
169
|
+
|
170
|
+
# @param logger [Logger]
|
171
|
+
# @param new_context [Contrast::Agent::RequestContext]
|
172
|
+
def update_logger_formatter logger, new_context: nil
|
173
|
+
context_update(new_context) if new_context
|
174
|
+
|
166
175
|
ip_address = extract_ip_address
|
167
176
|
logger.formatter = proc do |severity, datetime, progname, msg|
|
168
177
|
date_format = datetime.strftime(DATE_TIME_FORMAT)
|
@@ -206,7 +215,7 @@ module Contrast
|
|
206
215
|
request_method = assign_request_method(context)
|
207
216
|
app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
|
208
217
|
attach_request_and_sender_info(message, sender_info)
|
209
|
-
message << "request=#{ context
|
218
|
+
message << "request=#{ context&.request&.url } "
|
210
219
|
message << "requestMethod=#{ request_method } "
|
211
220
|
message << "app=#{ app_name } "
|
212
221
|
message << "outcome=#{ outcome } "
|
@@ -238,16 +247,18 @@ module Contrast
|
|
238
247
|
end
|
239
248
|
|
240
249
|
def extract_sender_ip
|
241
|
-
request_headers = context
|
250
|
+
request_headers = context&.activity&.request&.headers&.transform_keys(&:to_s)
|
251
|
+
return unless request_headers
|
252
|
+
|
242
253
|
request_headers['X-Forwarded-For']
|
243
254
|
end
|
244
255
|
|
245
256
|
def assign_request_method context
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
257
|
+
request_method = context&.request&.rack_request&.env
|
258
|
+
request_method = request_method['REQUEST_METHOD'] if request_method
|
259
|
+
return DEFAULT_METADATA if request_method.nil? || !request_method.length.positive?
|
260
|
+
|
261
|
+
request_method
|
251
262
|
end
|
252
263
|
end
|
253
264
|
end
|
@@ -12,7 +12,7 @@ module Contrast
|
|
12
12
|
NUM_PATTERN = %r{/\d+/}.cs__freeze
|
13
13
|
END_PATTERN = %r{/\d+$}.cs__freeze
|
14
14
|
STATIC_SUFFIXES = /\.(?:js|css|jpeg|jpg|gif|png|ico|woff|svg|pdf|eot|ttf|jar)$/i.cs__freeze
|
15
|
-
UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable
|
15
|
+
UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable Layout/LineLength
|
16
16
|
# Regular expression to match any type of hash pattern that is 16 bytes like uuid with no
|
17
17
|
# slashes, md5, sha1, sha256, etc
|
18
18
|
HASH_PATTERN = Regexp.new('([a-fA-F0-9]{2}){16,}').cs__freeze
|
data/lib/contrast/utils/timer.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contrast-agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- galen.palmer@contrastsecurity.com
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: exe
|
15
15
|
cert_chain: []
|
16
|
-
date: 2023-
|
16
|
+
date: 2023-09-21 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -1058,6 +1058,7 @@ files:
|
|
1058
1058
|
- lib/contrast/agent/protect/rule/xss/xss.rb
|
1059
1059
|
- lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb
|
1060
1060
|
- lib/contrast/agent/protect/rule/xxe/xxe.rb
|
1061
|
+
- lib/contrast/agent/protect/state.rb
|
1061
1062
|
- lib/contrast/agent/reactions/disable_reaction.rb
|
1062
1063
|
- lib/contrast/agent/reporting/attack_result/attack_result.rb
|
1063
1064
|
- lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb
|
@@ -1253,6 +1254,7 @@ files:
|
|
1253
1254
|
- lib/contrast/config/diagnostics/effective_config_value.rb
|
1254
1255
|
- lib/contrast/config/diagnostics/environment_variables.rb
|
1255
1256
|
- lib/contrast/config/diagnostics/monitor.rb
|
1257
|
+
- lib/contrast/config/diagnostics/singleton_tools.rb
|
1256
1258
|
- lib/contrast/config/diagnostics/source_config_value.rb
|
1257
1259
|
- lib/contrast/config/diagnostics/tools.rb
|
1258
1260
|
- lib/contrast/config/diagnostics/user_configuration_file.rb
|