contrast-agent 7.1.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/ext/extconf_common.rb +88 -14
  3. data/lib/contrast/agent/assess/policy/source_method.rb +13 -4
  4. data/lib/contrast/agent/assess/policy/trigger_method.rb +12 -18
  5. data/lib/contrast/agent/excluder/excluder.rb +64 -31
  6. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
  7. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
  8. data/lib/contrast/agent/protect/rule/base.rb +9 -7
  9. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +29 -13
  11. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
  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/deserialization/deserialization.rb +2 -2
  15. data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
  16. data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
  17. data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
  18. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
  19. data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
  20. data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
  21. data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
  22. data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
  23. data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
  24. data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
  25. data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
  26. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
  27. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
  28. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +1 -1
  29. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
  30. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +1 -1
  31. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
  32. data/lib/contrast/agent/protect/rule/utils/filters.rb +6 -6
  33. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
  34. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +1 -1
  35. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
  36. data/lib/contrast/agent/reporting/client/interface.rb +132 -0
  37. data/lib/contrast/agent/reporting/client/interface_base.rb +27 -0
  38. data/lib/contrast/agent/reporting/connection_status.rb +0 -1
  39. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
  40. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +17 -4
  41. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
  42. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
  43. data/lib/contrast/agent/reporting/reporter.rb +11 -26
  44. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
  45. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
  46. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +1 -1
  47. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  48. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +47 -6
  49. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +41 -32
  50. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +144 -0
  51. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +35 -13
  52. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_mode.rb +14 -1
  53. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +11 -11
  54. data/lib/contrast/agent/request/request.rb +27 -12
  55. data/lib/contrast/agent/telemetry/base.rb +44 -19
  56. data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
  57. data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
  58. data/lib/contrast/agent/telemetry/client.rb +10 -2
  59. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +97 -0
  60. data/lib/contrast/agent/telemetry/exception.rb +1 -0
  61. data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
  62. data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
  63. data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
  64. data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
  65. data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
  66. data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
  67. data/lib/contrast/agent/version.rb +1 -1
  68. data/lib/contrast/components/config/sources.rb +6 -5
  69. data/lib/contrast/components/config.rb +4 -4
  70. data/lib/contrast/components/protect.rb +11 -1
  71. data/lib/contrast/components/sampling.rb +15 -10
  72. data/lib/contrast/components/settings.rb +9 -0
  73. data/lib/contrast/config/diagnostics/environment_variables.rb +3 -1
  74. data/lib/contrast/config/diagnostics/source_config_value.rb +5 -1
  75. data/lib/contrast/config/diagnostics/tools.rb +4 -4
  76. data/lib/contrast/config/validate.rb +2 -2
  77. data/lib/contrast/config/yaml_file.rb +8 -0
  78. data/lib/contrast/configuration.rb +11 -19
  79. data/lib/contrast/framework/grape/support.rb +1 -2
  80. data/lib/contrast/framework/manager.rb +17 -8
  81. data/lib/contrast/framework/rack/support.rb +99 -1
  82. data/lib/contrast/framework/rails/support.rb +4 -2
  83. data/lib/contrast/framework/sinatra/support.rb +1 -2
  84. data/lib/contrast/logger/aliased_logging.rb +18 -9
  85. data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
  86. data/lib/contrast/utils/hash_utils.rb +21 -2
  87. data/lib/contrast/utils/metrics_hash.rb +1 -1
  88. data/lib/contrast/utils/object_share.rb +2 -1
  89. data/lib/contrast/utils/request_utils.rb +14 -0
  90. data/lib/contrast/utils/response_utils.rb +12 -0
  91. data/lib/contrast/utils/timer.rb +2 -0
  92. data/lib/contrast.rb +9 -2
  93. data/resources/assess/policy.json +11 -0
  94. data/ruby-agent.gemspec +1 -1
  95. metadata +25 -7
  96. data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +0 -27
  97. data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -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
