contrast-agent 7.2.0 → 7.3.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/policy_node.rb +25 -6
  3. data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
  4. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  5. data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
  6. data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
  7. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
  8. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
  9. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
  10. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  11. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
  13. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
  14. data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
  15. data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
  16. data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
  17. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
  18. data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
  19. data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
  20. data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
  21. data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
  22. data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
  23. data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
  24. data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
  25. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
  26. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
  27. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
  28. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
  29. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
  30. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
  31. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
  32. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
  33. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
  34. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
  35. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
  36. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
  37. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
  38. data/lib/contrast/agent/telemetry/base.rb +28 -2
  39. data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
  40. data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
  41. data/lib/contrast/agent/telemetry/client.rb +10 -2
  42. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +4 -3
  43. data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
  44. data/lib/contrast/agent/telemetry/identifier.rb +13 -26
  45. data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
  46. data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
  47. data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
  48. data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
  49. data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/components/assess.rb +33 -6
  52. data/lib/contrast/components/base.rb +4 -2
  53. data/lib/contrast/components/config.rb +6 -6
  54. data/lib/contrast/components/protect.rb +11 -1
  55. data/lib/contrast/components/sampling.rb +15 -10
  56. data/lib/contrast/config/diagnostics/command_line.rb +2 -2
  57. data/lib/contrast/config/diagnostics/environment_variables.rb +5 -2
  58. data/lib/contrast/config/diagnostics/tools.rb +15 -5
  59. data/lib/contrast/config/yaml_file.rb +8 -0
  60. data/lib/contrast/configuration.rb +61 -29
  61. data/lib/contrast/framework/rails/support.rb +3 -0
  62. data/lib/contrast/logger/application.rb +3 -3
  63. data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
  64. data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
  65. data/lib/contrast/utils/metrics_hash.rb +1 -1
  66. data/lib/contrast/utils/object_share.rb +2 -1
  67. data/lib/contrast/utils/os.rb +1 -9
  68. data/lib/contrast/utils/response_utils.rb +12 -0
  69. data/lib/contrast/utils/timer.rb +2 -0
  70. data/lib/contrast.rb +9 -2
  71. data/resources/assess/policy.json +80 -3
  72. data/ruby-agent.gemspec +1 -1
  73. metadata +22 -6
  74. data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -0,0 +1,198 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/utils/lru_cache'
