contrast-agent 7.2.0 → 7.3.1

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