contrast-agent 5.1.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_array/cs__assess_array.c +7 -0
  3. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +19 -5
  4. data/ext/cs__assess_fiber_track/cs__assess_fiber_track.c +1 -1
  5. data/ext/cs__assess_hash/cs__assess_hash.c +3 -4
  6. data/ext/cs__assess_kernel/cs__assess_kernel.c +7 -5
  7. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +26 -12
  8. data/ext/cs__assess_module/cs__assess_module.c +7 -7
  9. data/ext/cs__assess_string/cs__assess_string.c +13 -1
  10. data/ext/cs__common/cs__common.c +16 -11
  11. data/ext/cs__common/cs__common.h +1 -0
  12. data/ext/cs__contrast_patch/cs__contrast_patch.c +100 -64
  13. data/ext/cs__contrast_patch/cs__contrast_patch.h +2 -0
  14. data/ext/cs__os_information/cs__os_information.c +13 -10
  15. data/ext/cs__scope/cs__scope.c +796 -0
  16. data/ext/cs__scope/cs__scope.h +88 -0
  17. data/ext/cs__scope/extconf.rb +5 -0
  18. data/lib/contrast/agent/assess/contrast_event.rb +20 -13
  19. data/lib/contrast/agent/assess/contrast_object.rb +4 -1
  20. data/lib/contrast/agent/assess/finalizers/hash.rb +2 -0
  21. data/lib/contrast/agent/assess/policy/policy_node.rb +50 -27
  22. data/lib/contrast/agent/assess/policy/policy_node_utils.rb +51 -0
  23. data/lib/contrast/agent/assess/policy/preshift.rb +8 -2
  24. data/lib/contrast/agent/assess/policy/propagation_method.rb +47 -13
  25. data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -5
  26. data/lib/contrast/agent/assess/policy/propagator/buffer.rb +118 -0
  27. data/lib/contrast/agent/assess/policy/propagator/keep.rb +19 -4
  28. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +2 -0
  29. data/lib/contrast/agent/assess/policy/propagator/remove.rb +18 -2
  30. data/lib/contrast/agent/assess/policy/propagator/splat.rb +17 -3
  31. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -1
  32. data/lib/contrast/agent/assess/policy/propagator/substitution_utils.rb +1 -1
  33. data/lib/contrast/agent/assess/policy/propagator/trim.rb +1 -1
  34. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  35. data/lib/contrast/agent/assess/policy/source_method.rb +7 -7
  36. data/lib/contrast/agent/assess/policy/trigger_method.rb +6 -1
  37. data/lib/contrast/agent/assess/property/tagged.rb +1 -1
  38. data/lib/contrast/agent/assess/rule/response/{autocomplete_rule.rb → auto_complete_rule.rb} +4 -3
  39. data/lib/contrast/agent/assess/rule/response/base_rule.rb +12 -79
  40. data/lib/contrast/agent/assess/rule/response/body_rule.rb +109 -0
  41. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +157 -0
  42. data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +26 -0
  43. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +14 -15
  44. data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +5 -25
  45. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +29 -0
  46. data/lib/contrast/agent/assess/rule/response/header_rule.rb +70 -0
  47. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +12 -36
  48. data/lib/contrast/agent/assess/rule/response/parameters_pollution_rule.rb +2 -1
  49. data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +26 -0
  50. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +35 -0
  51. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +0 -7
  52. data/lib/contrast/agent/deadzone/policy/policy.rb +0 -6
  53. data/lib/contrast/agent/exclusion_matcher.rb +3 -3
  54. data/lib/contrast/agent/middleware.rb +4 -1
  55. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +1 -3
  56. data/lib/contrast/agent/patching/policy/patch.rb +2 -6
  57. data/lib/contrast/agent/patching/policy/patcher.rb +4 -4
  58. data/lib/contrast/agent/patching/policy/policy_node.rb +15 -2
  59. data/lib/contrast/agent/protect/exploitable_collection.rb +38 -0
  60. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +147 -0
  61. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +2 -1
  62. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +2 -2
  63. data/lib/contrast/agent/protect/rule/base.rb +61 -2
  64. data/lib/contrast/agent/protect/rule/base_service.rb +12 -1
  65. data/lib/contrast/agent/protect/rule/cmd_injection.rb +15 -0
  66. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +83 -0
  67. data/lib/contrast/agent/protect/rule/cmdi/cmdi_worth_watching.rb +64 -0
  68. data/lib/contrast/agent/protect/rule/deserialization.rb +6 -0
  69. data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +96 -0
  70. data/lib/contrast/agent/protect/rule/http_method_tampering.rb +13 -1
  71. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +231 -0
  72. data/lib/contrast/agent/protect/rule/no_sqli.rb +28 -0
  73. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -0
  74. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +88 -0
  75. data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +118 -0
  76. data/lib/contrast/agent/protect/rule/sqli.rb +33 -0
  77. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +82 -0
  78. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_matcher.rb +45 -0
  79. data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +42 -0
  80. data/lib/contrast/agent/protect/rule/xxe.rb +4 -0
  81. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +63 -0
  82. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +52 -0
  83. data/lib/contrast/agent/reporting/attack_result/response_type.rb +29 -0
  84. data/lib/contrast/agent/reporting/attack_result/user_input.rb +87 -0
  85. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +44 -0
  86. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +115 -0
  87. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +44 -0
  88. data/lib/contrast/agent/reporting/input_analysis/score_level.rb +21 -0
  89. data/lib/contrast/agent/reporting/masker/masker.rb +246 -0
  90. data/lib/contrast/agent/reporting/masker/masker_utils.rb +58 -0
  91. data/lib/contrast/agent/reporting/report.rb +3 -0
  92. data/lib/contrast/agent/reporting/reporter.rb +31 -12
  93. data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +30 -0
  94. data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +7 -3
  95. data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +40 -0
  96. data/lib/contrast/agent/reporting/reporting_events/application_startup_instrumentation.rb +27 -0
  97. data/lib/contrast/agent/reporting/reporting_events/finding.rb +69 -36
  98. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +88 -59
  99. data/lib/contrast/agent/reporting/reporting_events/{finding_object.rb → finding_event_object.rb} +24 -20
  100. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +39 -0
  101. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +40 -0
  102. data/lib/contrast/agent/reporting/reporting_events/{finding_signature.rb → finding_event_signature.rb} +29 -24
  103. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +12 -8
  104. data/lib/contrast/agent/reporting/reporting_events/{finding_stack.rb → finding_event_stack.rb} +23 -19
  105. data/lib/contrast/agent/reporting/reporting_events/{finding_taint_range.rb → finding_event_taint_range.rb} +17 -15
  106. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +26 -53
  107. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +5 -5
  108. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +9 -9
  109. data/lib/contrast/agent/reporting/reporting_events/poll.rb +29 -0
  110. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +2 -1
  111. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +6 -4
  112. data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +8 -6
  113. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +1 -0
  114. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +1 -1
  115. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  116. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +0 -1
  117. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +17 -5
  118. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +54 -45
  119. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +1 -1
  120. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +97 -0
  121. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +69 -7
  122. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_mode.rb +63 -0
  123. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +123 -85
  124. data/lib/contrast/agent/reporting/settings/application_settings.rb +9 -0
  125. data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +5 -33
  126. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +1 -1
  127. data/lib/contrast/agent/reporting/settings/sampling.rb +36 -0
  128. data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +110 -0
  129. data/lib/contrast/agent/reporting/settings/sensitive_data_masking_rule.rb +58 -0
  130. data/lib/contrast/agent/request_context.rb +7 -2
  131. data/lib/contrast/agent/request_context_extend.rb +85 -21
  132. data/lib/contrast/agent/request_handler.rb +4 -0
  133. data/lib/contrast/agent/scope.rb +102 -107
  134. data/lib/contrast/agent/service_heartbeat.rb +45 -2
  135. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +51 -0
  136. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +36 -0
  137. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +97 -0
  138. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +65 -0
  139. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +47 -0
  140. data/lib/contrast/agent/{metric_telemetry_event.rb → telemetry/events/metric_telemetry_event.rb} +1 -1
  141. data/lib/contrast/agent/{startup_metrics_telemetry_event.rb → telemetry/events/startup_metrics_telemetry_event.rb} +3 -3
  142. data/lib/contrast/agent/{telemetry_event.rb → telemetry/events/telemetry_event.rb} +1 -1
  143. data/lib/contrast/agent/{telemetry.rb → telemetry/telemetry.rb} +32 -19
  144. data/lib/contrast/agent/thread_watcher.rb +1 -1
  145. data/lib/contrast/agent/version.rb +1 -1
  146. data/lib/contrast/agent.rb +3 -0
  147. data/lib/contrast/api/communication/speedracer.rb +1 -1
  148. data/lib/contrast/api/decorators/address.rb +1 -1
  149. data/lib/contrast/api/decorators/bot_blocker.rb +37 -0
  150. data/lib/contrast/api/decorators/ip_denylist.rb +37 -0
  151. data/lib/contrast/api/decorators/rasp_rule_sample.rb +29 -0
  152. data/lib/contrast/api/decorators/response_type.rb +30 -0
  153. data/lib/contrast/api/decorators/user_input.rb +11 -1
  154. data/lib/contrast/api/decorators/virtual_patch.rb +34 -0
  155. data/lib/contrast/api/decorators.rb +1 -0
  156. data/lib/contrast/components/app_context.rb +0 -4
  157. data/lib/contrast/components/assess.rb +14 -0
  158. data/lib/contrast/components/logger.rb +5 -0
  159. data/lib/contrast/components/protect.rb +6 -4
  160. data/lib/contrast/components/sampling.rb +7 -11
  161. data/lib/contrast/components/scope.rb +98 -91
  162. data/lib/contrast/components/settings.rb +106 -8
  163. data/lib/contrast/config/agent_configuration.rb +41 -12
  164. data/lib/contrast/config/api_configuration.rb +37 -12
  165. data/lib/contrast/config/api_proxy_configuration.rb +12 -3
  166. data/lib/contrast/config/application_configuration.rb +38 -14
  167. data/lib/contrast/config/assess_configuration.rb +47 -12
  168. data/lib/contrast/config/assess_rules_configuration.rb +15 -3
  169. data/lib/contrast/config/base_configuration.rb +18 -50
  170. data/lib/contrast/config/certification_configuration.rb +17 -3
  171. data/lib/contrast/config/exception_configuration.rb +14 -3
  172. data/lib/contrast/config/heap_dump_configuration.rb +43 -17
  173. data/lib/contrast/config/inventory_configuration.rb +17 -3
  174. data/lib/contrast/config/logger_configuration.rb +10 -3
  175. data/lib/contrast/config/protect_configuration.rb +17 -7
  176. data/lib/contrast/config/protect_rule_configuration.rb +17 -8
  177. data/lib/contrast/config/protect_rules_configuration.rb +115 -17
  178. data/lib/contrast/config/request_audit_configuration.rb +26 -3
  179. data/lib/contrast/config/root_configuration.rb +52 -12
  180. data/lib/contrast/config/ruby_configuration.rb +60 -22
  181. data/lib/contrast/config/sampling_configuration.rb +19 -9
  182. data/lib/contrast/config/server_configuration.rb +19 -10
  183. data/lib/contrast/config/service_configuration.rb +27 -11
  184. data/lib/contrast/configuration.rb +5 -3
  185. data/lib/contrast/extension/assess/string.rb +20 -1
  186. data/lib/contrast/extension/module.rb +0 -1
  187. data/lib/contrast/framework/manager.rb +2 -2
  188. data/lib/contrast/logger/application.rb +1 -1
  189. data/lib/contrast/logger/cef_log.rb +151 -0
  190. data/lib/contrast/tasks/config.rb +90 -3
  191. data/lib/contrast/utils/assess/object_store.rb +36 -0
  192. data/lib/contrast/utils/assess/propagation_method_utils.rb +6 -0
  193. data/lib/contrast/utils/class_util.rb +3 -12
  194. data/lib/contrast/utils/hash_digest.rb +14 -6
  195. data/lib/contrast/utils/input_classification.rb +73 -0
  196. data/lib/contrast/utils/log_utils.rb +114 -0
  197. data/lib/contrast/utils/middleware_utils.rb +9 -9
  198. data/lib/contrast/utils/net_http_base.rb +13 -10
  199. data/lib/contrast/utils/object_share.rb +2 -1
  200. data/lib/contrast/utils/os.rb +0 -5
  201. data/lib/contrast/utils/patching/policy/patch_utils.rb +4 -9
  202. data/lib/contrast/utils/response_utils.rb +18 -33
  203. data/lib/contrast/utils/telemetry.rb +1 -1
  204. data/lib/contrast/utils/telemetry_client.rb +1 -1
  205. data/lib/contrast/utils/telemetry_identifier.rb +1 -1
  206. data/lib/contrast.rb +4 -3
  207. data/resources/assess/policy.json +98 -0
  208. data/resources/deadzone/policy.json +0 -86
  209. data/ruby-agent.gemspec +9 -8
  210. data/service_executables/VERSION +1 -1
  211. data/service_executables/linux/contrast-service +0 -0
  212. data/service_executables/mac/contrast-service +0 -0
  213. metadata +103 -38
  214. data/lib/contrast/agent/assess/rule/response/cachecontrol_rule.rb +0 -184
  215. data/lib/contrast/agent/assess/rule/response/clickjacking_rule.rb +0 -66
  216. data/lib/contrast/agent/assess/rule/response/x_content_type_rule.rb +0 -52
  217. data/lib/contrast/agent/assess/rule/response/x_xss_protection_rule.rb +0 -53
  218. data/lib/contrast/extension/kernel.rb +0 -54
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
3
3
  # frozen_string_literal: true
