contrast-agent 7.4.0 → 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 (33) 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 +56 -25
  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 +2 -10
  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/no_sqli/no_sqli.rb +5 -2
  13. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +12 -7
  14. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +2 -2
  15. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +2 -3
  16. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +3 -4
  17. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload.rb +3 -0
  18. data/lib/contrast/agent/protect/rule/utils/builders.rb +3 -4
  19. data/lib/contrast/agent/protect/rule/utils/filters.rb +32 -16
  20. data/lib/contrast/agent/protect/rule/xss/xss.rb +80 -0
  21. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +9 -2
  22. data/lib/contrast/agent/reporting/details/xss_match.rb +17 -0
  23. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +32 -0
  24. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -5
  25. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +1 -4
  26. data/lib/contrast/agent/request/request_context_extend.rb +0 -2
  27. data/lib/contrast/agent/version.rb +1 -1
  28. data/lib/contrast/components/assess.rb +4 -0
  29. data/lib/contrast/framework/rails/support.rb +2 -2
  30. data/lib/contrast/logger/cef_log.rb +30 -4
  31. data/lib/contrast/utils/io_util.rb +3 -0
  32. data/lib/contrast/utils/log_utils.rb +21 -10
  33. metadata +2 -2
@@ -26,9 +26,8 @@ module Contrast
26
26
  return unless result
27
27
 
28
28
  append_to_activity(context, result)
29
-
30
- cef_logging(result, :successful_attack)
31
- raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked?
29
+ record_triggered(context)
30
+ raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE)) if blocked_violation?(result)
32
31
  end
33
32
 
34
33
  protected
@@ -39,8 +38,8 @@ module Contrast
39
38
 
40
39
  def build_violation context, potential_attack_string
41
40
  result = build_attack_result(context)
42
- update_successful_attack_response(context, nil, result, potential_attack_string)
43
41
  append_sample(context, nil, result, potential_attack_string)
42
+ update_successful_attack_response(context, nil, result, potential_attack_string)
44
43
  result
45
44
  end
46
45
 
@@ -28,6 +28,9 @@ module Contrast
28
28
  APPLICABLE_USER_INPUTS
29
29
  end
30
30
 
31
+ # Return the specific blocking message for this rule.
32
+ #
33
+ # @return [String] the reason for the raised security exception.
31
34
  def block_message
32
35
  BLOCK_MESSAGE
33
36
  end
@@ -29,8 +29,8 @@ module Contrast
29
29
  # this input
30
30
  def build_attack_with_match context, ia_result, result, candidate_string, **kwargs
31
31
  result ||= build_attack_result(context)
32
- update_successful_attack_response(context, ia_result, result, candidate_string)
33
32
  append_sample(context, ia_result, result, candidate_string, **kwargs)
33
+ update_successful_attack_response(context, ia_result, result, candidate_string)
34
34
 
35
35
  result
36
36
  end
@@ -53,8 +53,8 @@ module Contrast
53
53
  # this input
54
54
  def build_attack_without_match context, ia_result, result, **kwargs
55
55
  result ||= build_attack_result(context)
56
- update_perimeter_attack_response(context, ia_result, result)
57
56
  append_sample(context, ia_result, result, nil, **kwargs)
57
+ update_perimeter_attack_response(context, ia_result, result)
58
58
 
59
59
  result
60
60
  end
@@ -97,11 +97,10 @@ module Contrast
97
97
  # @param potential_attack_string [String]
98
98
  def build_violation context, potential_attack_string
99
99
  result = build_attack_result(context)
100
+ append_sample(context, nil, result, potential_attack_string)
100
101
  update_successful_attack_response(context, nil, result, potential_attack_string)
101
102
  return unless result
102
103
 
103
- append_sample(context, nil, result, potential_attack_string)
104
- cef_logging(result, :successful_attack)
105
104
  result
106
105
  end
107
106
  end
@@ -25,11 +25,12 @@ module Contrast
25
25
 
26
26
  ia_results.each do |ia_result|
27
27
  result = build_attack_result(context)
28
- build_attack_without_match(context, ia_result, result)
29
- append_to_activity(context, result)
28
+ result = build_attack_without_match(context, ia_result, result)
29
+ next unless result
30
30
 
31
- cef_logging(result, :successful_attack)
32
- raise(Contrast::SecurityException.new(self, block_message)) if blocked?
31
+ append_to_activity(context, result)
32
+ record_triggered(context)
33
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
33
34
  end
34
35
  end
35
36
 
@@ -39,10 +40,10 @@ module Contrast
39
40
  # @param context [Contrast::Agent::RequestContext]
40
41
  # @return [Boolean]
41
42
  def prefilter? context
42
- return false unless context
43
43
  return false unless enabled?
44
- return false unless (results = gather_ia_results(context)) && results.any?
45
44
  return false if protect_excluded_by_url?(rule_name)