5
+ require 'contrast/utils/duck_utils'
6
+ require 'contrast/agent/protect/rule/input_classification/cached_result'
7
+ require 'contrast/agent/protect/rule/input_classification/statistics'
8
+ require 'contrast/agent/protect/rule/input_classification/utils'
9
+
10
+ module Contrast
11
+ module Agent
12
+ module Protect
13
+ module Rule
14
+ module InputClassification
15
+ # This Class with store the input classification results for a given user input.
16
+ # Among the most used inputs are the headers values for session_id and path values.
17
+ class LRUCache < Contrast::Utils::LRUCache
18
+ include Contrast::Components::Logger::InstanceMethods
19
+ include Contrast::Agent::Protect::Rule::InputClassification::Utils
20
+
21
+ # Protect rules will always be fixed number, on other hand the number of inputs will grow,
22
+ # we need to limit the number of inputs to be cached.
23
+ RESULTS_CAPACITY = 20
24
+
25
+ private :[], :[]=
26
+
27
+ # Initialize the LRU Cache.
28
+ #
29
+ # @param capacity [Integer] the maximum number of elements to store in the cache. For this
30
+ # instance it will never reach outside of the number of supported Protect rules.
31
+ def initialize capacity = 10
32
+ super(capacity)
33
+ end
34
+
35
+ def mutex
36
+ @_mutex ||= Mutex.new
37
+ end
38
+
39
+ def with_mutex &block
40
+ return_type = mutex.synchronize(&block)
41
+ ensure
42
+ return_type
43
+ end
44
+
45
+ # Capacity of the statistics will always be the number of rule_id Protect supports.
46
+ #
47
+ # @return [Contrast::Agent::Protect::Rule::InputClassification::Statistics]
48
+ def statistics
49
+ @_statistics ||= Contrast::Agent::Protect::Rule::InputClassification::Statistics.new
50
+ end
51
+
52
+ # Clear the cache and statistics.
53
+ def clear
54
+ with_mutex { @cache.clear }
55
+ clear_statistics
56
+ end
57
+
58
+ # Clear only the statistics.
59
+ def clear_statistics
60
+ with_mutex { statistics.send(:clear) }
61
+ end
62
+
63
+ # Check if the cache is empty.
64
+ #
65
+ # @return [Boolean]
66
+ def empty?
67
+ Contrast::Utils::DuckUtils.empty_duck?(@cache)
68
+ end
69
+
70
+ # Check if the input is cached and returns it if so and record the statistics for the required input.
71
+ #
72
+ # @param rule_id [String] the Protect rule name.
73
+ # @param input [String] the user input.
74
+ # @param input_type [Symbol] Type of the input
75
+ # @param request [Contrast::Agent::Request] the current request.
76
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
77
+ def lookout rule_id, input, input_type, request
78
+ with_mutex { _loockout(rule_id, input, input_type, request) }
79
+ end
80
+
81
+ # Save the input classification result for a given user input.
82
+ #
83
+ # @param rule_id [String] the Protect rule name.
84
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
85
+ # @param request [Contrast::Agent::Request] the current request.
86
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
87
+ def save rule_id, result, request
88
+ with_mutex { _save(rule_id, result, request) }
89
+ end
90
+
91
+ private
92
+
93
+ # Check if the input is cached and returns it if so and record the statistics for the required input.
94
+ #
95
+ # @param rule_id [String] the Protect rule name.
96
+ # @param input [String] the user input.
97
+ # @param input_type [Symbol] Type of the input
98
+ # @param request [Contrast::Agent::Request] the current request.
99
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
100
+ def _loockout rule_id, input, input_type, request
101
+ cached = retrieve(rule_id, input, input_type)
102
+ if cached.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
103
+ # Telemetry event matched.
104
+ statistics.match!(rule_id, cached, request)
105
+ cached
106
+ else
107
+ # Telemetry event mismatched.
108
+ statistics.mismatch!(rule_id, input_type)
109
+ nil
110
+ end
111
+ rescue StandardError => e
112
+ logger.error("[IA_LRU_Cache] Error while looking for #{ input_type } for #{ rule_id }", error: e)
113
+ nil
114
+ end
115
+
116
+ # Save the input classification result for a given user input.
117
+ #
118
+ # @param rule_id [String] the Protect rule name.
119
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
120
+ # @param request [Contrast::Agent::Request] the current request.
121
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
122
+ def _save rule_id, result, request
123
+ cached_result = Contrast::Agent::Protect::InputClassification::CachedResult.new(result, request.__id__)
124
+ new_entry = safe_extract(push(rule_id, cached_result)) unless cached_result.empty?
125
+ statistics.push(rule_id, cached_result)
126
+
127
+ new_entry
128
+ rescue StandardError => e
129
+ logger.error("[IA_LRU_Cache] Error while saving #{ result } for #{ rule_id }",
130
+ error: e,
131
+ stack_trace: e.backtrace)
132
+ end
133
+
134
+ # @param rule_id [String] the Protect rule name.
135
+ # @param result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
136
+ # @return [Array<Contrast::Agent::Protect::InputClassification::CachedResult>, nil]
137
+ def push rule_id, result
138
+ return unless result.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
139
+ return @cache[rule_id] = [result] unless key?(rule_id)
140
+
141
+ cached_results = @cache[rule_id]
142
+ cached_results.shift if cached_results.length >= RESULTS_CAPACITY
143
+ return @cache[rule_id] << result unless already_saved?(result, rule_id)
144
+
145
+ nil
146
+ end
147
+
148
+ # Returns the cached result for a given user input. If the input is not cached, it will return nil.
149
+ # If the input is cached and key is needed, it will return the cached result and the key and key_type.
150
+ #
151
+ # @param rule_id [String] the Protect rule name.
152
+ # @param input [String] the user input.
153
+ # @param input_type [Symbol] Type of the input
154
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
155
+ def retrieve rule_id, input, input_type
156
+ # Check to see if cache exist if not just return the keys info.
157
+ return unless key?(rule_id)
158
+
159
+ safe_extract(query_fetch(rule_id, input, input_type))
160
+ end
161
+
162
+ # returns true on first input already saved with the same input.
163
+ # We will know if the key is needed from the result itself.
164
+ #
165
+ # @param cached_result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
166
+ # @param rule_id [String]
167
+ # @return [Boolean]
168
+ def already_saved? cached_result, rule_id
169
+ return false unless cached_result.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
170
+
171
+ @cache[rule_id].any? do |entry|
172
+ if entry.result.value == cached_result.result.value &&
173
+ entry.result.input_type == cached_result.result.input_type
174
+
175
+ return true
176
+ end
177
+ end
178
+ false
179
+ end
180
+
181
+ # Returns first match for the required input.
182
+ #
183
+ # @param rule_id [String] the Protect rule name.
184
+ # @param input [String] the user input.
185
+ # @param input_type [Symbol] Type of the input
186
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
187
+ def query_fetch rule_id, input, input_type
188
+ @cache[rule_id].map do |cached_result|
189
+ return cached_result if cached_result.result.value == input &&
190
+ cached_result.result.input_type == input_type
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,66 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/protect/rule/input_classification/rates'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Protect
9
+ module Rule
10
+ module InputClassification
11
+ # This class will hold match information when input classification is being saved in LRU cache.
12
+ class MatchRates < Rates
13
+ # @return [Integer]
14
+ attr_reader :input_matches
15
+ # @return [Integer]
16
+ attr_reader :input_mismatches
17
+ # @return [Integer]
18
+ attr_reader :request_matches
19
+ # @return [String]
20
+ attr_reader :score_level
21
+
22
+ # @param rule_id [String]
23
+ # @param input_type [Symbol, nil]
24
+ # @param score_level [String]
25
+ def initialize rule_id, input_type, score_level
26
+ super(rule_id, input_type)
27
+ @input_matches = 0
28
+ @request_matches = 0
29
+ @input_mismatches = 0
30
+ @score_level = score_level if Contrast::Agent::Reporting::ScoreLevel.to_a.include?(score_level)
31
+ end
32
+
33
+ # Increase the match count for the given rule_id and input.
34
+ def increase_match_for_input
35
+ @input_matches += 1
36
+ end
37
+
38
+ def increase_mismatch_for_input
39
+ @input_mismatches += 1
40
+ end
41
+
42
+ # increase the missmatch count for the given rule_id and input.
43
+ def increase_match_for_request
44
+ @request_matches += 1
45
+ end
46
+
47
+ def empty?
48
+ super && @input_matches.zero? && @input_mismatches.zero? && @request_matches.zero?
49
+ end
50
+
51
+ # Returns Agent Telemetry reportable fields.
52
+ #
53
+ # @return [Hash]
54
+ def to_fields
55
+ {
56
+ "#{ to_field_title }.input_matches" => input_matches,
57
+ "#{ to_field_title }.input_mismatches" => input_mismatches,
58
+ "#{ to_field_title }.request_matches" => request_matches
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/reporting/input_analysis/input_type'
5
+ require 'contrast/utils/duck_utils'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module Rule
11
+ module InputClassification
12
+ # This class will hold match information when input classification is being saved in LRU cache.
13
+ class Rates
14
+ # Titles:
15
+ FIELD_NAME = 'ia'
16
+ TEST_NAME = '_t'
17
+
18
+ # @return [String]
19
+ attr_reader :rule_id
20
+ # @return [Symbol, nil]
21
+ attr_reader :input_type
22
+
23
+ def initialize rule_id, input_type
24
+ @rule_id = rule_id if rule_id
25
+ @input_type = input_type if Contrast::Agent::Reporting::InputType.to_a.include?(input_type)
26
+ end
27
+
28
+ def empty?
29
+ Contrast::Utils::DuckUtils.empty_duck?(@input_type)
30
+ end
31
+
32
+ # Override this method to return the required Agent Telemetry fields.
33
+ # @return [Hash]
34
+ def to_fields
35
+ {}
36
+ end
37
+
38
+ private
39
+
40
+ # @return [String]
41
+ def to_field_title
42
+ if (ENV['CONTRAST_AGENT_TELEMETRY_TEST'] = '1')
43
+ TEST_NAME
44
+ else
45
+ FIELD_NAME
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/protect/rule/input_classification/utils'
5
+ require 'contrast/agent/protect/rule/input_classification/match_rates'
6
+ require 'contrast/agent/telemetry/input_analysis_cache_event'
7
+ require 'contrast/agent/reporting/input_analysis/score_level'
8
+ require 'contrast/agent/reporting/input_analysis/input_type'
9
+
10
+ module Contrast
11
+ module Agent
12
+ module Protect
13
+ module Rule
14
+ module InputClassification
15
+ # This class will hold match information for each rule when input classification is being saved in LRU cache.
16
+ class Statistics
17
+ include Contrast::Agent::Protect::Rule::InputClassification::Utils
18
+ include Contrast::Components::Logger::InstanceMethods
19
+
20
+ attr_reader :data
21
+
22
+ # Protect rules will always be fixed number, on other hand the number of inputs will grow,
23
+ # we need to limit the number of inputs to be cached.
24
+ CAPACITY = 30
25
+
26
+ def initialize
27
+ @data = {}
28
+ end
29
+
30
+ # This method will handle the statistics for the input match
31
+ #
32
+ # @param rule_id [String] the Protect rule name.
33
+ # @param cached [Contrast::Agent::Protect::InputClassification::CachedResult]
34
+ # @param request [Contrast::Agent::Request] the current request.
35
+ def match! rule_id, cached, request
36
+ return unless Contrast::Agent::Telemetry::Base.enabled?
37
+
38
+ push(rule_id, cached)
39
+ fetch(rule_id, cached.result.input_type)&.increase_match_for_input
40
+ return unless cached.request_id == request.__id__
41
+
42
+ fetch(rule_id, cached.result.input_type)&.increase_match_for_request
43
+ end
44
+
45
+ # This method will handle the statistics for the input mismatch.
46
+ # Skip if this is the called with empty cache since it's not fair.
47
+ #
48
+ # @param rule_id [String] the Protect rule name.
49
+ # @param input_type [Symbol] Type of the input
50
+ def mismatch! rule_id, input_type
51
+ return unless Contrast::Agent::Telemetry::Base.enabled?
52
+ return if Contrast::Agent::Protect::InputAnalyzer.lru_cache.empty?
53
+
54
+ fetch(rule_id, input_type)&.increase_mismatch_for_input
55
+ end
56
+
57
+ # @return [Array<Contrast::Agent::Telemetry::InputAnalysisCacheEvent>] the events to be sent.
58
+ def to_events
59
+ events = []
60
+ data.each do |_rule_id, match_rates|
61
+ match_rates.each do |match_rate|
62
+ event = Contrast::Agent::Telemetry::InputAnalysisCacheEvent.new(match_rate.rule_id, match_rate)
63
+ next if event.empty?
64
+
65
+ events << event
66
+ end
67
+ end
68
+ events
69
+ rescue StandardError => e
70
+ logger.error("[IA_LRU_Cache] Error while creating events: #{ e }", stacktrace: e.backtrace)
71
+ []
72
+ end
73
+
74
+ # Creates new statisctics for protect rule.
75
+ #
76
+ # @param rule_id [String] the Protect rule name.
77
+ # @param cached [Contrast::Agent::Protect::InputClassification::CachedResult]
78
+ def push rule_id, cached
79
+ new_entry = Contrast::Agent::Protect::Rule::InputClassification::MatchRates.
80
+ new(rule_id, cached.result.input_type, cached.result.score_level)
81
+
82
+ @data[rule_id] = [] if Contrast::Utils::DuckUtils.empty_duck?(@data[rule_id])
83
+ @data[rule_id].shift if @data[rule_id].length >= CAPACITY
84
+ @data[rule_id] << new_entry unless saved?(rule_id, cached)
85
+ end
86
+
87
+ # Get the statistics for the protect rule.
88
+ #
89
+ # @param rule_id [String] the Protect rule name.
90
+ # @param input_type [Symbol] Type of the input
91
+ def fetch rule_id, input_type
92
+ safe_extract(@data[rule_id]&.select { |e| e.input_type == input_type })
93
+ end
94
+
95
+ private
96
+
97
+ # Checks if the rate is saved for the input.
98
+ #
99
+ # @param rule_id [String] the Protect rule name.
100
+ # @param new_entry [Contrast::Agent::Protect::InputClassification::CachedResult]
101
+ def saved? rule_id, new_entry
102
+ !fetch(rule_id, new_entry&.result&.input_type).nil?
103
+ end
104
+
105
+ # Call this method from the LRU Cache to get the statistics for a given rule_id, so it could be
106
+ # thread safe.
107
+ def clear
108
+ @data.clear
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Protect
7
+ module Rule
8
+ module InputClassification
9
+ # Utils module for Input Classification.
10
+ module Utils
11
+ # Extracts data return as array from the cache.
12
+ #
13
+ # @param object [NilClass, Array<Contrast::Agent::Protect::InputClassification::CachedResult>]
14
+ # @return object [Contrast::Agent::Protect::InputClassification::CachedResult]
15
+ def safe_extract object
16
+ Array(object)[0]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,7 +4,7 @@
4
4
  require 'contrast/utils/object_share'
5
5
  require 'contrast/agent/protect/rule/no_sqli/no_sqli'
6
6
  require 'contrast/agent/protect/input_analyzer/input_analyzer'
7
- require 'contrast/utils/input_classification_base'
7
+ require 'contrast/agent/protect/rule/input_classification/base'
8
8
 
9
9
  module Contrast
10
10
  module Agent
@@ -15,7 +15,7 @@ module Contrast
15
15
  # to be analyzed at the sink level.
16
16
  module NoSqliInputClassification
17
17
  class << self
18
- include InputClassificationBase
18
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
19
19
 
20
20
  NOSQL_COMMENT_REGEXP = %r{"\s*(?:<--|//)}.cs__freeze
21
21
  NOSQL_OR_REGEXP = /(?=(\s+\|\|\s+))/.cs__freeze
@@ -96,8 +96,15 @@ module Contrast
96
96
  #
97
97
  # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
98
98
  def create_new_input_result request, rule_id, input_type, value
99
- score = evaluate_patterns(value)
100
- score = evaluate_rules(value, score)
99
+ return unless Contrast::AGENT_LIB
100
+
101
+ # Cache retrieve
102
+ cached = Contrast::Agent::Protect::InputAnalyzer.lru_cache.lookout(rule_id, value, input_type, request)
103
+ return cached.result if cached.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
104
+
105
+ eval_value = base64_decode_input(value, input_type)
106
+ score = evaluate_patterns(eval_value)
107
+ score = evaluate_rules(eval_value, score)
101
108
 
102
109
  score_level = if definite_attack?(score)
103
110
  DEFINITEATTACK
@@ -106,9 +113,12 @@ module Contrast
106
113
  else
107
114
  IGNORE
108
115
  end
109
- result = new_ia_result(rule_id, input_type, score_level, request.path, value)
110
- add_needed_key(request, result, input_type, value)
111
- result
116
+ ia_result = new_ia_result(rule_id, input_type, score_level, request.path, value)
117
+ add_needed_key(request, ia_result, input_type, value) if KEYS_NEEDED.include?(input_type)
118
+
119
+ # Cache save. Cache must be saved after the input evaluation is completed.
120
+ Contrast::Agent::Protect::InputAnalyzer.lru_cache.save(rule_id, ia_result, request)
121
+ ia_result
112
122
  end
113
123
 
114
124
  # This method evaluates the patterns relevant to NoSQL Injection to check whether
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/utils/input_classification_base'
4
+ require 'contrast/agent/protect/rule/input_classification/base'
5
5
 
6
6
  module Contrast
7
7
  module Agent
@@ -15,27 +15,31 @@ module Contrast
15
15
 
16
16
  THRESHOLD = 90.cs__freeze
17
17
  class << self
18
- include InputClassificationBase
18
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
19
19
 
20
20
  private
21
21
 
22
- # This methods checks if input is tagged DEFINITEATTACK or IGNORE matches value with it's
23
- # key if needed and Creates new isntance of InputAnalysisResult.
22
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
24
23
  #
25
- # @param request [Contrast::Agent::Request] the current request context.
26
24
  # @param rule_id [String] The name of the Protect Rule.
27
25
  # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
28
26
  # @param value [String, Array<String>] the value of the input.
29
- #
30
- # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
31
- def create_new_input_result request, rule_id, input_type, value
32
- return unless Contrast::AGENT_LIB
33
-
34
- input_eval = Contrast::AGENT_LIB.eval_input(value,
35
- convert_input_type(input_type),
36
- Contrast::AGENT_LIB.rule_set[rule_id],
37
- Contrast::AGENT_LIB.eval_option[:PREFER_WORTH_WATCHING])
27
+ def build_input_eval rule_id, input_type, value
28
+ Contrast::AGENT_LIB.eval_input(value,
29
+ Contrast::Agent::Protect::Rule::InputClassification::Base.
30
+ convert_input_type(input_type),
31
+ Contrast::AGENT_LIB.rule_set[rule_id],
32
+ Contrast::AGENT_LIB.eval_option[:PREFER_WORTH_WATCHING])
33
+ end
38
34
 
35
+ # Creates specific result from the AgentLib evaluation.
36
+ #
37
+ # @param rule_id [String] The name of the Protect Rule.
38
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
39
+ # @param value [String, Array<String>] the value of the input.
40
+ # @param request [Contrast::Agent::Request] the current request context.
41
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
42
+ def build_ia_result rule_id, input_type, value, request, input_eval
39
43
  ia_result = new_ia_result(rule_id, input_type, request.path, value)
40
44
  score = input_eval&.score || 0
41
45
  if score >= THRESHOLD
@@ -50,7 +54,6 @@ module Contrast
50
54
  else
51
55
  ia_result.score_level = IGNORE
52
56
  end
53
- add_needed_key(request, ia_result, input_type, value)
54
57
  ia_result
55
58
  end
56
59
  end
@@ -5,7 +5,7 @@ require 'contrast/utils/object_share'
5
5
  require 'contrast/agent/reporting/input_analysis/input_type'
6
6
  require 'contrast/agent/reporting/input_analysis/score_level'
7
7
  require 'contrast/agent/protect/input_analyzer/input_analyzer'
8
- require 'contrast/utils/input_classification_base'
8
+ require 'contrast/agent/protect/rule/input_classification/base'
9
9
 
10
10
  module Contrast
11
11
  module Agent
@@ -17,7 +17,7 @@ module Contrast
17
17
  module SqliInputClassification
18
18
  WORTHWATCHING_MATCH = 'sqli-worth-watching-v2'.cs__freeze
19
19
  class << self
20
- include InputClassificationBase
20
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
21
21
  include Contrast::Components::Logger::InstanceMethods
22
22
  end
23
23
  end
@@ -4,7 +4,7 @@
4
4
  require 'contrast/utils/object_share'
5
5
  require 'contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload'
6
6
  require 'contrast/agent/protect/input_analyzer/input_analyzer'
7
- require 'contrast/utils/input_classification_base'
7
+ require 'contrast/agent/protect/rule/input_classification/base'
8
8
 
9
9
  module Contrast
10
10
  module Agent
@@ -16,26 +16,30 @@ module Contrast
16
16
  UNSAFE_UPLOAD_MATCH = 'unsafe-file-upload-input-tracing-v1'
17
17
 
18
18
  class << self
19
- include InputClassificationBase
19
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
20
20
 
21
21
  private
22
22
 
23
- # This methods checks if input is tagged DEFINITEATTACK or IGNORE matches value with it's
24
- # key if needed and Creates new isntance of InputAnalysisResult.
23
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
25
24
  #
26
- # @param request [Contrast::Agent::Request] the current request context.
27
25
  # @param rule_id [String] The name of the Protect Rule.
28
- # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
26
+ # @param _input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
29
27
  # @param value [String, Array<String>] the value of the input.
30
- #
31
- # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
32
- def create_new_input_result request, rule_id, input_type, value
33
- return unless Contrast::AGENT_LIB
34
- return unless (input_eval = Contrast::AGENT_LIB.eval_input(value,
35
- Contrast::AGENT_LIB.input_set[:MULTIPART_NAME],
36
- Contrast::AGENT_LIB.rule_set[rule_id],
37
- Contrast::AGENT_LIB.eval_option[:NONE]))
28
+ def build_input_eval rule_id, _input_type, value
29
+ Contrast::AGENT_LIB.eval_input(value,
30
+ Contrast::AGENT_LIB.input_set[:MULTIPART_NAME],
31
+ Contrast::AGENT_LIB.rule_set[rule_id],
32
+ Contrast::AGENT_LIB.eval_option[:NONE])
33
+ end
38
34
 
35
+ # Creates specific result from the AgentLib evaluation.
36
+ #
37
+ # @param rule_id [String] The name of the Protect Rule.
38
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
39
+ # @param value [String, Array<String>] the value of the input.
40
+ # @param request [Contrast::Agent::Request] the current request context.
41
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
42
+ def build_ia_result rule_id, input_type, value, request, input_eval
39
43
  ia_result = new_ia_result(rule_id, input_type, request.path, value)
40
44
  if input_eval.score >= THRESHOLD
41
45
  ia_result.score_level = DEFINITEATTACK
@@ -51,7 +55,6 @@ module Contrast
51
55
  else
52
56
  Contrast::Utils::ObjectShare::EMPTY_STRING
53
57
  end
54
-
55
58
  ia_result
56
59
  end
57
60
  end