4
4
 
5
- require 'contrast/agent/assess/rule/response/base_rule'
5
+ require 'contrast/agent/assess/rule/response/header_rule'
6
6
  require 'contrast/utils/string_utils'
7
7
 
8
8
  module Contrast
@@ -11,27 +11,22 @@ module Contrast
11
11
  module Rule
12
12
  module Response
13
13
  # These rules check that the HTTP Headers include CSP header types
14
- class CspHeaderInsecure < BaseRule
14
+ class CspHeaderInsecure < HeaderRule
15
15
  def rule_id
16
16
  'csp-header-insecure'
17
17
  end
18
18
 
19
19
  protected
20
20
 
21
- CSP_HEADERS = %w[CONTENT_SECURITY_POLICY X_CONTENT_SECURITY_POLICY X_WEBKIT_CSP].cs__freeze
21
+ HEADER_KEYS = %w[Content-Security-Policy X-Content-Security-Policy X-Webkit-CSP].cs__freeze
22
+ DEFAULT_SAFE = false
22
23
  SETTINGS = %w[
23
24
  base-uri child-src default-src connect-src frame-src media-src object-src script-src
24
25
  style-src form-action frame-ancestors plugin-types reflected-xss referer
25
26
  ].cs__freeze
26
27
  UNSAFE_VALUE_REGEXP = /^unsafe-(?:inline|eval)$/.cs__freeze
