contrast-agent 5.1.0 → 5.2.0

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_kernel/cs__assess_kernel.c +7 -4
  3. data/ext/cs__assess_module/cs__assess_module.c +7 -7
  4. data/ext/cs__common/cs__common.c +4 -0
  5. data/ext/cs__common/cs__common.h +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +52 -27
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +2 -0
  8. data/ext/cs__scope/cs__scope.c +747 -0
  9. data/ext/cs__scope/cs__scope.h +88 -0
  10. data/ext/cs__scope/extconf.rb +5 -0
  11. data/lib/contrast/agent/assess/contrast_event.rb +20 -13
  12. data/lib/contrast/agent/assess/contrast_object.rb +4 -1
  13. data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -5
  14. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +4 -1
  16. data/lib/contrast/agent/assess/rule/response/{autocomplete_rule.rb → auto_complete_rule.rb} +4 -3
  17. data/lib/contrast/agent/assess/rule/response/base_rule.rb +12 -79
  18. data/lib/contrast/agent/assess/rule/response/body_rule.rb +109 -0
  19. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +157 -0
  20. data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +26 -0
  21. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +14 -15
  22. data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +5 -25
  23. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +29 -0
  24. data/lib/contrast/agent/assess/rule/response/header_rule.rb +70 -0
  25. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +12 -36
  26. data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +26 -0
  28. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +36 -0
  29. data/lib/contrast/agent/middleware.rb +1 -0
  30. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +1 -3
  31. data/lib/contrast/agent/patching/policy/patch.rb +2 -6
  32. data/lib/contrast/agent/patching/policy/patcher.rb +1 -1
  33. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +94 -0
  34. data/lib/contrast/agent/protect/rule/base.rb +28 -1
  35. data/lib/contrast/agent/protect/rule/base_service.rb +10 -1
  36. data/lib/contrast/agent/protect/rule/cmd_injection.rb +2 -0
  37. data/lib/contrast/agent/protect/rule/deserialization.rb +6 -0
  38. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +5 -1
  39. data/lib/contrast/agent/protect/rule/no_sqli.rb +1 -0
  40. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -0
  41. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +124 -0
  42. data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +121 -0
  43. data/lib/contrast/agent/protect/rule/sqli.rb +33 -0
  44. data/lib/contrast/agent/protect/rule/xxe.rb +4 -0
  45. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +44 -0
  46. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +115 -0
  47. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +44 -0
  48. data/lib/contrast/agent/reporting/input_analysis/score_level.rb +21 -0
  49. data/lib/contrast/agent/reporting/report.rb +1 -0
  50. data/lib/contrast/agent/reporting/reporter.rb +8 -1
  51. data/lib/contrast/agent/reporting/reporting_events/finding.rb +69 -36
  52. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +88 -59
  53. data/lib/contrast/agent/reporting/reporting_events/{finding_object.rb → finding_event_object.rb} +24 -20
  54. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +39 -0
  55. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +40 -0
  56. data/lib/contrast/agent/reporting/reporting_events/{finding_signature.rb → finding_event_signature.rb} +29 -24
  57. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +12 -8
  58. data/lib/contrast/agent/reporting/reporting_events/{finding_stack.rb → finding_event_stack.rb} +23 -19
  59. data/lib/contrast/agent/reporting/reporting_events/{finding_taint_range.rb → finding_event_taint_range.rb} +17 -15
  60. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +26 -53
  61. data/lib/contrast/agent/reporting/reporting_events/poll.rb +29 -0
  62. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +5 -4
  63. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +1 -0
  64. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +1 -1
  65. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  66. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +0 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -0
  68. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +28 -20
  69. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +1 -1
  70. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +13 -1
  71. data/lib/contrast/agent/request_context.rb +6 -1
  72. data/lib/contrast/agent/request_context_extend.rb +85 -21
  73. data/lib/contrast/agent/scope.rb +102 -107
  74. data/lib/contrast/agent/service_heartbeat.rb +45 -2
  75. data/lib/contrast/agent/version.rb +1 -1
  76. data/lib/contrast/api/decorators/bot_blocker.rb +37 -0
  77. data/lib/contrast/api/decorators/ip_denylist.rb +37 -0
  78. data/lib/contrast/api/decorators/rasp_rule_sample.rb +29 -0
  79. data/lib/contrast/api/decorators/user_input.rb +11 -1
  80. data/lib/contrast/api/decorators/virtual_patch.rb +34 -0
  81. data/lib/contrast/components/logger.rb +5 -0
  82. data/lib/contrast/components/protect.rb +4 -2
  83. data/lib/contrast/components/scope.rb +98 -91
  84. data/lib/contrast/config/agent_configuration.rb +58 -12
  85. data/lib/contrast/config/api_configuration.rb +100 -12
  86. data/lib/contrast/config/api_proxy_configuration.rb +55 -3
  87. data/lib/contrast/config/application_configuration.rb +114 -15
  88. data/lib/contrast/config/assess_configuration.rb +106 -12
  89. data/lib/contrast/config/assess_rules_configuration.rb +44 -3
  90. data/lib/contrast/config/base_configuration.rb +1 -0
  91. data/lib/contrast/config/certification_configuration.rb +74 -3
  92. data/lib/contrast/config/exception_configuration.rb +61 -3
  93. data/lib/contrast/config/heap_dump_configuration.rb +101 -17
  94. data/lib/contrast/config/inventory_configuration.rb +64 -3
  95. data/lib/contrast/config/logger_configuration.rb +46 -3
  96. data/lib/contrast/config/protect_rule_configuration.rb +36 -9
  97. data/lib/contrast/config/protect_rules_configuration.rb +120 -17
  98. data/lib/contrast/config/request_audit_configuration.rb +68 -3
  99. data/lib/contrast/config/ruby_configuration.rb +96 -22
  100. data/lib/contrast/config/sampling_configuration.rb +76 -10
  101. data/lib/contrast/config/server_configuration.rb +56 -11
  102. data/lib/contrast/configuration.rb +6 -3
  103. data/lib/contrast/logger/cef_log.rb +151 -0
  104. data/lib/contrast/utils/hash_digest.rb +14 -6
  105. data/lib/contrast/utils/log_utils.rb +114 -0
  106. data/lib/contrast/utils/middleware_utils.rb +6 -7
  107. data/lib/contrast/utils/net_http_base.rb +12 -9
  108. data/lib/contrast/utils/patching/policy/patch_utils.rb +0 -4
  109. data/lib/contrast.rb +4 -3
  110. data/ruby-agent.gemspec +1 -1
  111. data/service_executables/VERSION +1 -1
  112. data/service_executables/linux/contrast-service +0 -0
  113. data/service_executables/mac/contrast-service +0 -0
  114. metadata +41 -21
  115. data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +0 -184
  116. data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +0 -66
  117. data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +0 -52
  118. data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +0 -53
  119. data/lib/contrast/extension/kernel.rb +0 -54
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/protect/rule/base'
5
+ require 'contrast/components/logger'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -11,6 +12,8 @@ module Contrast
11
12
  # Deserialization Protect rule.