45
+ return false unless context
46
+ return false unless (results = gather_ia_results(context)) && results.any?
46
47
  return false if protect_excluded_by_input?(results)
47
48
 
48
49
  true
@@ -52,13 +53,20 @@ module Contrast
52
53
  # have a different implementation based on the rule. As such, there
53
54
  # is not parent implementation.
54
55
  #
55
- # @param _context [Contrast::Agent::RequestContext] the context for
56
+ # @param context [Contrast::Agent::RequestContext] the context for
56
57
  # the current request
57
- # @param _match_string [String] the input that violated the rule and
58
+ # @param match_string [String] the input that violated the rule and
58
59
  # matched the attack detection logic
59
60
  # @param _kwargs [Hash] key-value pairs used by the rule to build a
60
61
  # report.
61
- def infilter _context, _match_string, **_kwargs; end
62
+ def infilter context, match_string, _kwargs
63
+ return unless infilter?(context)
64
+ return unless (result = build_violation(context, match_string))
65
+
66
+ append_to_activity(context, result)
67
+ record_triggered(context)
68
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked?
69
+ end
62
70
 
63
71
  # Infilter check always called before infilter to check if the rule is infilter
64
72
  # capable, not disabled or in other way excluded by url or input exclusions.
@@ -74,6 +82,18 @@ module Contrast
74
82
  true
75
83
  end
76
84
 
85
+ # Check befor commiting infilter
86
+ #
87
+ # @param context [Contrast::Agent::RequestContext]
88
+ def postfilter? context
89
+ return false unless enabled? && POSTFILTER_MODES.include?(mode)
90
+ return false if protect_excluded_by_url?(rule_name)
91
+ return false if protect_excluded_by_input?(gather_ia_results(context))
92
+ return false if mode == :NO_ACTION || mode == :PERMIT
93
+
94
+ true
95
+ end
96
+
77
97
  # Actions required for the rules that have to happen after the
78
98
  # application has completed its processing of the request.
79
99
  #
@@ -88,18 +108,14 @@ module Contrast
88
108
  # @param context [Contrast::Agent::RequestContext]
89
109
  # @raise [Contrast::SecurityException]
90
110
  def postfilter context
91
- return unless enabled? && POSTFILTER_MODES.include?(mode)
92
- return false if protect_excluded_by_url?(rule_name)
93
- return if protect_excluded_by_input?(gather_ia_results(context))
94
-
95
- return if mode == :NO_ACTION || mode == :PERMIT
111
+ return unless postfilter?(context)
96
112
 
97
113
  result = find_postfilter_attacker(context, nil)
98
114
  return unless result&.samples&.any?
99
115
 
100
- cef_logging(result)
101
116
  append_to_activity(context, result)
102
- return unless result.response == :BLOCKED
117
+ record_triggered(context)
118
+ return unless blocked_violation?(result)
103
119
 
104
120
  raise(Contrast::SecurityException.new(self, "#{ rule_name } triggered in postfilter. Response blocked."))
105
121
  end
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'contrast/agent/protect/rule/base'
5
5
  require 'contrast/agent/protect/rule/xss/reflected_xss_input_classification'
6
+ require 'contrast/agent/reporting/details/xss_details'
7
+ require 'contrast/agent/reporting/details/xss_match'
6
8
  require 'contrast/agent/reporting/input_analysis/input_type'
7
9
 
8
10
  module Contrast
@@ -25,10 +27,56 @@ module Contrast
25
27
  NAME
26
28
  end
27
29
 
30
+ # Return the specific blocking message for this rule.
31
+ #
32
+ # @return [String] the reason for the raised security exception.
28
33
  def block_message
29
34
  BLOCK_MESSAGE
30
35
  end
31
36
 
37
+ # Prefilter check always called before infilter to check if the rule is infilter
38
+ # capable, not disabled or in other way excluded by url or input exclusions.
39
+ #
40
+ # @param context [Contrast::Agent::RequestContext]
41
+ # @return [Boolean]
42
+ def prefilter? context
43
+ return false unless enabled?
44
+ return false if protect_excluded_by_url?(rule_name)
45
+ return false unless context
46
+ return false unless (results = gather_ia_results(context)) && results.any?
47
+ return false if protect_excluded_by_input?(results)
48
+
49
+ true
50
+ end
51
+
52
+ def prefilter context
53
+ return unless prefilter?(context)
54
+
55
+ ia_results = gather_ia_results(context)
56
+
57
+ ia_results.each do |ia_result|
58
+ result = build_attack_result(context)
59
+ result = build_attack_without_match(context, ia_result, result)
60
+ next unless result
61
+
62
+ append_to_activity(context, result)
63
+ # XSS is being triggered, so we need to add it to the triggered rules,
64
+ # So the IA won't be done for this rule again for the current request.
65
+ record_triggered(context)
66
+ raise(Contrast::SecurityException.new(self, block_message)) if blocked_violation?(result)
67
+ end
68
+ end
69
+
70
+ # XSS is evaluated only on prefilter
71
+ def infilter? _context
72
+ false
73
+ end
74
+
75
+ # XSS is evaluated only on prefilter
76
+ def postfilter? _context
77
+ false
78
+ end
79
+
32
80
  # XSS Upload input classification