27
28
  ASTERISK_REGEXP = /[*]/.cs__freeze
28
-
29
- # Rules discern which responses they can/should analyze.
30
- #
31
- # @param response [Contrast::Agent::Response] the response of the application
32
- def analyze_response? response
33
- super && headers?(response)
34
- end
29
+ SAFE_REFLECTED_XSS = /1/.cs__freeze
35
30
 
36
31
  # Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
37
32
  #
@@ -39,16 +34,19 @@ module Contrast
39
34
  # @return [Contrast::Utils::ObjectShare::EMPTY_STRING, nil] if CSP Header is not found
40
35
  def violated? response
41
36
  settings = {}
42
- csp_hash = get_csp_header_values(response.headers)
37
+ csp_hash = get_header_value(response)
43
38
  return if csp_hash.nil?
44
39
 
45
40
  SETTINGS.each do |setting_attr|
41
+ # default src has to be checked all other keys may be missing
42
+ next unless csp_hash.key?(setting_attr) || setting_attr == 'default-src'
43
+
46
44
  value = csp_hash[setting_attr]
47
45
  key = convert_key(setting_attr)
48
46
  settings["#{ key }Secure"] = !value.nil? && value_secure?(value) && value_safe?(value)
49
47
  settings["#{ key }Value"] = value.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : value
