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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/middleware/middleware.rb +1 -1
  3. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +9 -11
  4. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +55 -20
  5. data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -4
  6. data/lib/contrast/agent/protect/rule/base.rb +61 -26
  7. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +12 -4
  8. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +19 -15
  9. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +2 -4
  10. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +2 -1
  11. data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +4 -4
  12. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  13. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  14. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli.rb +5 -2
  15. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +20 -8
  16. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
  17. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  18. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
  19. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
  20. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
  21. data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
  22. data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
  23. data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
  24. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
  25. data/lib/contrast/agent/protect/state.rb +110 -0
  26. data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
  27. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
  28. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +2 -0
  29. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  30. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -4
  31. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  32. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -0
  33. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  34. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -8
  35. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  36. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  37. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  38. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -5
  39. data/lib/contrast/agent/reporting/settings/protect.rb +3 -3
  40. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  41. data/lib/contrast/agent/request/request_context_extend.rb +0 -2
  42. data/lib/contrast/agent/version.rb +1 -1
  43. data/lib/contrast/components/agent.rb +3 -5
  44. data/lib/contrast/components/api.rb +3 -3
  45. data/lib/contrast/components/assess.rb +4 -0
  46. data/lib/contrast/components/assess_rules.rb +1 -2
  47. data/lib/contrast/components/base.rb +1 -2
  48. data/lib/contrast/components/config/sources.rb +23 -0
  49. data/lib/contrast/components/logger.rb +19 -0
  50. data/lib/contrast/components/protect.rb +55 -14
  51. data/lib/contrast/components/sampling.rb +5 -12
  52. data/lib/contrast/components/security_logger.rb +17 -0
  53. data/lib/contrast/components/settings.rb +110 -76
  54. data/lib/contrast/config/certification_configuration.rb +1 -1
  55. data/lib/contrast/config/configuration_files.rb +0 -2
  56. data/lib/contrast/config/diagnostics/config.rb +3 -3
  57. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  58. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  59. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  60. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  61. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  62. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  63. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  64. data/lib/contrast/config/server_configuration.rb +3 -15
  65. data/lib/contrast/configuration.rb +5 -2
  66. data/lib/contrast/framework/manager.rb +4 -3
  67. data/lib/contrast/framework/manager_extend.rb +3 -1
  68. data/lib/contrast/framework/rack/support.rb +11 -2
  69. data/lib/contrast/framework/rails/support.rb +2 -2
  70. data/lib/contrast/logger/cef_log.rb +30 -4
  71. data/lib/contrast/utils/io_util.rb +3 -0
  72. data/lib/contrast/utils/log_utils.rb +22 -11
  73. data/lib/contrast/utils/request_utils.rb +1 -1
  74. data/lib/contrast/utils/timer.rb +1 -1
  75. 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
- # Recursively converts each value to string.
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 effective_config, config_values, canonical_prefix, name_prefix
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
- next if Contrast::Utils::DuckUtils.empty_duck?((config_value = send(config_value_name.to_sym)))
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 effective_config, config_name, config_value, canonical_prefix, name_prefix
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
- new_effective_value.value = config_value
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 source.include?(Contrast::Config::LocalSourceValue::YAML_EXT) ||
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, "#{ CONTRAST }.#{ 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
- 'type',
59
- Contrast::APP_CONTEXT.server_type,
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
- paths << ENV['CONTRAST_CONFIG_PATH'] if ENV['CONTRAST_CONFIG_PATH']
150
- paths << ENV['CONTRAST_SECURITY_CONFIG'] if ENV['CONTRAST_SECURITY_CONFIG']
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
- if Contrast::Utils::DuckUtils.empty_duck?(@_frameworks)
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.flat_map { |framework| framework.send(method_name) }.
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[::Rack::PATH_INFO]
78
- return unless full_route && method
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.warn("Unable to determine the current route of this request: #{ request.rack_request }")
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.warn('Unable to determine the current route of this request due to exception: ', e)
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
- def successful_attack rule_id, outcome, input_type = nil, input_value = nil
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
- def ineffective_attack rule_id, outcome, input_type = nil, input_value = nil
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
- # newer - currently not in the agent, currently is a probe for us
157
- def suspicious_attack rule_id, outcome, input_type = nil, input_value = nil
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 = ::Ougai::Logging::Severity::INFO
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
- change_logger_formatter(logger)
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 change_logger_formatter logger
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.request.url } "
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.activity.request.headers&.transform_keys(&:to_s)
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
- if context.request.rack_request.env['REQUEST_METHOD'].length.positive?
247
- context.request.rack_request.env['REQUEST_METHOD']
248
- else
249
- DEFAULT_METADATA
250
- end
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 Metrics/LineLength
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
@@ -29,7 +29,7 @@ module Contrast
29
29
  #
30
30
  # @return[String]
31
31
  def self.time_now
32
- Time.now.utc.iso8601(7)
32
+ Time.now.utc.iso8601(2)
33
33
  end
34
34
 
35
35
  # Converts time given in ms format form TS to HttpDate.
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.3.2
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-08-09 00:00:00.000000000 Z
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