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,191 @@
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/object_share'
5
+ require 'contrast/agent/protect/input_analyzer/input_analyzer'
6
+ require 'contrast/agent/protect/rule/input_classification/extendable'
7
+ require 'contrast/agent/protect/rule/input_classification/encoding'
8
+ require 'contrast/components/logger'
9
+
10
+ module Contrast
11
+ module Agent
12
+ module Protect
13
+ module Rule
14
+ module InputClassification
15
+ # This module will include all the similar information for all input classifications
16
+ # between different rules
17
+ module Base
18
+ UNKNOWN_KEY = 'unknown'
19
+ include Contrast::Components::Logger::InstanceMethods
20
+ include Contrast::Agent::Protect::Rule::InputClassification::Extendable
21
+ include Contrast::Agent::Protect::Rule::InputClassification::Encoding
22
+
23
+ KEYS_NEEDED = [
24
+ COOKIE_VALUE, PARAMETER_VALUE, HEADER, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
25
+ ].cs__freeze
26
+
27
+ BASE64_INPUT_TYPES = [BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE].cs__freeze
28
+
29
+ class << self
30
+ include Contrast::Components::Logger::InstanceMethods
31
+ include Contrast::Agent::Reporting::InputType
32
+
33
+ # Finds key value and type based on input type and value.
34
+ # @param request [Contrast::Agent::Request] the current request context.
35
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
36
+ # @param value [String, Array<String>] the value of the input.
37
+ # @return [Array<(String, Contrast::Agent::Reporting::InputType)>] key and key type.
38
+ def find_key request, input_type, value
39
+ # TODO: RUBY-99999 Add handling for multipart, json and if any missing types.
40
+ case input_type
41
+ when COOKIE_VALUE
42
+ [request.cookies.key(value), Contrast::Agent::Reporting::InputType::COOKIE_NAME]
43
+ when PARAMETER_VALUE, URL_PARAMETER
44
+ [request.parameters.key(value), Contrast::Agent::Reporting::InputType::PARAMETER_NAME]
45
+ when HEADER
46
+ [request.headers.key(value), Contrast::Agent::Reporting::InputType::HEADER]
47
+ when UNKNOWN
48
+ [UNKNOWN_KEY, Contrast::Agent::Reporting::InputType::UNKNOWN]
49
+ else
50
+ [nil, nil]
51
+ end
52
+ rescue StandardError => e
53
+ logger.warn('[InputAnalyzer] Could not find proper key for input traced value', message: e)
54
+ [nil, nil]
55
+ end
56
+
57
+ # Some input types are not yet supported from the AgentLib.
58
+ # This will convert the type to the closet possible if viable,
59
+ # so that the input tracing could be done.
60
+ #
61
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
62
+ # @return [Integer<Contrast::AgentLib::Interface::INPUT_SET>]
63
+ def convert_input_type input_type
64
+ case input_type
65
+ when URI, URL_PARAMETER
66
+ Contrast::AGENT_LIB.input_set[:URI_PATH]
67
+ when BODY, DWR_VALUE, SOCKET, UNDEFINED_TYPE, UNKNOWN, REQUEST, QUERYSTRING
68
+ Contrast::AGENT_LIB.input_set[:PARAMETER_VALUE]
69
+ when HEADER
70
+ Contrast::AGENT_LIB.input_set[:HEADER_VALUE]
71
+ when MULTIPART_VALUE, MULTIPART_FIELD_NAME
72
+ Contrast::AGENT_LIB.input_set[:MULTIPART_NAME]
73
+ when JSON_ARRAYED_VALUE
74
+ Contrast::AGENT_LIB.input_set[:JSON_KEY]
75
+ when PARAMETER_NAME
76
+ Contrast::AGENT_LIB.input_set[:PARAMETER_KEY]
77
+ else
78
+ Contrast::AGENT_LIB.input_set[input_type]
79
+ end
80
+ rescue StandardError => e
81
+ logger.debug('[InputAnalyzer] Protect Input classification could not determine input type,
82
+ falling back to default',
83
+ error: e)
84
+ Contrast::AGENT_LIB.input_set[:PARAMETER_VALUE]
85
+ end
86
+ end
87
+
88
+ # Input Classification stage is done to determine if an user input is
89
+ # DEFINITEATTACK or to be ignored.
90
+ #
91
+ # @param rule_id [String] Name of the protect rule.
92
+ # @param input_type [Symbol, Contrast::Agent::Reporting::InputType] The type of the user input.
93
+ # @param value [String, Array<String>] the value of the input.
94
+ # @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Holds all the results from the
95
+ # agent analysis from the current
96
+ # Request.
97
+ # @return ia [Contrast::Agent::Reporting::InputAnalysis, nil] with updated results.
98
+ def classify rule_id, input_type, value, input_analysis
99
+ return unless (rule = Contrast::PROTECT.rule(rule_id))
100
+ return unless rule.applicable_user_inputs.include?(input_type)
101
+ return unless input_analysis.request
102
+
103
+ Array(value).each do |val|
104
+ Array(val).each do |v|
105
+ next unless v
106
+
107
+ result = create_new_input_result(input_analysis.request, rule.rule_name, input_type, v)
108
+ append_result(input_analysis, result)
109
+ end
110
+ end
111
+
112
+ input_analysis
113
+ rescue StandardError => e
114
+ logger.debug("An Error was recorded in the input classification of the #{ rule_id }", error: e)
115
+ nil
116
+ end
117
+
118
+ # This methods checks if input is value that matches a key in the input.
119
+ #
120
+ # @param request [Contrast::Agent::Request] the current request context.
121
+ # @param ia_result [Contrast::Agent::Reporting::InputAnalysisResult] result to be updated.
122
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
123
+ # @param value [String, Array<String>] the value of the input.
124
+ #
125
+ # @return result [Array<String, Symbol>] updated with key result.
126
+ def add_needed_key request, ia_result, input_type, value
127
+ ia_result.key, ia_result.key_type = Contrast::Agent::Protect::Rule::InputClassification::Base.
128
+ find_key(request, input_type, value)
129
+ end
130
+
131
+ private
132
+
133
+ # Appends result to the InputAnalysis.
134
+ #
135
+ # @param ia_analysis [Contrast::Agent::Reporting::InputAnalysis] the current input analysis.
136
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult] result to be appended.
137
+ # @return [Contrast::Agent::Reporting::InputAnalysis] the input analysis with the appended result.
138
+ def append_result ia_analysis, result
139
+ ia_analysis.results << result if result
140
+ ia_analysis
141
+ end
142
+
143
+ # Do not override this method, it will hold base operations, instead overwrite methods called inside
144
+ # of this method.
145
+ # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
146
+ # key if needed and Creates new instance of InputAnalysisResult.
147
+ #
148
+ # @param request [Contrast::Agent::Request] the current request context.
149
+ # @param rule_id [String] The name of the Protect Rule.
150
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
151
+ # @param value [String, Array<String>] the value of the input.
152
+ #
153
+ # @return res [Contrast::Agent::Reporting::InputAnalysisResult, nil]
154
+ def create_new_input_result request, rule_id, input_type, value
155
+ return unless Contrast::AGENT_LIB
156
+
157
+ # Cache retrieve
158
+ cached = Contrast::Agent::Protect::InputAnalyzer.lru_cache.lookout(rule_id, value, input_type, request)
159
+ return cached.result if cached.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
160
+
161
+ # Input evaluation
162
+ input_eval = build_input_eval(rule_id, input_type, base64_decode_input(value, input_type))
163
+ ia_result = build_ia_result(rule_id, input_type, value, request, input_eval)
164
+ return unless ia_result
165
+
166
+ add_needed_key(request, ia_result, input_type, value) if KEYS_NEEDED.include?(input_type)
167
+ # Input evaluation end
168
+
169
+ # Cache save. Cache must be saved after the input evaluation is completed.
170
+ Contrast::Agent::Protect::InputAnalyzer.lru_cache.save(rule_id, ia_result, request)
171
+ ia_result
172
+ end
173
+
174
+ # Decodes the value for the given input type.
175
+ # Applies to BODY, COOKIE_VALUE, HEADER, PARAMETER_VALUE, MULTIPART_VALUE, XML_VALUE
176
+ #
177
+ # @param value [String]
178
+ # @param input_type [Symbol]
179
+ # @return input [String]
180
+ def base64_decode_input value, input_type
181
+ return value unless Contrast::PROTECT.normalize_base64?
182
+ return value unless BASE64_INPUT_TYPES.include?(input_type)
183
+
184
+ cs__decode64(value, input_type)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,71 @@
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/encoding_rates'
5
+ require 'contrast/agent/telemetry/input_analysis_encoding_event'
6
+ require 'contrast/components/logger'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Protect
11
+ module Rule
12
+ module InputClassification
13
+ # This class will safe all the information for the Base64 decoding matches per input type.
14
+ class Base64Statistic
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
17
+ # @return [Hash<Contrast::Agent::Protect::Rule::InputClassification::EncodingRates>]
18
+ attr_reader :data
19
+
20
+ # Capacity for request context life cycle.
21
+ CAPACITY = 1000
22
+
23
+ def initialize
24
+ @data = {}
25
+ end
26
+
27
+ # Add a match for the given input type.
28
+ #
29
+ # @param input_type [Symbol]
30
+ def match! input_type
31
+ return @data[input_type]&.increase_match_base64 if @data[input_type]
32
+
33
+ @data[input_type] = Contrast::Agent::Protect::Rule::InputClassification::EncodingRates.new(input_type)
34
+ @data[input_type].increase_match_base64
35
+ end
36
+
37
+ # Add a mismatch for the given input type.
38
+ #
39
+ # @param input_type [Symbol]
40
+ def mismatch! input_type
41
+ return @data[input_type]&.increase_mismatch_base64 if @data[input_type]
42
+
43
+ @data[input_type] = Contrast::Agent::Protect::Rule::InputClassification::EncodingRates.new(input_type)
44
+ @data[input_type].increase_mismatch_base64
45
+ end
46
+
47
+ # Clears statistic data.
48
+ def clear
49
+ @data.clear
50
+ end
51
+
52
+ # @return [Array<Contrast::Agent::Telemetry::InputAnalysisCacheEvent>] the events to be sent.
53
+ def to_events
54
+ events = []
55
+ data.each do |_input_type, encoding_rate|
56
+ event = Contrast::Agent::Telemetry::InputAnalysisEncodingEvent.new(nil, encoding_rate)
57
+ next if event.empty?
58
+
59
+ events << event
60
+ end
61
+ events
62
+ rescue StandardError => e
63
+ logger.error("[Telemetry] Error while creating events: #{ e }", stacktrace: e.backtrace)
64
+ []
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,37 @@
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/duck_utils'
5
+ require 'contrast/agent/reporting/input_analysis/input_analysis_result'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module InputClassification
11
+ # This Class with store the input classification results for a given user input.
12
+ class CachedResult
13
+ # @return [String]
14
+ attr_reader :result
15
+ # @return [Integer]
16
+ attr_reader :request_id
17
+
18
+ # Initialize Input Classification Cached Result
19
+ #
20
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
21
+ # @param request_id [Integer] the id of current request.
22
+ def initialize result, request_id
23
+ @result = result.dup if result&.cs__is_a?(Contrast::Agent::Reporting::InputAnalysisResult)
24
+ @request_id = request_id
25
+ end
26
+
27
+ # Check if the input classification result is empty.
28
+ #
29
+ # @return [Boolean]
30
+ def empty?
31
+ Contrast::Utils::DuckUtils.empty_duck?(@result) && Contrast::Utils::DuckUtils.empty_duck?(@request_id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,109 @@
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 'base64'
5
+ require 'cgi'
6
+ require 'uri'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Protect
11
+ module Rule
12
+ module InputClassification
13
+ # Module to hold different encoding utils.
14
+ module Encoding
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
17
+ # Still a list is needed for this one, as it is not possible to determine if the value is encoded or not.
18
+ # As long as the list is short the method has a good percentage of success.
19
+ KNOWN_DECODING_EXCEPTIONS = %w[cmd].cs__freeze
20
+
21
+ # This methods is not performant, but is more safe for false positive.
22
+ # Base64 check is no trivial task. For example if one passes a value like 'stringdw' it will return true,
23
+ # or value 'pass', but it is indeed not encoded. using regexp like:
24
+ #
25
+ # ^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$
26
+ #
27
+ # This will fail with any inputs from above, and this is because any characters with 4 bytes will be
28
+ # considered as base64 encoded, and without additional context it is impossible to determine if the
29
+ # value is encoded or not. Not to mention the above regexp will not detect empty spaces.
30
+ #
31
+ # Alternative to the above regexp, acting the same way, could be this:
32
+ #
33
+ # Base64.strict_encode64(Base64.decode64(value)) == value
34
+ #
35
+ # Making an exception list is not a good idea, because it will be hard to maintain.
36
+ #
37
+ # The Base64 method will return printable ascii characters, so we can use this to determine if the value is
38
+ # encoded or not.
39
+ #
40
+ # The solution in this case is encodind the value, and then decoding it. If the value is already encoded
41
+ # it will not be eq to the original value. If the value is not encoded, it will be eq to the original value.
42
+ #
43
+ # @param value [String] input to check for encoding status.
44
+ # @param input_type [Symbol] input type.
45
+ # @return [Boolean] true if value is base64 encoded, false otherwise.
46
+ def cs__base64? value, input_type
47
+ return false unless value.is_a?(String)
48
+ return false if Contrast::Utils::DuckUtils.empty_duck?(value)
49
+
50
+ # Encoded string levels of decoding example:
51
+ #
52
+ # Value encoded 'pass' => 'cGFzcw=='
53
+ # decode level 0 => 'pass'
54
+ # decode level 1 => '\xA5\xAB,'
55
+ # decode level 2 => ''
56
+ check_value = value.dup
57
+ return false if KNOWN_DECODING_EXCEPTIONS.include?(check_value)
58
+
59
+ level = 0
60
+ iteration = 0
61
+ until Contrast::Utils::DuckUtils.empty_duck?(Base64.decode64(check_value))
62
+ iteration += 1
63
+ # handle cases like 'command' or 'injection' which will check out as encoded regarding the level of
64
+ # decoding, but will produce ascii escape characters on first iteration, rather than decoded value.
65
+ level += 1 unless iteration == 2 && ::CGI.escape(check_value) != check_value
66
+
67
+ check_value = Base64.decode64(check_value)
68
+ end
69
+
70
+ # if we have more than 2 levels the value is encoded.
71
+ base64 = level > 1
72
+
73
+ # Call base64 statistics:
74
+ if base64
75
+ Contrast::Agent::Protect::InputAnalyzer.base64_statistic.match!(input_type)
76
+ else
77
+ Contrast::Agent::Protect::InputAnalyzer.base64_statistic.mismatch!(input_type)
78
+ end
79
+
80
+ base64
81
+ rescue StandardError => e
82
+ logger.error('Error while checking for base64 encoding',
83
+ error: e,
84
+ message: e.message,
85
+ backtrace: e.backtrace)
86
+ false
87
+ end
88
+
89
+ # This method will decode the value using Base64.decode64, only if value was encoded.
90
+ # If value is not encoded, it will return the original value.
91
+ #
92
+ # @param value [String] input to decode.
93
+ # @param input_type [Symbol] input type.
94
+ # @return [String] decoded or original value.
95
+ # @raise [StandardError]
96
+ def cs__decode64 value, input_type
97
+ return value unless cs__base64?(value, input_type)
98
+
99
+ Base64.decode64(value)
100
+ rescue StandardError => e
101
+ logger.error('Error while decoding base64', error: e, message: e.message, backtrace: e.backtrace)
102
+ value
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,47 @@
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 EncodingRates < Contrast::Agent::Protect::Rule::InputClassification::Rates
13
+ # @return [Integer]
14
+ attr_reader :base64_matches
15
+ # @return [Integer]
16
+ attr_reader :base64_mismatches
17
+
18
+ def initialize input_type
19
+ super(nil, input_type)
20
+ @base64_matches = 0
21
+ @base64_mismatches = 0
22
+ end
23
+
24
+ # Increase the match count for the given rule_id and input.
25
+ def increase_match_base64
26
+ @base64_matches += 1
27
+ end
28
+
29
+ def increase_mismatch_base64
30
+ @base64_mismatches += 1
31
+ end
32
+
33
+ # Returns Agent Telemetry reportable fields.
34
+ #
35
+ # @return [Hash]
36
+ def to_fields
37
+ {
38
+ "#{ to_field_title }.input_base64_matches" => base64_matches,
39
+ "#{ to_field_title }.input_base64_mismatches" => base64_mismatches
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,80 @@
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/agent/reporting/input_analysis/score_level'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module Rule
11
+ module InputClassification
12
+ # Module holding the overwritable methods for input classification. This is used by the
13
+ # Protect rules to define their own input classification logic. To be Used input_types,
14
+ # score_level, AgentLib, and InputAnalysisResult must be required.
15
+ module Extendable
16
+ THRESHOLD = 90.cs__freeze
17
+ WORTHWATCHING_THRESHOLD = 10.cs__freeze
18
+ include Contrast::Agent::Reporting::InputType
19
+ include Contrast::Agent::Reporting::ScoreLevel
20
+
21
+ ################################################################
22
+ # Methods to be overwritten for each individual Protect rule. #
23
+ ##############################################################
24
+
25
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
26
+ #
27
+ # @param rule_id [String] The name of the Protect Rule.
28
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
29
+ # @param value [String] the value of the input.
30
+ # @return [Contrast::AgentLib::EvalResult, nil] the result of the input evaluation.
31
+ def build_input_eval rule_id, input_type, value
32
+ Contrast::AGENT_LIB.eval_input(value,
33
+ Contrast::Agent::Protect::Rule::InputClassification::Base.
34
+ convert_input_type(input_type),
35
+ Contrast::AGENT_LIB.rule_set[rule_id],
36
+ Contrast::AGENT_LIB.eval_option[:PREFER_WORTH_WATCHING])
37
+ end
38
+
39
+ # Creates specific result from the AgentLib evaluation.
40
+ #
41
+ # @param rule_id [String] The name of the Protect Rule.
42
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
43
+ # @param value [String the value of the input.
44
+ # @param request [Contrast::Agent::Request] the current request context.
45
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
46
+ # @return [Contrast::Agent::Reporting::InputAnalysisResult, nil] the result of the input analysis.
47
+ def build_ia_result rule_id, input_type, value, request, input_eval
48
+ ia_result = new_ia_result(rule_id, input_type, request.path, value)
49
+ score = input_eval&.score || 0
50
+ if score >= WORTHWATCHING_THRESHOLD
51
+ ia_result.score_level = WORTHWATCHING
52
+ ia_result.ids << self::WORTHWATCHING_MATCH
53
+ else
54
+ ia_result.score_level = IGNORE
55
+ end
56
+ ia_result
57
+ end
58
+
59
+ # Creates new isntance of InputAnalysisResult with basic info.
60
+ #
61
+ # @param rule_id [String] The name of the Protect Rule.
62
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
63
+ # @param value [String, Array<String>] the value of the input.
64
+ # @param path [String] the path of the current request context.
65
+ #
66
+ # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
67
+ def new_ia_result rule_id, input_type, path, value = nil
68
+ res = Contrast::Agent::Reporting::InputAnalysisResult.new
69
+ res.rule_id = rule_id
70
+ res.input_type = input_type
71
+ res.path = path
72
+ res.value = value
73
+ res
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end