50
48
  end
51
- { DATA => settings }
49
+ evidence(settings) if settings.value?(false)
52
50
  end
53
51
 
54
52
  # Get the CSP values from and transforms them to key value hash
@@ -58,9 +56,10 @@ module Contrast
58
56
  #
59
57
  # @param headers [Hash] the response of the application
60
58
  # @return [Array, nil] array of CSP header values
61
- def get_csp_header_values headers
59
+ def get_header_value response
62
60
  csp_hash = {}
63
- CSP_HEADERS.each do |header_key|
61
+ headers = response.headers
62
+ HEADER_KEYS.each do |header_key|
64
63
  next unless headers[header_key]&.length&.positive?
65
64
 
66
65
  values = headers[header_key].split(Contrast::Utils::ObjectShare::SEMICOLON)
@@ -78,7 +77,7 @@ module Contrast
78
77
  end
79
78
 
80
79
  def value_safe? value
81
- UNSAFE_VALUE_REGEXP.match(value).nil?
80
+ UNSAFE_VALUE_REGEXP.match(value).nil? || !SAFE_REFLECTED_XSS.match(value).nil?
82
81
  end
83
82
 
84
83
  # Converts the CSP key to camelcase to be used as key for evidence object
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2022 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/agent/assess/rule/response/base_rule'
4
+ require 'contrast/agent/assess/rule/response/header_rule'
5
5
  require 'contrast/utils/string_utils'
6
6
 
7
7
  module Contrast
@@ -10,34 +10,14 @@ module Contrast
10
10
  module Rule
11
11
  module Response
12
12
  # These rules check that the HTTP Headers include CSP header types