12
13
  class Deserialization < Contrast::Agent::Protect::Rule::Base
13
14
  # The TeamServer recognized name of this rule
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
14
17
  NAME = 'untrusted-deserialization'
15
18
 
16
19
  # The rule specific reason for raising a security exception.
@@ -78,6 +81,8 @@ module Contrast
78
81
  result = build_attack_with_match(context, ia_result, nil, serialized_input, **kwargs)
79
82
  append_to_activity(context, result)
80
83
 
84
+ cef_logging result, :successful_attack
85
+
81
86
  raise Contrast::SecurityException.new(self, block_message) if blocked?
82
87
  end
83
88
 
@@ -97,6 +102,7 @@ module Contrast
97
102
  ia_result = build_evaluation(gadget_command)
98
103
  result = build_attack_with_match(context, ia_result, nil, gadget_command, **kwargs)
99
104
  append_to_activity(context, result)
105
+ cef_logging result, :successful_attack, gadget_command
100
106
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked?
101
107
  end
102
108
 
@@ -34,7 +34,11 @@ module Contrast
34
34
  else
35
35
  build_attack_with_match(context, nil, nil, nil, method: method, response_code: response_code)
36
36
  end
37
- append_to_activity(context, result) if result
37
+
38
+ return unless result
39
+
40
+ append_to_activity(context, result)
41
+ cef_logging result, :ineffective_attack
38
42
  end
39
43
 
40
44
  protected
@@ -35,6 +35,7 @@ module Contrast
35
35
 
36
36
  append_to_activity(context, result)
37
37
 