33
81
  #
34
82
  # @return [module<Contrast::Agent::Protect::Rule::ReflectedXssInputClassification>]
@@ -43,6 +91,38 @@ module Contrast
43
91
  def applicable_user_inputs
44
92
  APPLICABLE_USER_INPUTS
45
93
  end
94
+
95
+ # Adding XSS details
96
+ #
97
+ # @param context [Contrast::Agent::RequestContext]
98
+ # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult]
99
+ # @param _xss_string
100
+ # @param **_kwargs
101
+ # @return [Contrast::Agent::Reporting::RaspRuleSample]
102
+ def build_sample context, ia_result, _xss_string, **_kwargs
103
+ sample = build_base_sample(context, ia_result)
104
+ sample.details = Contrast::Agent::Reporting::Details::XssDetails.new
105
+ sample.details.input = ia_result.value
106
+
107
+ # TODO: RUBY-99999 check the if the ReflectedXss matches are needed.
108
+ xss_match = Contrast::Agent::Reporting::Details::XssMatch.new(ia_result.value)
109
+ sample.details.matches << xss_match unless xss_match.empty?
110
+
111
+ sample
112
+ end
113
+
114
+ private
115
+
116
+ # @param context [Contrast::Agent::RequestContext]
117
+ # @param potential_attack_string [String, nil]
118
+ # @return [Contrast::Agent::Reporting::AttackResult, nil]
119
+ def find_postfilter_attacker context, potential_attack_string, **kwargs
120
+ ia_results = gather_ia_results(context)
121
+ ia_results.reject! do |ia_result|
122
+ ia_result.score_level == Contrast::Agent::Reporting::ScoreLevel::IGNORE
123
+ end
124
+ find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
125
+ end
46
126
  end
47
127
  end
48
128
  end
@@ -26,6 +26,13 @@ module Contrast
26
26
  NAME
27
27
  end
28
28
 
29
+ # Return the specific blocking message for this rule.
30
+ #
31
+ # @return [String] the reason for the raised security exception.
32
+ def block_message
33
+ BLOCK_MESSAGE
34
+ end
35
+
29
36
  # Given an xml, evaluate it for an XXE attack. There's no return here
30
37
  # as this method handles appending the evaluation to the request
31
38
  # context, connecting it to the reporting mechanism at request end.
@@ -43,9 +50,9 @@ module Contrast
43
50
  return unless result
44
51
 
45
52
  append_to_activity(context, result)
46
- return unless blocked?
53
+ record_triggered(context)
54
+ return unless blocked_violation?(result)
47
55
 
48
- cef_logging(result, :successful_attack, value: xml)
49
56
  raise(Contrast::SecurityException.new(self, BLOCK_MESSAGE))
50
57
  end
51
58
 
@@ -9,6 +9,9 @@ module Contrast
9
9
  module Details
10
10
  # Matcher data for XSS rule.
11
11
  class XssMatch
12
+ EVIDENCE_START = /<script.*?>/i.cs__freeze
13
+ EVIDENCE_END = %r{</script.*?>}i.cs__freeze
14
+
12
15
  # @return [Integer] in ms
13
16
  attr_accessor :evidence_start
14
17
  # @return [String]
@@ -16,6 +19,16 @@ module Contrast
16
19
  # @return [Integer]
17
20
  attr_accessor :offset
18
21
 
22
+ # @param xss_string [String] to check for matches.
23
+ def initialize xss_string = ''
24
+ return if xss_string.empty?
25
+
26
+ @evidence_start = xss_string.index(EVIDENCE_START)
27
+ @offset = (xss_string[0...@evidence_start] || '').length
28
+ @evidence = xss_string[@evidence_start...xss_string.index(EVIDENCE_END)].gsub(EVIDENCE_START, '').
29
+ gsub(EVIDENCE_END, '')
30
+ end
31
+
19
32
  def to_controlled_hash
20
33
  {
21
34
  evidenceStart: evidence_start,
@@ -23,6 +36,10 @@ module Contrast
23
36
  offset: offset
24
37
  }
25
38
  end
39
+
40
+ def empty?
41
+ evidence_start.nil? || evidence.nil? || offset.nil?
42
+ end
26
43
  end
27
44
  end
28
45
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/utils/object_share'
5
+ require 'contrast/utils/duck_utils'
5
6
  require 'contrast/agent/reporting/input_analysis/input_analysis_result'