13
- class CspHeaderMissing < BaseRule
13
+ class CspHeaderMissing < HeaderRule
14
14
  def rule_id
15
15
  'csp-header-missing'
16
16
  end
17
17
 
18
- protected
19
-
20
- CSP_HEADERS = %w[CONTENT_SECURITY_POLICY X_CONTENT_SECURITY_POLICY X_WEBKIT_CSP].cs__freeze
21
-
22
- DATA = 'data'.cs__freeze
23
-
24
- # Rules discern which responses they can/should analyze.
25
- #
26
- # @param response [Contrast::Agent::Response] the response of the application
27
- def analyze_response? response
28
- super && headers?(response)
29
- end
30
-
31
- # Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
32
- #
33
- # @param response [Contrast::Agent::Response] the response of the application
34
- # @return [Contrast::Utils::ObjectShare::EMPTY_STRING, nil] if CSP Header is not found
35
- def violated? response
36
- response_headers = response.headers
37
- return if CSP_HEADERS.any? { |header_key| response_headers[header_key]&.length&.positive? }
38
-
39
- { DATA => Contrast::Utils::ObjectShare::EMPTY_STRING }
40
- end
18
+ HEADER_KEYS = %w[Content-Security-Policy X-Content-Security-Policy X-Webkit-CSP].cs__freeze
19
+ ACCEPTED_VALUES = [/(.)/].cs__freeze
20
+ DEFAULT_SAFE = false
41
21
  end
42
22
  end
43
23
  end
@@ -0,0 +1,29 @@
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 Assess
7
+ module Rule
8
+ module Response
9
+ module Framework
10
+ # Rails 7 supports managing potential unsafe Headers
11
+ # this module contains methods for checking if Rails 7 supersedes our rules
12
+ module RailsSupport
13
+ RAILS_VERSION = Gem::Version.new('7.0.0')
14
+
15
+ def framework_supported?
16
+ return false unless defined?(::Rails)
17
+
18
+ rails_version = ::Rails.version
19
+ return false unless !!rails_version
20
+
21
+ Gem::Version.new(rails_version) >= RAILS_VERSION
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,70 @@
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 'rack'
5
+ require 'contrast/agent/reporting/reporting_utilities/dtm_message'
6
+ require 'contrast/utils/hash_digest'
7
+ require 'contrast/utils/preflight_util'
8
+ require 'contrast/utils/string_utils'
9
+ require 'contrast/agent/assess/rule/response/base_rule'
10
+
11
+ module Contrast
12
+ module Agent
13
+ module Assess
14
+ module Rule
15
+ module Response
16
+ # These rules check the content of the HTTP Response to determine if something was set incorrectly or
17
+ # insecurely in it.
18
+ class HeaderRule < BaseRule
19
+ HEADER_TYPE = 'header'
20
+
21
+ # Rules discern which responses they can/should analyze.
22
+ #
23
+ # @param response [Contrast::Agent::Response] the response of the application
24
+ def analyze_response? response
25
+ super && headers?(response)
26
+ end
27
+
28
+ # Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
29
+ #
30
+ # @param response [Contrast::Agent::Response] the response of the application
31
+ # @return [Hash, nil] the evidence required to prove the violation of the rule
32
+ def violated? response
33
+ header_value = get_header_value(response)
34
+ if header_value
35
+ return evidence(header_value) unless valid_header?(header_value)
36
+ else
37
+ return evidence(header_value) unless cs__class::DEFAULT_SAFE
38
+ end
39
+ nil
40
+ end
41
+
42
+ # Determine if a response has headers.
43
+ #
44
+ # @param response [Contrast::Agent::Response] the response of the application
45
+ # @return [Boolean]
46
+ def headers? response
47
+ response.headers&.any?
48
+ end
49
+
50
+ protected
51
+
52
+ def get_header_value response
53
+ response_headers = response.headers
54
+ values = response_headers.values_at(*cs__class::HEADER_KEYS, *cs__class::HEADER_KEYS.map(&:to_sym))
55
+ values.compact.first
56
+ end
57
+
58
+ # Determine if the value of the Response Header has a valid value
59
+ #
60
+ # @param header [Contrast::Agent::Response] a response header
61
+ # @return [Boolean] whether the header value is valid
62
+ def valid_header? header
63
+ cs__class::ACCEPTED_VALUES.any? { |val| val.match(header) }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,6 +1,9 @@
1
1
  # Copyright (c) 2022 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/agent/assess/rule/response/header_rule'
5
+ require 'contrast/utils/string_utils'
6
+
4
7
  module Contrast
5
8
  module Agent
6
9
  module Assess