@@ -46,7 +46,7 @@ module Contrast
46
46
  # @raise [Contrast::SecurityException] if the rule mode ise set
47
47
  # to BLOCK and valid cdmi is detected.
48
48
  def infilter context, method, path
49
- return if protect_excluded_by_url?(rule_name, context.request.path)
49
+ return if protect_excluded_by_url?(rule_name)
50
50
  return unless rule_violated?(path)
51
51
 
52
52
  result = build_violation(context, path)
@@ -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
@@ -19,7 +19,7 @@ module Contrast
19
19
  end
20
20
 
21
21
  def infilter context, sql_query
22
- return false if protect_excluded_by_url?(rule_name, context.request.path)
22
+ return false if protect_excluded_by_url?(rule_name)
23
23
  return unless violated?(sql_query)
24
24
 
25
25
  result = build_violation(context, sql_query)
@@ -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
@@ -42,8 +42,8 @@ module Contrast
42
42
  return false unless context
43
43
  return false unless enabled?
44
44
  return false unless (results = gather_ia_results(context)) && results.any?
45
- return false if protect_excluded_by_url?(rule_name, context.request.path)
46
- return false if protect_excluded_by_input?(results, context.request.path)
45
+ return false if protect_excluded_by_url?(rule_name)
46
+ return false if protect_excluded_by_input?(results)
47
47
 
48
48
  true
49
49
  end
@@ -68,8 +68,8 @@ module Contrast
68
68
  def infilter? context
69
69
  return false unless enabled?
70
70
  return false unless (results = gather_ia_results(context)) && results.any?
71
- return false if protect_excluded_by_url?(rule_name, context.request.path)
72
- return false if protect_excluded_by_input?(results, context.request.path)
71
+ return false if protect_excluded_by_url?(rule_name)
72
+ return false if protect_excluded_by_input?(results)
73
73
 
74
74
  true
75
75
  end
@@ -89,8 +89,8 @@ module Contrast
89
89
  # @raise [Contrast::SecurityException]
90
90
  def postfilter context
91
91
  return unless enabled? && POSTFILTER_MODES.include?(mode)
92
- return false if protect_excluded_by_url?(rule_name, context.request.path)
93
- return if protect_excluded_by_input?(gather_ia_results(context), context.request.path)
92
+ return false if protect_excluded_by_url?(rule_name)
93
+ return if protect_excluded_by_input?(gather_ia_results(context))
94
94
 
95
95
  return if mode == :NO_ACTION || mode == :PERMIT
96
96
 
@@ -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
@@ -13,28 +13,32 @@ module Contrast
13
13
  REFLECTED_XSS_MATCH = 'reflected-xss-input-tracing-v1'.cs__freeze
14
14
  WORTHWATCHING_MATCH = 'xss-worth-watching-v2'.cs__freeze
15
15
  class << self
16
- include InputClassificationBase
16
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
17
17
 
18
18
  private
19
19
 
20
- # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
21
- # key if needed and Creates new isntance of InputAnalysisResult.
20
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
22
21
  #
23
- # @param request [Contrast::Agent::Request] the current request context.
24
22
  # @param rule_id [String] The name of the Protect Rule.
25
23
  # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
26
24
  # @param value [String, Array<String>] the value of the input.
27
- #
28
- # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
29
- def create_new_input_result request, rule_id, input_type, value
30
- return unless Contrast::AGENT_LIB
31
-
32
- input_eval = Contrast::AGENT_LIB.eval_input(value,
33
- convert_input_type(input_type),
34
- Contrast::AGENT_LIB.rule_set[rule_id],
35
- Contrast::AGENT_LIB.
36
- eval_option[:PREFER_WORTH_WATCHING])
25
+ def build_input_eval rule_id, input_type, value
26
+ Contrast::AGENT_LIB.eval_input(value,
27
+ Contrast::Agent::Protect::Rule::InputClassification::Base.
28
+ convert_input_type(input_type),
29
+ Contrast::AGENT_LIB.rule_set[rule_id],
30
+ Contrast::AGENT_LIB.
31
+ eval_option[:PREFER_WORTH_WATCHING])
32
+ end
37
33
 
