contrast-agent 7.3.2 → 7.4.1

Sign up to get free protection for your applications and to get access to all the features.
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