@@ -8,49 +11,22 @@ module Contrast
8
11
  module Response
9
12
  # This rule checks if the HTTP Headers include HSTS header and ensures that the max-age value
10
13
  # is set to a value greater than 0.
11
- class HSTSHeader < BaseRule
14
+ class HSTSHeader < HeaderRule
12
15
  def rule_id
13
16
  'hsts-header-missing'
14
17
  end
15
18
 
16
19
  protected
17
20
 
18
- HEADER_KEY = 'Strict-Transport-Security'
19
- HEADER_KEY_SYM = HEADER_KEY.to_sym
20
- MAX_AGE = 'max-age'
21
- MAX_AGE_SYM = MAX_AGE.to_sym
22
- # Rules discern which responses they can/should analyze.
23
- #
24
- # @param response [Contrast::Agent::Response] the response of the application
25
- def analyze_response? response
26
- super && response.headers.cs__is_a?(Hash)
27
- end
28
-
29
- # Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
30
- #
31
- # @param response [Contrast::Agent::Response] the response of the application
32
- # @return [Hash<data: Contrast::Utils::ObjectShare::EMPTY_STRING, String>, nil] return string
33
- # representation of the max_age
34
- def violated? response
35
- headers = response.headers
36
- target = headers[HEADER_KEY] || headers[HEADER_KEY_SYM]
37
- # this rule is safe by default if no target => no evidence
38
- # if the property max_age is not positive or absent then the rule is violated
39
- return unless target
40
-
41
- max_age = target[MAX_AGE] || target[MAX_AGE_SYM]
42
- return if max_age.to_i.positive?
43
-
44
- evidence max_age
45
- end
21
+ HEADER_KEYS = %w[Strict-Transport-Security].cs__freeze
22
+ ACCEPTED_VALUES = [/max-age=(\.)?\d+(\.\d*)?/].cs__freeze
23
+ DEFAULT_SAFE = true
46
24
 
47
- # returns evidence that the max_age is negative or absent
48
- #
49
- # @param max_age [String] String representation of the max-age value to which the header is set
50
- # @return [Hash<data: Contrast::Utils::ObjectShare::EMPTY_STRING, String>] return string representation of
51
- # the max_age
52
- def evidence max_age
53
- { data: max_age.to_s }
25
+ def evidence data
26
+ # get only the value of the max-age property
27
+ val = data&.split('=')&.last
28
+ val = Contrast::Utils::ObjectShare::EMPTY_STRING if val.nil? || val == 'max-age'
29
+ { DATA => val }
54
30
  end
55
31
  end
56
32
  end
@@ -12,6 +12,7 @@ module Contrast
12
12
  # These rules check the content of the HTTP Response to determine if the body contains a form which
13
13
  # incorrectly sets the action attribute.
14
14
  class ParametersPollution < BaseRule
15
+ include BodyRule
15
16
  def rule_id
16
17
  'parameter-pollution'
17
18
  end
@@ -31,7 +32,7 @@ module Contrast
31
32
  # @return [Hash, nil] the evidence required to prove the violation of the rule
32
33
  def violated? response
33
34
  body = response.body
34
- forms = forms(body)
35
+ forms = html_elements(body, FORM_START_REGEXP, capture_overflow: true)
35
36
  forms.each do |form|
36
37
  # Because TeamServer will reject any subsequent form on the same page due to deduplication, we can
37
38
  # skip out on the first violation.
@@ -0,0 +1,26 @@
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/agent/assess/rule/response/header_rule'
5
+ require 'contrast/utils/string_utils'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Assess
10
+ module Rule
11
+ module Response
12
+ # These rules check the content of the HTTP Response to determine if the response contains the needed header
13
+ class XContentType < HeaderRule
14
+ def rule_id
15
+ 'xcontenttype-header-missing'
16
+ end
17
+
18
+ HEADER_KEYS = %w[X-Content-Type-Options].cs__freeze
19
+ ACCEPTED_VALUES = [/^nosniff/i].cs__freeze
20
+ DEFAULT_SAFE = false
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,35 @@
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/agent/assess/rule/response/framework/rails_support'
5
+ require 'contrast/agent/assess/rule/response/header_rule'
6
+ require 'contrast/utils/string_utils'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Assess
11
+ module Rule
12
+ module Response
13
+ # These rules check the content of the HTTP Response to determine if the response contains the needed header
14
+ class XXssProtection < HeaderRule
15
+ include Framework::RailsSupport
16
+
17
+ def rule_id
18
+ 'xxssprotection-header-disabled'
19
+ end
20
+
21
+ HEADER_KEYS = %w[X-XSS-Protection].cs__freeze
22
+ ACCEPTED_VALUES = [/^1/].cs__freeze
23
+ DEFAULT_SAFE = true
24
+
25
+ protected
26
+
27
+ def analyze_response? response
28
+ !framework_supported? && super
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -24,13 +24,6 @@ module Contrast
24
24
  def method_scope