34
+ # Creates specific result from the AgentLib evaluation.
35
+ #
36
+ # @param rule_id [String] The name of the Protect Rule.
37
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
38
+ # @param value [String, Array<String>] the value of the input.
39
+ # @param request [Contrast::Agent::Request] the current request context.
40
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
41
+ def build_ia_result rule_id, input_type, value, request, input_eval
38
42
  score = input_eval&.score || 0
39
43
  ia_result = new_ia_result(rule_id, input_type, request.path, value)
40
44
  if score >= THRESHOLD
@@ -46,8 +50,6 @@ module Contrast
46
50
  else
47
51
  ia_result.score_level = IGNORE
48
52
  end
49
-
50
- add_needed_key(request, ia_result, input_type, value)
51
53
  ia_result
52
54
  end
53
55
  end
@@ -37,7 +37,7 @@ module Contrast
37
37
  # @raise [Contrast::SecurityException] Security exception if an XXE
38
38
  # attack is found and the rule is in block mode.
39
39
  def infilter context, framework, xml
40
- return if protect_excluded_by_url?(rule_name, context.request.path)
40
+ return if protect_excluded_by_url?(rule_name)
41
41
 
42
42
  result = find_attacker(context, xml, framework: framework)
43
43
  return unless result
@@ -5,6 +5,7 @@ require 'contrast/utils/object_share'
5
5
  require 'contrast/utils/timer'
6
6
  require 'contrast/agent/reporting/attack_result/response_type'
7
7
  require 'contrast/agent/reporting/attack_result/rasp_rule_sample'
8
+ require 'contrast/utils/duck_utils'
8
9
 
9
10
  module Contrast
10
11
  module Agent
@@ -65,6 +66,11 @@ module Contrast
65
66
  def details= protect_details
66
67
  @_details = protect_details if protect_details.is_a?(Contrast::Agent::Reporting::Details::ProtectRuleDetails)
67
68
  end
69
+
70
+ def empty?
71
+ Contrast::Utils::DuckUtils.empty_duck?(samples) || Contrast::Utils::DuckUtils.empty_duck?(rule_id) ||
72
+ response == ::Contrast::Agent::Reporting::ResponseType::NO_ACTION
73
+ end
68
74
  end
69
75
  end
70
76
  end