38
+ cef_logging result, :successful_attack
38
39
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked?
39
40
  end
40
41
 
@@ -38,6 +38,7 @@ module Contrast
38
38
  append_to_activity(context, result)
39
39
  return unless blocked?
40
40
 
41
+ cef_logging result, :successful_attack, path
41
42
  raise Contrast::SecurityException.new(self,
42
43
  "Path Traversal rule triggered. Call to File.#{ method } blocked.")
43
44
  end
@@ -0,0 +1,124 @@
1
+ # Copyright (c) 2022 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/reporting/input_analysis/input_type'
6
+ require 'contrast/agent/protect/rule/sqli'
7
+ require 'contrast/agent/reporting/input_analysis/score_level'
8
+ require 'contrast/agent/protect/rule/sqli/sqli_worth_watching'
9
+ require 'contrast/agent/protect/input_analyzer/input_analyzer'
10
+
11
+ module Contrast
12
+ module Agent
13
+ module Protect
14
+ module Rule
15
+ # This module will do the Input Classification stage of SQLI rule
16
+ # as a result input would be marked as WORTHWATCHING or IGNORE,
17
+ # to be analyzed at the sink level.
18
+ module SqliInputClassification
19
+ class << self
20
+ include Contrast::Agent::Reporting::InputType
21
+ include Contrast::Agent::Reporting::ScoreLevel
22
+ include Contrast::Agent::Protect::Rule::SqliWorthWatching
23
+
24
+ WORTHWATCHING_MATCH = 'sqli-worth-watching-v2'
25
+ SQLI_KEYS_NEEDED = [
26
+ COOKIE_VALUE, PARAMETER_VALUE, JSON_VALUE, MULTIPART_VALUE, XML_VALUE, DWR_VALUE
27
+ ].cs__freeze
28
+
29
+ # Input Classification stage is done to determine if an user input is
30
+ # WORTHWATCHING or to be ignored.
31
+ #
32
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
33
+ # @param value [String, Array<String>] the value of the input.
34
+ # @param input_analysis [Contrast::Agent::Reporting::InputAnalysis] Holds all the results from the
35
+ # agent analysis from the current
36
+ # Request.
37
+ # @return ia [Contrast::Agent::Reporting::InputAnalysis] with updated results.
38
+ def classify input_type, value, input_analysis
39
+ return unless Contrast::Agent::Protect::Rule::Sqli::APPLICABLE_USER_INPUTS.include?(input_type)
40
+ return unless input_analysis.request
41
+
42
+ rule_id = Contrast::Agent::Protect::Rule::Sqli::NAME
43
+ results = []
44
+
45
+ # double check the input to avoid calling match? on array
46
+ Array(value).each do |val|
47
+ Array(val).each do |v|
48
+ results << sqli_create_new_input_result(input_analysis.request, rule_id, input_type, v)
49
+ end
50
+ end
51
+
52
+ input_analysis.results = results
53
+ input_analysis
54
+ end
55
+
56
+ private
57
+
58
+ # Creates new isntance of InputAnalysisResult with basic info.
59
+ #
60
+ # @param rule_id [String] The name of the Protect Rule.
61
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
62
+ # @param score_level [Contrast::Agent::Reporting::ScoreLevel] the score tag after analysis.
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, score_level, path, value
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.score_level = score_level
73
+ res.value = value
74
+ res
75
+ end
76
+
77
+ # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
78
+ # key if needed and Creates new isntance of InputAnalysisResult.
79
+ #
80
+ # @param request [Contrast::Agent::Request] the current request context.
81
+ # @param rule_id [String] The name of the Protect Rule.
82
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
83
+ # @param value [String, Array<String>] the value of the input.
84
+ #
85
+ # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
86
+ def sqli_create_new_input_result request, rule_id, input_type, value
87
+ result = if sqli_worth_watching? value
88
+ ia_result = new_ia_result(rule_id, input_type, WORTHWATCHING, request.path, value)
89
+ ia_result.ids << WORTHWATCHING_MATCH
90
+ ia_result
91
+ else
92
+ new_ia_result(rule_id, input_type, IGNORE, request.path, value)
93
+ end
94
+ if SQLI_KEYS_NEEDED.include? input_type
95
+ result.key = sqli_add_needed_key request, result, input_type, value
96
+ end
97
+ result
98
+ end
99
+
100
+ # This methods checks if input is value that matches a key in the input.
101
+ #
102
+ # @param request [Contrast::Agent::Request] the current request context.
103
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult] result to be updated.
104
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
105
+ # @param value [String, Array<String>] the value of the input.
106
+ #
107
+ # @return result [Contrast::Agent::Reporting::InputAnalysisResult] updated with key result.
108
+ def sqli_add_needed_key request, result, input_type, value
109
+ case input_type
110
+ when COOKIE_VALUE
111
+ result.key = request.cookies.key(value)
112
+ when PARAMETER_VALUE
113
+ result.key = request.parameters.key(value)
114
+ else
115
+ result.key
116
+ end
117
+ result.key
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,121 @@
1
+ # Copyright (c) 2022 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
+
6
+ module Contrast
7
+ module Agent
8
+ module Protect
9
+ module Rule
10
+ # This module implements the sqli-worth-watching-v2 check to determine whether input
11
+ # is IGNORED or WORTH_WATCHING. If WORTH_WATCHING => analyze at sink level.
12
+ # https://protect-spec.prod.dotnet.contsec.com/rules/sql-injection.html#input-tracing
13
+ module SqliWorthWatching
14
+ COLOR_CODE = /^#[0-9A-Fa-f]{6}$/.cs__freeze
15
+ ALPHA_NUMERIC_AND_SPACES = /^+[a-zA-Z\d\s]+$/.cs__freeze
16
+ OR_CLAUSE = /[oO][rR]/.cs__freeze
17
+ EXPLOITABLE_SUBSTRING = %w[# -- // /*].cs__freeze
18
+ SUSPICIOUS_CHARS = %w[` " \' ; - % , ( ) | { } =].cs__freeze
19
+ SQL_COMMENTS = %w[# /* */ // -- @@].cs__freeze
20
+ BLOCK_START = '/*'.cs__freeze
21
+ BLOCK_END = '*/'.cs__freeze
22
+ SQL_KEYWORDS = %w[
23
+ alter begin between create case column_name
24
+ current_user delete drop exec execute from
25
+ group insert limit like merge order outfile
26
+ select session_user syslogins update union
27
+ UTL_INADDR UTL_HTTP
28
+ ].cs__freeze
29
+
30
+ # This method will determine if a user input is Worth watching and return true if it is.
31
+ # This is done by running checks, and if the inputs is worth to watch it would be
32
+ # saved for the later sink sqli input analysis.
33
+ #
34
+ # @param input [String] the user input to be inspected
35
+ # @return true | false
36
+ def sqli_worth_watching? input
37
+ return false if input.nil? || input.empty?
38
+
39
+ exploitable?(input) && (
40
+ input.match?(OR_CLAUSE) || sql_comments?(input) || suspicious_chars?(input) || language_keywords?(input)
41
+ )
42
+ end
43
+
44
+ private
45
+
46
+ # Check if input is exploitable, with min length set to 3 chars
47
+ #
48
+ # @param input [String] the user input to be inspected
49
+ # @return true | false
50
+ def exploitable? input
51
+ return false if input.length < 3 && input.match?(ALPHA_NUMERIC_AND_SPACES)
52
+ return false if input.length == 3 && !contains_substring?(input, EXPLOITABLE_SUBSTRING)
53
+ return false if input.length == 7 && input.match?(COLOR_CODE)
54
+
55
+ true
56
+ end
57
+
58
+ # Check if input contains sqli comments:
59
+ # '# /* */ // -- @@'
60
+ #
61
+ # @param input [String] the user input to be inspected
62
+ # @return true | false
63
+ def sql_comments? input
64
+ input.length >= 3 && contains_substring?(input, SQL_COMMENTS)
65
+ end
66
+
67
+ # Check if input contains block comments starting and
68
+ # ending with '/*..*/'
69
+ #
70
+ # @param input [String] the user input to be inspected
71
+ # @return true | false
72
+ def block_comments? input
73
+ idx1 = input.index(BLOCK_START)
74
+ idx2 = input.index(BLOCK_END)
75
+
76
+ return false if idx1.nil? || idx2.nil?
77
+
78
+ (idx1 >= 0 && idx2 >= 2 && (idx1 < idx2))
79
+ end
80
+
81
+ # Runs the input against suspicious chars array.
82
+ #
83
+ # @param input [String] the user input to be inspected
84
+ # @return true | false
85
+ def suspicious_chars? input
86
+ input.length >= 7 && (number_of_substrings(input, SUSPICIOUS_CHARS) >= 2 || block_comments?(input))
87
+ end
88
+
89
+ # Runs the input against SQL language preserved words.
90
+ #
91
+ # @param input [String] the user input to be inspected
92
+ # @return true | false
93
+ def language_keywords? input
94
+ contains_substring? input, SQL_KEYWORDS
95
+ end
96
+
97
+ # Helper method to find a substrings in given input.
98
+ #
99
+ # @param substrings [Array] set of substrings to inspect.
100
+ # @return true | false
101
+ def contains_substring? input, substrings
102
+ return true if substrings.any? { |sub| input.include?(sub) }
103
+
104
+ false
105
+ end
106
+
107
+ # Helper method to find the number of substrings.
108
+ #
109
+ # @param input [String] the user input to be inspected
110
+ # @return number [Integer] Number of substrings
111
+ def number_of_substrings input, substring
112
+ number = 0
113
+ input.each_char.reduce(0) { |_acc, elem| number += 1 if contains_substring?(elem, substring) }
114
+
115
+ number
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -4,6 +4,7 @@
4
4
  require 'contrast/agent/protect/rule/base_service'
5
5
  require 'contrast/agent/protect/policy/applies_sqli_rule'
6
6
  require 'contrast/agent/protect/rule/sql_sample_builder'
7
+ require 'contrast/agent/reporting/input_analysis/input_type'
7
8
 
8
9
  module Contrast
9
10
  module Agent
@@ -16,7 +17,17 @@ module Contrast
16
17
  include SqlSampleBuilder::SqliSample
17
18
  # Defining build_attack_with_match method
18
19
  include SqlSampleBuilder::AttackBuilder
20
+ include Contrast::Agent::Reporting::InputType
21
+ class << self
22
+ include Contrast::Agent::Reporting::InputType
23
+ end
19
24
 
25
+ APPLICABLE_USER_INPUTS = [
26
+ BODY, COOKIE_NAME, COOKIE_VALUE, HEADER,
27
+ PARAMETER_NAME, PARAMETER_VALUE, JSON_VALUE,
28
+ MULTIPART_VALUE, MULTIPART_FIELD_NAME,
29
+ XML_VALUE, DWR_VALUE
30
+ ].cs__freeze
20
31
  NAME = 'sql-injection'
21
32
  BLOCK_MESSAGE = 'SQLi rule triggered. Response blocked.'
22
33
 
@@ -36,8 +47,30 @@ module Contrast
36
47
 
37
48
  append_to_activity(context, result)
38
49
 
50
+ cef_logging result, :successful_attack, query_string
39
51
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE) if blocked?
40
52
  end
53
+
54
+ private
55
+
56
+ def find_attacker context, potential_attack_string, **kwargs
57
+ ia_results = gather_ia_results(context)
58
+ find_attacker_with_results(context, potential_attack_string, ia_results, **kwargs)
59
+ end
60
+
61
+ def infilter? context
62
+ return false unless context&.agent_input_analysis&.results
63
+ return false unless enabled?
64
+ return false if protect_excluded_by_code?
65
+
66
+ true
67
+ end
68
+
69
+ def gather_ia_results context
70
+ context.agent_input_analysis.results.select do |ia_result|
71
+ ia_result.rule_id == rule_name
72
+ end
73
+ end
41
74
  end
42
75
  end
43
76
  end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/protect/rule/base'
5
5
  require 'contrast/utils/timer'
6
+ require 'contrast/components/logger'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -11,6 +12,8 @@ module Contrast
11
12
  # Implementation of the XXE Protect Rule used to evaluate XML calls for exploit
12
13
  # of unsafe external entity resolution.
13
14
  class Xxe < Contrast::Agent::Protect::Rule::Base
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
14
17
  NAME = 'xxe'
15
18
  BLOCK_MESSAGE = 'XXE rule triggered. Response blocked.'
16
19
  EXTERNAL_ENTITY_PATTERN = /<!ENTITY\s+[a-zA-Z0-f]+\s+(?:SYSTEM|PUBLIC)\s+(.*?)>/.cs__freeze
@@ -36,6 +39,7 @@ module Contrast
36
39
  append_to_activity(context, result)
37
40
  return unless blocked?
38
41
 
42
+ cef_logging result, :successful_attack, xml
39
43
  raise Contrast::SecurityException.new(self, BLOCK_MESSAGE)
40
44
  end
41
45
 
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2022 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/reporting/input_analysis/input_analysis_result'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Reporting
10
+ # This class will do ia analysis for our protect rules instead of
11
+ # using the service.
12
+ class InputAnalysis
13
+ # result from input analysis
14
+ #
15
+ # @return @_results [Array<Contrast::Agent::Reporting::Settings::InputAnalysisResult>]
16
+ def results
17
+ @_results ||= []
18
+ end
19
+
20
+ # result from input analysis
21
+ #
22
+ # @return @_results [Array<Contrast::Agent::Reporting::Settings::InputAnalysisResult>]
23
+ def results= results
24
+ @_results = results
25
+ end
26
+
27
+ # Returns our wrapper around the Rack::Request for this context
28
+ #
29
+ # @return request [Contrast::Agent::Request, nil]
30
+ def request
31
+ @_request ||= nil
32
+ end
33
+
34
+ # Sets current request
35
+ #
36
+ # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request for this context
37
+ # @return request [Contrast::Agent::Request, nil]
38
+ def request= request
39
+ @_request = request if request.instance_of?(Contrast::Agent::Request)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2022 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/reporting/input_analysis/input_type'
6
+ require 'contrast/agent/reporting/input_analysis/score_level'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Reporting
11
+ # This class will do ia analysis for our protect rules instead of
12
+ # using the service.
13
+ class InputAnalysisResult
14
+ INPUT_TYPE = Contrast::Agent::Reporting::InputType
15
+ SCORE_LEVEL = Contrast::Agent::Reporting::ScoreLevel
16
+
17
+ # @return @_rule_id [String]
18
+ def rule_id
19
+ @_rule_id ||= Contrast::Utils::ObjectShare::EMPTY_STRING
20
+ end
21
+
22
+ # @param id [String]
23
+ # @return @_rule_id [String]
24
+ def rule_id= id
25
+ @_rule_id = id if id.is_a?(String)
26
+ end
27
+
28
+ # @return @_input_type [
29
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::InputType>]
30
+ def input_type
31
+ @_input_type ||= INPUT_TYPE::UNDEFINED_TYPE
32
+ end
33
+
34
+ # @param input_type [
35
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::InputType>]
36
+ # @return @_input_type [
37
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::InputType>]
38
+ def input_type= input_type
39
+ @_input_type = input_type if INPUT_TYPE.to_a.include?(input_type)
40
+ end
41
+
42
+ # @return @_path [String]
43
+ def path
44
+ @_path ||= Contrast::Utils::ObjectShare::EMPTY_STRING
45
+ end
46
+
47
+ # @param path [String]
48
+ # @return @_path [String]
49
+ def path= path
50
+ @_path = path if path.is_a?(String)
51
+ end
52
+
53
+ # @return @_key [String]
54
+ def key
55
+ @_key ||= Contrast::Utils::ObjectShare::EMPTY_STRING
56
+ end
57
+
58
+ # @param key [String]
59
+ # @return @_key [String]
60
+ def key= key
61
+ @_key = key if key.is_a?(String)
62
+ end
63
+
64
+ # @return value [String]
65
+ def value
66
+ @_value ||= Contrast::Utils::ObjectShare::EMPTY_STRING
67
+ end
68
+
69
+ # @param value [String]
70
+ # @return value [String]
71
+ def value= value
72
+ @_value = value if value.is_a?(String)
73
+ end
74
+
75
+ # Matchers IDs
76
+ # @return @_ids [Array<String>]
77
+ def ids
78
+ @_ids ||= []
79
+ end
80
+
81
+ # Matchers IDs
82
+ # @param ids [Array<String>]
83
+ # @return @_ids [Array<String>]
84
+ def ids= ids
85
+ @_ids = ids if ids.is_a?(Array) && ids.any?(String)
86
+ end
87
+
88
+ # @return @_attack_count [Integer]
89
+ def attack_count
90
+ @_attack_count ||= 0
91
+ end
92
+
93
+ # @param attack_count
94
+ # @return @_attack_count
95
+ def attack_count= attack_count
96
+ @_attack_count = attack_count if attack_count.is_a?(Integer)
97
+ end
98
+
99
+ # @return @_score_level [
100
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::ScoreLevel>]
101
+ def score_level
102
+ @_score_level ||= SCORE_LEVEL::IGNORE
103
+ end
104
+
105
+ # @param score_level [
106
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::ScoreLevel>]
107
+ # @return @_score_level [
108
+ # Symbol<Contrast::Agent::Reporting::Settings::InputAnalysis::InputAnalysisResult::ScoreLevel>]
109
+ def score_level= score_level
110
+ @_score_level = score_level if SCORE_LEVEL.to_a.include?(score_level)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2022 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 Reporting
7
+ # input types for InputAnalysis results
8
+ module InputType
9
+ UNDEFINED_TYPE = :UNDEFINED_TYPE.cs__freeze
10
+ BODY = :BODY.cs__freeze
11
+ COOKIE_NAME = :COOKIE_NAME.cs__freeze
12
+ COOKIE_VALUE = :COOKIE_VALUE.cs__freeze
13
+ HEADER = :HEADER.cs__freeze
14
+ PARAMETER_NAME = :PARAMETER_NAME.cs__freeze
15
+ PARAMETER_VALUE = :PARAMETER_VALUE.cs__freeze
16
+ QUERYSTRING = :QUERYSTRING.cs__freeze
17
+ URI = :URI.cs__freeze
18
+ SOCKET = :SOCKET.cs__freeze
19
+ JSON_VALUE = :JSON_VALUE.cs__freeze
20
+ JSON_ARRAYED_VALUE = :JSON_ARRAYED_VALUE.cs__freeze
21
+ MULTIPART_CONTENT_TYPE = :MULTIPART_CONTENT_TYPE.cs__freeze
22
+ MULTIPART_VALUE = :MULTIPART_VALUE.cs__freeze
23
+ MULTIPART_FIELD_NAME = :MULTIPART_FIELD_NAME.cs__freeze
24
+ MULTIPART_NAME = :MULTIPART_NAME.cs__freeze
25
+ XML_VALUE = :XML_VALUE.cs__freeze
26
+ DWR_VALUE = :DWR_VALUE.cs__freeze
27
+ METHOD = :METHOD.cs__freeze
28
+ REQUEST = :REQUEST.cs__freeze
29
+ URL_PARAMETER = :URL_PARAMETER.cs__freeze
30
+ UNKNOWN = :UNKNOWN.cs__freeze
31
+
32
+ class << self
33
+ def to_a
34
+ [
35
+ UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME, PARAMETER_VALUE,
36
+ QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE, MULTIPART_VALUE,
37
+ MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST, URL_PARAMETER, UNKNOWN
38
+ ]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2022 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 Reporting
7
+ # input types for InputAnalysis results
8
+ module ScoreLevel
9
+ IGNORE = :DONTCARE.cs__freeze
10
+ WORTHWATCHING = :WORTHWATCHING.cs__freeze
11
+ DEFINITEATTACK = :DEFINITEATTACK.cs__freeze
12
+
13
+ class << self
14
+ def to_a
15
+ [IGNORE, WORTHWATCHING, DEFINITEATTACK]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -25,3 +25,4 @@ require 'contrast/agent/reporting/reporting_events/preflight'
25
25
  require 'contrast/agent/reporting/reporting_events/observed_route'
26
26
  require 'contrast/agent/reporting/reporting_events/route_coverage'
27
27
  require 'contrast/agent/reporting/reporting_events/observed_library_usage'
28
+ require 'contrast/agent/reporting/reporting_events/poll'
@@ -53,6 +53,11 @@ module Contrast
53
53
  @_thread = Contrast::Agent::Thread.new do
54
54
  logger.debug('Starting background Reporter thread.')
55
55
  loop do
56
+ # TODO: RUBY-99999
57
+ # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
58
+ # to figure out why that is and lock it so that it isn't.
59
+ next unless client && connection
60
+
56
61
  event = queue.pop
57
62
  begin
58
63
  response = client.send_event(event, connection)
@@ -86,7 +91,9 @@ module Contrast
86
91
  return
87
92
  end
88
93
  response = client.send_event(event, connection, true)
89
- client.send(:handle_response, event, response, connection) if response&.code == 200
94
+ return unless response
95
+
96
+ client.handle_response(event, response, connection)
90
97
  audit&.audit_event(event, response) if ::Contrast::API.request_audit_enable?
91
98
  rescue StandardError => e
92
99
  logger.error('Could not send message to service from Reporter queue.', e)