25
25
  :contrast
26
26
  end
27
-
28
- # TODO: RUBY-99999 remove, used to clean up logs while debugging
29
- # def validate
30
- # return if class_name
31
- #
32
- # raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
33
- # end
34
27
  end
35
28
  end
36
29
  end
@@ -35,12 +35,6 @@ module Contrast
35
35
  end
36
36
  end
37
37
 
38
- def validate
39
- return if class_name
40
-
41
- raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
42
- end
43
-
44
38
  def module_names
45
39
  @_module_names ||= Set.new(deadzones.map(&:class_name))
46
40
  end
@@ -51,7 +51,7 @@ module Contrast
51
51
 
52
52
  @urls = []
53
53
  @exclusion.urls.each do |url|
54
- url_pattern = build_regexp(url, true, true)
54
+ url_pattern = build_regexp(url, start_anchor: true, end_anchor: true)
55
55
  @urls << url_pattern if url_pattern
56
56
  end
57
57
  end
@@ -66,7 +66,7 @@ module Contrast
66
66
  @wildcard_exclusions = []
67
67
  @exclusion.denylist.each do |code|
68
68
  class_name, method_name = code.split(Contrast::Utils::ObjectShare::COLON)
69
- class_pattern = build_regexp(class_name, false, true)
69
+ class_pattern = build_regexp(class_name, start_anchor: false, end_anchor: true)
70
70
  method_pattern = build_regexp(method_name)
71
71
  next unless class_pattern && method_pattern
72
72
 
@@ -74,7 +74,7 @@ module Contrast
74
74
  end
75
75
  end
76
76
 
77
- def build_regexp pattern, start_anchor = false, end_anchor = false
77
+ def build_regexp pattern, start_anchor: false, end_anchor: false
78
78
  pattern = Contrast::Utils::ObjectShare::CARROT + pattern if start_anchor
79
79
  pattern += Contrast::Utils::ObjectShare::DOLLAR_SIGN if end_anchor
80
80
  Regexp.compile(pattern)
@@ -13,7 +13,7 @@ require 'contrast/utils/heap_dump_util'
13
13
  require 'contrast/utils/telemetry'
14
14
  require 'contrast/agent/request_handler'
15
15
  require 'contrast/agent/static_analysis'
16
- require 'contrast/agent/startup_metrics_telemetry_event'
16
+ require 'contrast/agent/telemetry/events/startup_metrics_telemetry_event'
17
17
  require 'contrast/utils/middleware_utils'
18
18
 
19
19
  require 'contrast/utils/timer'
@@ -163,6 +163,8 @@ module Contrast
163
163
 
164
164
  # Build and report all collected findings prior response
165
165
  Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
166
+ # All protect rules, which are trigger but require response to be reported
167
+ Contrast::Agent::EXPLOITS.report_recorded_exploits context unless Contrast::Agent::EXPLOITS.collection.empty?
166
168
 
167
169
  if Contrast::Agent.framework_manager.streaming?(env)
168
170
  context.reset_activity
@@ -173,6 +175,7 @@ module Contrast
173
175
  request_handler.send_activity_messages # TODO: RUBY-1438 -- remove
174
176
  end
175
177
  end
178
+ # unsuccessful attack
176
179
  rescue StandardError => e
177
180
  raise e if security_exception?(e)
178
181
 
@@ -29,14 +29,13 @@ module Contrast
29
29
  # there are no require time side effects of loading our core
30
30
  # extensions.
31
31
  def apply_direct_patches!
32
- @_apply_direct_patches ||= begin
32
+ @_apply_direct_patches ||= begin # TODO: RUBY-1541 - put 'kernel' back
33
33
  paths = %w[
34
34
  array
35
35
  basic_object
36
36
  module
37
37
  fiber_track
38
38
  hash
39
- kernel
40
39
  marshal_module
41
40
  regexp
42
41
  string
@@ -75,7 +74,6 @@ module Contrast
75
74
  def apply_require_patches!
76
75
  @_apply_require_patches ||= begin
77
76
  require 'contrast/extension/thread'
78
- require 'contrast/extension/kernel'
79
77
  true
80
78
  rescue LoadError, StandardError => e
81
79
  logger.error('failed instrumenting apply_require_patches!', e)
@@ -49,15 +49,11 @@ module Contrast
49
49
  ].cs__freeze