@@ -0,0 +1,132 @@
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/client/interface_base'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ module Client
10
+ # Interface to safely manage connections across threads.
11
+ class Interface < Contrast::Agent::Reporting::Client::InterfaceBase
12
+ # Execute startup routine and
13
+ def startup
14
+ with_monitor { reporter_client.startup!(reporting_connection) }
15
+ end
16
+
17
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
18
+ # @return [Net::HTTPResponse]
19
+ def send_event event
20
+ with_monitor { reporter_client.send_event(event, reporting_connection) }
21
+ end
22
+
23
+ # return [Boolean]
24
+ def sleep?
25
+ with_monitor { reporter_client.sleep? }
26
+ end
27
+
28
+ # Retry once than discard the event. This is trigger on too many events of
29
+ # same kind error.
30
+ #
31
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
32
+ def handle_resend event
33
+ with_monitor do
34
+ reporter_client.handle_resend(event, reporting_connection)
35
+ end
36
+ end
37
+
38
+ # Check to see if client exists and there is connection
39
+ #
40
+ # @return [Boolean
41
+ def connected?
42
+ with_monitor do
43
+ return true if reporter_client && reporting_connection
44
+
45
+ false
46
+ end
47
+ end
48
+
49
+ # Check to see if statup connections are sent or not
50
+ def startup_messages_sent?
51
+ with_monitor { reporter_client.status.startup_messages_sent? }
52
+ end
53
+
54
+ # Check to see if event need to be resend
55
+ #
56
+ # @return [Boolean, nil]
57
+ def resending?
58
+ with_monitor { reporter_client.mode.status == reporter_client.mode.resending }
59
+ end
60
+
61
+ # Return timeout in ms
62
+ def timeout
63
+ with_monitor { reporter_client.timeout } || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
64
+ end
65
+
66
+ # @return headers [Contrast::Agent::Reporting::Headers]
67
+ def headers
68
+ with_monitor { reporter_client.headers }
69
+ end
70
+
71
+ # Response_handler
72
+ #
73
+ # @return [Contrast::Agent::Reporting::ResponseHandler]
74
+ def response_handler
75
+ with_monitor { reporter_client.response_handler }
76
+ end
77
+
78
+ def status
79
+ with_monitor { reporter_client.status }
80
+ end
81
+
82
+ private
83
+
84
+ # @return [Contrast::Agent::Reporting::ReporterClient]
85
+ def reporter_client
86
+ @_reporter_client ||= Contrast::Agent::Reporting::ReporterClient.new
87
+ end
88
+
89
+ # @return [Net::HTTP, nil] Return open connection or nil
90
+ def reporting_connection
91
+ @_reporting_connection ||= reporter_client.initialize_connection
92
+ end
93
+ end
94
+ end
95
+
96
+ module Telemetry
97
+ # Interface to safely manage connections across threads.
98
+ class Interface < Contrast::Agent::Reporting::Client::InterfaceBase
99
+ URL = 'https://telemetry.ruby.contrastsecurity.com/'
100
+
101
+ # Check to see if client exists and there is connection
102
+ #
103
+ # @return [Boolean
104
+ def connected?
105
+ with_monitor do
106
+ return true if telemetry_client && telemetry_connection
107
+
108
+ false
109
+ end
110
+ end
111
+
112
+ # Starts telemetry request.
113
+ def request_with_response event
114
+ with_monitor do
115
+ telemetry_client.handle_response(telemetry_client.send_request(event, telemetry_connection))
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def telemetry_client
122
+ @_telemetry_client ||= Contrast::Agent::Telemetry::Client.new
123
+ end
124
+
125
+ def telemetry_connection
126
+ @_telemetry_connection ||= telemetry_client.initialize_connection(URL)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,27 @@
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 'monitor'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Reporting
9
+ module Client
10
+ # Common methods for Client interface.
11
+ class InterfaceBase
12
+ # Execute calls to connection with thread safety.
13
+ def with_monitor &block
14
+ monitor.synchronize(&block)
15
+ end
16
+
17
+ private
18
+
19
+ # @return [Monitor]
20
+ def monitor
21
+ @_monitor ||= Monitor.new
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -10,7 +10,6 @@ module Contrast
10
10
  @last_success = nil
11
11
  @last_failure = nil
12
12
  @startup_messages_sent = false
13
- @_startup_server_message_sent = false
14
13
  end
15
14
 
16
15
  # Whether we have sent startup message to TeamServer. True after successfully sending startup messages to
@@ -9,13 +9,8 @@ module Contrast
9
9
  module Reporting
10
10
  # This class will do ia analysis for our protect rules
11
11
  class InputAnalysis
12
- def inputs
13
- @_inputs
14
- end
15
-
16
- def inputs= extracted_inputs
17
- @_inputs = extracted_inputs
18
- end
12
+ # @return [Hash] Stored request inputs for this context.
13
+ attr_accessor :inputs
19
14
 
20
15
  def triggered_rules
21
16
  @_triggered_rules ||= []
@@ -4,7 +4,7 @@
4
4
  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
- require 'contrast/agent/reporting/input_analysis/details/protect_rule_details'
7
+ require 'contrast/agent/reporting/details/protect_rule_details'
8
8
 
9
9
  module Contrast
10
10
  module Agent