6
7
 
7
8
  module Contrast
@@ -12,10 +13,20 @@ module Contrast
12
13
  # @return [Hash] Stored request inputs for this context.
13
14
  attr_accessor :inputs
14
15
 
16
+ # Records rules triggered on sink.
17
+ #
18
+ # @return [Array<String>] array with rule names.
15
19
  def triggered_rules
16
20
  @_triggered_rules ||= []
17
21
  end
18
22
 
23
+ # Records rules with input analysis for current request.
24
+ #
25
+ # @return [Array<String>] array with rule names.
26
+ def analysed_rules
27
+ @_analysed_rules ||= []
28
+ end
29
+
19
30
  # result from input analysis
20
31
  #
21
32
  # @return @_results [Array<Contrast::Agent::Reporting::Settings::InputAnalysisResult>]
@@ -44,6 +55,27 @@ module Contrast
44
55
  def request= request
45
56
  @_request = request if request.instance_of?(Contrast::Agent::Request)
46
57
  end
58
+
59
+ # Check if the InputAnalysis is empty.
60
+ def no_inputs?
61
+ Contrast::Utils::DuckUtils.empty_duck?(inputs)
62
+ end
63
+
64
+ # We add the analysed rules to the list. The IA won't be build for this rule,
65
+ # since already it's already available.
66
+ #
67
+ # @param rule_name [String] name to record.
68
+ def record_analysed_rule rule_name
69
+ analysed_rules << rule_name unless triggered_rules.include?(rule_name)
70
+ end
71
+
72
+ # We add the triggered rules to the list. After request analysis will skip this rule
73
+ # as already triggered.
74
+ #
75
+ # @param rule_name [String] name to record.
76
+ def record_rule_triggered rule_name
77
+ triggered_rules << rule_name unless triggered_rules.include?(rule_name)
78
+ end
47
79
  end
48
80
  end
49
81
  end
@@ -119,16 +119,12 @@ module Contrast
119
119
  ruleId: rule_id,
120
120
  session_id: ::Contrast::ASSESS.session_id,
121
121
  version: 4
122
- }
122
+ }.compact
123
123
  end
124
124
 
125
125
  # @raise [ArgumentError]
126
126
  def validate
127
127
  raise(ArgumentError, "#{ self } did not have a proper rule. Unable to continue.") unless @rule_id
128
-
129
- unless ::Contrast::ASSESS.session_id
130
- raise(ArgumentError, "#{ self } did not have a proper session id. Unable to continue.")
131
- end
132
128
  if event_based? && events.empty?
133
129
  raise(ArgumentError, "#{ self } did not have proper events for #{ @rule_id }. Unable to continue.")
134
130
  end
@@ -47,7 +47,7 @@ module Contrast
47
47
  key: 0,
48
48
  session_id: ::Contrast::ASSESS.session_id,
49
49
  routes: @routes.map(&:to_controlled_hash)
50
- }
50
+ }.compact
51
51
  end
52
52
 
53
53
  # @raise [ArgumentError]
@@ -55,9 +55,6 @@ module Contrast
55
55
  unless Contrast::Utils::StringUtils.present?(data)
56
56
  raise(ArgumentError, "#{ cs__class } did not have a proper data. Unable to continue.")
57
57
  end
58
- unless ::Contrast::ASSESS.session_id
59
- raise(ArgumentError, "#{ cs__class } did not have a proper session id. Unable to continue.")
60
- end
61
58
  unless Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
62
59
  raise(ArgumentError, "#{ cs__class } did not have a proper Application Name. Unable to continue.")
63
60
  end
@@ -52,8 +52,6 @@ module Contrast
52
52
  if (ia = Contrast::Agent::Protect::InputAnalyzer.analyse(request))
53
53
  # Handle prefilter
54
54
  Contrast::Agent::Protect::InputAnalyzer.input_classification(ia, prefilter: true)
55
- # Reflected xss infilter
56
- Contrast::Agent::Protect::InputAnalyzer.input_classification_for('reflected-xss', ia)
57
55
  @agent_input_analysis = ia
58
56
  else
59
57
  logger.trace('Analysis from Agent was empty.')
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.4.0'
6
+ VERSION = '7.4.1'
7
7
  end
8
8
  end
@@ -237,6 +237,10 @@ module Contrast
237
237
 
238
238
  # The id for this process, based on the session metadata or id provided by the user, as indicated in
239
239
  # application startup.
240
+ #
241
+ # The ID of the current application run, as returned by the application settings endpoint or set by
242
+ # application.session_id. If there is no session associated with this run, this field should be omitted
243
+ # when reporting to TS.
240
244
  def session_id
241
245
  ::Contrast::SETTINGS.assess_state.session_id
242
246
  end
@@ -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
@@ -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
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.0
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-09-12 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