50
50
 
51
51
  def enter_method_scope! method_policy
52
- method_policy.scopes_to_enter.each do |scope|
53
- enter_scope!(scope)
54
- end
52
+ contrast_enter_method_scopes! method_policy.scopes_to_enter
55
53
  end
56
54
 
57
55
  def exit_method_scope! method_policy
58
- method_policy.scopes_to_exit.each do |scope|
59
- exit_scope!(scope)
60
- end
56
+ contrast_exit_method_scopes! method_policy.scopes_to_exit
61
57
  end
62
58
 
63
59
  # @param mod [Module] the module in which the patch should be
@@ -133,7 +133,7 @@ module Contrast
133
133
  # @param module_data [Contrast::Agent::ModuleData] the module, and its name, that's being patched into
134
134
  # @param redo_patch [Boolean] a trigger to force patching regardless of the state of the
135
135
  # Contrast::Agent::Patching::Policy::PatchStatus status on the Module
136
- def patch_into_module module_data, redo_patch = false
136
+ def patch_into_module module_data, redo_patch: false
137
137
  status = Contrast::Agent::Patching::Policy::PatchStatus.get_status(module_data.mod)
138
138
  return if (status&.patched? || status&.patching?) && !redo_patch
139
139
 
@@ -147,7 +147,7 @@ module Contrast
147
147
  return
148
148
  end
149
149
 
150
- patch_methods status, module_data, module_policy
150
+ patch_methods(status, module_data, module_policy)
151
151
  rescue StandardError => e
152
152
  status&.failed_patch!
153
153
  logger.warn('Patching failed', e, module: module_data.mod_name)
@@ -179,7 +179,7 @@ module Contrast
179
179
  # @param private [Boolean] Indicate if the query should include
180
180
  # private, as well as public, instance methods
181
181
  # @return [Array<Symbol>]
182
- def all_instance_methods mod, private = false
182
+ def all_instance_methods mod, private: false
183
183
  instance_methods = mod.instance_methods(false)
184
184
  # C magic rb_define_global_function creates private instance
185
185
  # methods. We need to instrument those dudes
@@ -206,7 +206,7 @@ module Contrast
206
206
  # this module, sorted by type.
207
207
  def patch_into_instance_methods module_data, module_policy
208
208
  mod = module_data.mod
209
- methods = all_instance_methods(mod, true)
209
+ methods = all_instance_methods(mod, private: true)
210
210
  methods.delete(:initialize) if mod.to_s.starts_with?('RSpec') && mod.to_s.include?('Matchers')
211
211
  patch_into_methods(mod, methods, module_policy, true)
212
212
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/scope'
5
+ require 'contrast/utils/object_share'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -14,8 +15,20 @@ module Contrast
14
15
  class PolicyNode
15
16
  include Contrast::Components::Scope::InstanceMethods
16
17
 
17
- attr_accessor :class_name, :instance_method, :method_name, :method_visibility
18
- attr_reader :properties, :method_scope
18
+ # Name of the class in which the method is being invoked.
19
+ attr_accessor :class_name
20
+ # Check for instance method.
21
+ #
22
+ # @return true | false
23
+ attr_accessor :instance_method
24
+ # The symbol representation of the invoked method.
25
+ attr_accessor :method_name
26
+ # Visibility of the invoked method [Private, Public, Protected]
27
+ attr_accessor :method_visibility
28
+ # Properties parsed from our JSON policy.
29
+ attr_reader :properties
30
+ # Scope of the method parsed from our JSON policy.
31
+ attr_reader :method_scope
19
32
 
20
33
  def node_class
21
34
  raise NoMethodError, 'specify the type of the feature for which this node patches'
@@ -0,0 +1,38 @@
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/components/logger'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Protect
9
+ # This class will store all exploits or definite attack but
10
+ # require us to wait for response
11
+ class ExploitableCollection
12
+ include Contrast::Components::Logger::InstanceMethods
13
+
14
+ def initialize
15
+ @_collection = []
16
+ end
17
+
18
+ def collection
19
+ @_collection ||= []
20
+ end
21
+
22
+ # Push the Result we need to store until response is available
23
+ #
24
+ # @param attack_result [Contrast::Agent::Reporting::AttackResult]
25
+ def push attack_result
26
+ @_collection << attack_result
27
+ end
28
+
29
+ # Attach attack results to the context
30
+ #
31
+ # @param context [Contrast::Agent::RequestContext]
32
+ def report_recorded_exploits context
33
+ context.activity.results.concat(@_collection)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end