@@ -61,6 +61,17 @@ module Contrast
61
61
  @_key = key if key.is_a?(String)
62
62
  end
63
63
 
64
+ # @param key_type [Symbol]
65
+ # @return @_key [String]
66
+ def key_type= key_type
67
+ @_key_type = key_type if INPUT_TYPE.to_a.include?(key_type)
68
+ end
69
+
70
+ # @return @_key [String]
71
+ def key_type
72
+ @_key_type ||= INPUT_TYPE::UNDEFINED_TYPE
73
+ end
74
+
64
75
  # @return value [String]
65
76
  def value
66
77
  @_value ||= Contrast::Utils::ObjectShare::EMPTY_STRING
@@ -112,14 +123,16 @@ module Contrast
112
123
 
113
124
  # Additional per rule details containing more specific info.
114
125
  #
115
- # @param protect_rule_details [Contrast::Agent::Reporting::ProtectRuleDetails]
126
+ # @param protect_rule_details [Contrast::Agent::Reporting::Details::ProtectRuleDetails]
116
127
  def details= protect_rule_details
117
- @_details = protect_rule_details if protect_rule_details.is_a?(Contrast::Agent::Reporting::ProtectRuleDetails)
128
+ return unless protect_rule_details.is_a?(Contrast::Agent::Reporting::Details::ProtectRuleDetails)
129
+
130
+ @_details = protect_rule_details
118
131
  end
119
132
 
120
133
  # Additional per rule details containing more specific info.
121
134
  #
122
- # @return [Contrast::Agent::Reporting::ProtectRuleDetails, nil]
135
+ # @return [Contrast::Agent::Reporting::Details::ProtectRuleDetails, nil]
123
136
  def details
124
137
  @_details
125
138
  end
@@ -30,13 +30,45 @@ module Contrast
30
30
  UNKNOWN = :UNKNOWN.cs__freeze
31
31
 
32
32
  class << self
33
+ # @return
33
34
  def to_a
34
- [
35
+ @_to_a ||= [
35
36
  UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME, PARAMETER_VALUE,
36
37
  QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE, MULTIPART_VALUE,
37
38
  MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST, URL_PARAMETER, UNKNOWN
38
39
  ]
39
40
  end
41
+
42
+ # This is a hash of the input types and their corresponding values.
43
+ #
44
+ # @return [Hash]
45
+
46
+ def to_hash
47
+ {
48
+ UNDEFINED_TYPE: '1',
49
+ BODY: '2',
50
+ COOKIE_NAME: '3',
51
+ COOKIE_VALUE: '4',
52
+ HEADER: '5',
53
+ PARAMETER_NAME: '6',
54
+ PARAMETER_VALUE: '7',
55
+ QUERYSTRING: '8',
56
+ URI: '9',
57
+ SOCKET: '10',
58
+ JSON_VALUE: '11',
59
+ JSON_ARRAYED_VALUE: '12',
60
+ MULTIPART_CONTENT_TYPE: '13',
61
+ MULTIPART_VALUE: '14',
62
+ MULTIPART_FIELD_NAME: '15',
63
+ MULTIPART_NAME: '16',
64
+ XML_VALUE: '17',
65
+ DWR_VALUE: '18',
66
+ METHOD: '19',
67
+ REQUEST: '20',
68
+ URL_PARAMETER: '21',
69
+ UNKNOWN: '22'
70
+ }
71
+ end
40
72
  end
41
73
  end
42
74
  end
@@ -23,7 +23,7 @@ module Contrast
23
23
  hash = URI.decode_www_form(query).to_h
24
24
  mask_with_dictionary(results, hash)
25
25
  # Restore to string form.
26
- hash.each { |k, v| masked += "#{ k }=#{ v }&" }
26
+ hash.each { |k, v| masked += "#{ k }#{ EQUALS }#{ v }#{ AMPERSAND }" }
27
27
  query = masked
28
28
  query.chomp!(masked[-1])
29
29
  end