contrast-agent 6.6.5 → 6.7.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.gitmodules +0 -3
  4. data/ext/cs__scope/cs__scope.c +1 -1
  5. data/lib/contrast/agent/assess/contrast_event.rb +2 -24
  6. data/lib/contrast/agent/assess/events/source_event.rb +7 -61
  7. data/lib/contrast/agent/assess/finalizers/hash.rb +11 -0
  8. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +0 -55
  9. data/lib/contrast/agent/assess/policy/policy_node.rb +3 -3
  10. data/lib/contrast/agent/assess/policy/policy_node_utils.rb +0 -1
  11. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  12. data/lib/contrast/agent/assess/policy/source_method.rb +24 -1
  13. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +7 -5
  14. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +6 -1
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +36 -132
  16. data/lib/contrast/agent/assess/policy/trigger_node.rb +3 -3
  17. data/lib/contrast/agent/assess/property/evented.rb +2 -12
  18. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +42 -84
  19. data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -27
  20. data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -3
  21. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +77 -62
  22. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +1 -1
  23. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +6 -1
  24. data/lib/contrast/agent/assess/rule/response/header_rule.rb +5 -5
  25. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +1 -1
  26. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +1 -1
  27. data/lib/contrast/agent/assess/tracker.rb +1 -7
  28. data/lib/contrast/agent/excluder.rb +206 -0
  29. data/lib/contrast/agent/exclusion_matcher.rb +6 -0
  30. data/lib/contrast/agent/inventory/database_config.rb +6 -10
  31. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +4 -0
  32. data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +1 -0
  33. data/lib/contrast/agent/protect/rule/base.rb +49 -5
  34. data/lib/contrast/agent/protect/rule/base_service.rb +1 -0
  35. data/lib/contrast/agent/protect/rule/cmd_injection.rb +18 -105
  36. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +129 -0
  37. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +169 -0
  38. data/lib/contrast/agent/protect/rule/deserialization.rb +2 -1
  39. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +51 -0
  40. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +67 -0
  41. data/lib/contrast/agent/protect/rule/sqli.rb +6 -31
  42. data/lib/contrast/agent/protect/rule/xxe.rb +2 -0
  43. data/lib/contrast/agent/protect/rule.rb +3 -1
  44. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +6 -0
  45. data/lib/contrast/agent/reporting/details/sqli_dangerous_functions.rb +22 -0
  46. data/lib/contrast/agent/reporting/reporter.rb +1 -2
  47. data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +2 -2
  48. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -4
  49. data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +1 -1
  50. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -23
  51. data/lib/contrast/agent/reporting/reporting_events/finding.rb +19 -49
  52. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +12 -9
  53. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +1 -1
  54. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +23 -21
  55. data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +5 -18
  56. data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +1 -0
  57. data/lib/contrast/{api/decorators/trace_taint_range_tags.rb → agent/reporting/reporting_events/finding_event_taint_range_tags.rb} +7 -6
  58. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +1 -1
  59. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +1 -1
  60. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +2 -2
  61. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +10 -14
  62. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +11 -0
  63. data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +3 -1
  64. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +11 -23
  65. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +8 -26
  66. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +4 -7
  68. data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +1 -1
  69. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +3 -3
  70. data/lib/contrast/agent/request.rb +2 -2
  71. data/lib/contrast/agent/request_context.rb +8 -20
  72. data/lib/contrast/agent/request_context_extend.rb +15 -36
  73. data/lib/contrast/agent/request_handler.rb +0 -8
  74. data/lib/contrast/agent/response.rb +0 -18
  75. data/lib/contrast/agent/telemetry/events/event.rb +1 -1
  76. data/lib/contrast/agent/telemetry/events/metric_event.rb +1 -1
  77. data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +3 -3
  78. data/lib/contrast/agent/version.rb +1 -1
  79. data/lib/contrast/api/communication/messaging_queue.rb +2 -3
  80. data/lib/contrast/api/communication/socket_client.rb +4 -4
  81. data/lib/contrast/api/communication/speedracer.rb +4 -8
  82. data/lib/contrast/api/decorators/agent_startup.rb +5 -6
  83. data/lib/contrast/api/decorators/application_settings.rb +2 -1
  84. data/lib/contrast/api/decorators/application_startup.rb +6 -6
  85. data/lib/contrast/api/decorators/message.rb +0 -4
  86. data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -6
  87. data/lib/contrast/api/decorators.rb +0 -6
  88. data/lib/contrast/api/dtm.pb.rb +0 -489
  89. data/lib/contrast/components/agent.rb +16 -12
  90. data/lib/contrast/components/api.rb +10 -10
  91. data/lib/contrast/components/app_context.rb +3 -3
  92. data/lib/contrast/components/app_context_extend.rb +1 -1
  93. data/lib/contrast/components/assess.rb +92 -38
  94. data/lib/contrast/components/assess_rules.rb +36 -0
  95. data/lib/contrast/components/config.rb +54 -12
  96. data/lib/contrast/components/contrast_service.rb +8 -8
  97. data/lib/contrast/components/heap_dump.rb +1 -1
  98. data/lib/contrast/components/protect.rb +5 -5
  99. data/lib/contrast/components/ruby_component.rb +81 -0
  100. data/lib/contrast/components/sampling.rb +1 -1
  101. data/lib/contrast/components/security_logger.rb +23 -0
  102. data/lib/contrast/components/service.rb +55 -0
  103. data/lib/contrast/components/settings.rb +12 -4
  104. data/lib/contrast/config/base_configuration.rb +1 -1
  105. data/lib/contrast/config/protect_rules_configuration.rb +17 -3
  106. data/lib/contrast/config/server_configuration.rb +1 -1
  107. data/lib/contrast/config.rb +0 -6
  108. data/lib/contrast/configuration.rb +81 -17
  109. data/lib/contrast/extension/assess/exec_trigger.rb +3 -1
  110. data/lib/contrast/extension/assess/marshal.rb +3 -2
  111. data/lib/contrast/extension/assess/string.rb +0 -1
  112. data/lib/contrast/extension/extension.rb +1 -1
  113. data/lib/contrast/framework/base_support.rb +0 -5
  114. data/lib/contrast/framework/grape/support.rb +1 -23
  115. data/lib/contrast/framework/manager.rb +0 -10
  116. data/lib/contrast/framework/rails/support.rb +5 -58
  117. data/lib/contrast/framework/sinatra/support.rb +2 -21
  118. data/lib/contrast/logger/cef_log.rb +21 -3
  119. data/lib/contrast/logger/log.rb +1 -11
  120. data/lib/contrast/tasks/config.rb +4 -2
  121. data/lib/contrast/utils/assess/event_limit_utils.rb +5 -8
  122. data/lib/contrast/utils/assess/trigger_method_utils.rb +10 -18
  123. data/lib/contrast/utils/findings.rb +6 -5
  124. data/lib/contrast/utils/hash_digest.rb +9 -24
  125. data/lib/contrast/utils/hash_digest_extend.rb +6 -6
  126. data/lib/contrast/utils/invalid_configuration_util.rb +21 -58
  127. data/lib/contrast/utils/log_utils.rb +32 -8
  128. data/lib/contrast/utils/net_http_base.rb +2 -2
  129. data/lib/contrast/utils/patching/policy/patch_utils.rb +3 -2
  130. data/lib/contrast/utils/stack_trace_utils.rb +0 -25
  131. data/lib/contrast/utils/string_utils.rb +9 -0
  132. data/lib/contrast/utils/telemetry_client.rb +13 -7
  133. data/lib/contrast.rb +5 -10
  134. metadata +22 -28
  135. data/lib/contrast/agent/reporting/reporting_events/trace_event_source.rb +0 -30
  136. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -36
  137. data/lib/contrast/api/decorators/activity.rb +0 -33
  138. data/lib/contrast/api/decorators/architecture_component.rb +0 -36
  139. data/lib/contrast/api/decorators/finding.rb +0 -29
  140. data/lib/contrast/api/decorators/route_coverage.rb +0 -91
  141. data/lib/contrast/api/decorators/trace_event.rb +0 -120
  142. data/lib/contrast/api/decorators/trace_event_object.rb +0 -63
  143. data/lib/contrast/api/decorators/trace_event_signature.rb +0 -69
  144. data/lib/contrast/api/decorators/trace_taint_range.rb +0 -52
  145. data/lib/contrast/config/assess_configuration.rb +0 -93
  146. data/lib/contrast/config/assess_rules_configuration.rb +0 -32
  147. data/lib/contrast/config/root_configuration.rb +0 -90
  148. data/lib/contrast/config/ruby_configuration.rb +0 -81
  149. data/lib/contrast/config/service_configuration.rb +0 -49
  150. data/lib/contrast/utils/preflight_util.rb +0 -13
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/assess/rule/response/header_rule'
5
5
  require 'contrast/agent/assess/rule/response/body_rule'
6
- require 'contrast/agent/assess/rule/response/framework/rails_support'
6
+ require 'contrast/utils/object_share'
7
7
  require 'contrast/utils/string_utils'
8
8
  require 'json'
9
9
 
@@ -16,7 +16,6 @@ module Contrast
16
16
  # set incorrectly the cache-control header
17
17
  class CacheControl < HeaderRule
18
18
  include BodyRule
19
- include Framework::RailsSupport
20
19
  HEADER_KEYS = %w[Cache-Control].cs__freeze
21
20
  ACCEPTED_VALUES = [/no-store/, /no-cache/].cs__freeze
22
21
  DEFAULT_SAFE = false
@@ -33,66 +32,65 @@ module Contrast
33
32
  # Determine if the Response violates the Rule or not. If it does, return the evidence that proves it so.
34
33
  #
35
34
  # @param response [Contrast::Agent::Response] the response of the application
36
- # @return [Hash, nil] the evidence required to prove the violation of the rule
35
+ # @return [Hash<String,Array<Hash<String,String>>>, nil] the evidence required to prove the violation of
36
+ # the rule
37
37
  def violated? response
38
- return unless header?(response) && meta_tag?(response)
39
-
40
- header_evidence = header_evidence(response)
41
- return if header_evidence.nil?
42
-
43
- tag_evidence = tag_evidence(response)
44
- return if tag_evidence.nil?
45
-
46
- { DATA => [header_evidence, tag_evidence] }
47
- end
48
-
49
- # Is a cache-control header available?
50
- # @param response [Contrast::Agent::Response] the response of the application
51
- # @return [Boolean]
52
- def header? response
53
- cache_control = cache_control_from(response)
54
- framework_supported? ? !cache_control.blank? : !!cache_control
55
- end
38
+ cache_header = cache_control_from(response)
39
+ cache_meta = cache_meta_tags(response)
40
+
41
+ has_header = cache_header && !cache_header.blank?
42
+ has_meta = cache_meta.any?
43
+ # Because we're not safe by default, the rule should never hit this case, but we'll handle it just in
44
+ # case.
45
+ return { DATA => Contrast::Utils::ObjectShare::EMPTY_ARRAY.to_json } unless has_header || has_meta
46
+
47
+ evidence = []
48
+ # If we have a header tag, then we need to make sure it is set safely. If it is, there'll be no evidence
49
+ # and we can return as the header prevents violation.
50
+ if has_header
51
+ header_evidence = header_evidence(cache_header)
52
+ return unless header_evidence
53
+
54
+ evidence << header_evidence
55
+ end
56
56
 
57
- # Is a cache-control meta tag available?
58
- # @param response [Contrast::Agent::Response] the response of the application
59
- # @return [Boolean]
60
- def meta_tag? response
61
- return false if meta_tags(response).empty?
57
+ # If we have no header, or an unsafe header, then we need to check the meta tag to make sure it is set
58
+ # safely. If it is, there'll be no evidence and we can return as the meta tag prevents violation.
59
+ if has_meta
60
+ tag_evidence = tag_evidence(cache_meta)
61
+ return unless tag_evidence
62
62
 
63
- meta_tags(response).each do |tag|
64
- return true if meta_cache_tag?(tag[HTML_PROP])
63
+ evidence << tag_evidence
65
64
  end
66
65
 
67
- false
66
+ # Otherwise, we'll report the violation.
67
+ { DATA => evidence.to_json }
68
68
  end
69
69
 
70
- def meta_tags response
71
- return @_meta_tags if defined? @meta_tags
72
-
73
- @_meta_tags = html_elements(response.body&.split(HEAD_TAG)&.last, META_START_STR)
70
+ # @param response [Contrast::Agent::Response] the response of the application
71
+ # @return [Array<Hash<String,String>]
72
+ def cache_meta_tags response
73
+ html_elements(response.body&.split(HEAD_TAG)&.last, META_START_STR).
74
+ select { |tag| cache_control_tag?(tag[HTML_PROP]) }
74
75
  end
75
76
 
76
77
  # Process Header value to determine if it violates rule
77
- # @param response [Contrast::Agent::Response] the response of the application
78
- # @return [Hash, nil] the evidence hash or nil
79
- def header_evidence response
80
- cache_control = cache_control_from(response)
81
- value = framework_supported? ? cache_control : cache_control_to_s(cache_control)
78
+ # @param cache_control [String] the value of the Cache-Control header
79
+ # @return [Hash<String,String>, nil] the evidence hash or nil
80
+ def header_evidence cache_control
82
81
  # If header is valid, then this portion of the rule isn't violated.
83
- return if valid_header?(value)
82
+ return if valid_header?(cache_control)
84
83
 
85
84
  # evidence requires header value string, pull directly instead of rebuilding from hash
86
- evidence(HEADER_TYPE, NAME, value)
85
+ evidence(HEADER_TYPE, NAME, cache_control)
87
86
  end
88
87
 
89
88
  # Process Body to determine if cache control meta tag violates rule
90
- # @param response [Contrast::Agent::Response] the response of the application
91
- # @return [Hash, nil] the evidence hash or nil
92
- def tag_evidence response
93
- meta_tags(response).each do |tag|
94
- return evidence(META_TYPE, PRAGMA, tag[HTML_PROP]) if meta_cache_tag?(tag[HTML_PROP])
95
- end
89
+ # @param cache_meta_tags [Array<Hash>] the meta tags which contain Cache-Control values
90
+ # @return [Hash<String,String>, nil] the evidence hash or nil
91
+ def tag_evidence cache_meta_tags
92
+ violation = cache_meta_tags.find { |tag| !safe_meta_cache_tag?(tag[HTML_PROP]) }
93
+ violation ? evidence(META_TYPE, PRAGMA, violation[HTML_PROP]) : nil
96
94
  end
97
95
 
98
96
  def potential_elements section, element_start
@@ -107,13 +105,27 @@ module Contrast
107
105
  [/'no-cache'/i, /"no-cache"/i, /"no-store"/i, /'no-store'/i, /'cache-control'/i, /"cache-control"/i]
108
106
  end
109
107
 
108
+ # @param tag [String] the tag to check
109
+ # @return [Boolean] if the tag has cache-control settings or not
110
+ def cache_control_tag? tag
111
+ http_equiv_idx = tag =~ /http-equiv=/i
112
+ return false unless http_equiv_idx
113
+
114
+ content_idx = tag =~ /content=/i
115
+ return false unless content_idx
116
+
117
+ # determine the value of the http-equiv if it's cache-control
118
+ http_equiv_idx += 11
119
+ accepted_http_values.any? { |el| (tag =~ el) == http_equiv_idx }
120
+ end
121
+
110
122
  # Determine if the given metatag does not have a valid cache-control tag.
111
123
  # Meta tags has the option to set http-equiv and content to set the http response header
112
124
  # to define for the document
113
125
  #
114
126
  # @param tag [String] the meta tag
115
127
  # @return [Boolean, nil]
116
- def meta_cache_tag? tag
128
+ def safe_meta_cache_tag? tag
117
129
  # Here we should determine the index of the needed keys
118
130
  # http-equiv and content
119
131
  http_equiv_idx = tag =~ /http-equiv=/i
@@ -128,33 +140,36 @@ module Contrast
128
140
  return false unless is_valid
129
141
 
130
142
  content_idx += 8
131
- return false if accepted_values.any? { |value| (tag =~ value) == content_idx }
132
-
133
- true
143
+ accepted_values.any? { |value| (tag =~ value) == content_idx }
134
144
  end
135
145
 
136
- # This method accepts the violation and transforms it to the proper hash
137
- # before returning a violation
146
+ # This method accepts the violation and transforms it to the proper hash before returning a violation.
147
+ # Unlike other rules, this returns a complex structure to be converted to JSON on reporting -- do NOT cast
148
+ # it here as that'll result in extra escaping later.
138
149
  #
139
150
  # @param type [String] String of Header or META of the type
140
151
  # @param name [String] String of either cache-control or pragma
141
152
  # @param value [String] String of the violated value
153
+ # @return [Hash<String, String>]
142
154
  def evidence type, name, value
143
- { type: type, name: name, value: value }.to_json
155
+ { 'type' => type, 'name' => name, 'value' => value }
144
156
  end
145
157
 
158
+ # return the cache control value from the response, either as a Hash in later versions of Rails or as a
159
+ # String in all other frameworks/ response types (remember, response can be a few things).
160
+ #
161
+ # @param response [Contrast::Agent::Response]
162
+ # @return [String]
146
163
  def cache_control_from response
147
- # Rails 7 adds support for the cache_control header directly in the
148
- # rack response, we should use that value
149
- if framework_supported? && response.rack_response.cs__is_a?(Rack::Response)
150
- response.rack_response.cache_control
151
- else
152
- get_header_value(response)
153
- end
164
+ control = if response.rack_response.cs__is_a?(Rack::Response)
165
+ response.rack_response.cache_control
166
+ else
167
+ get_header_value(response)
168
+ end
169
+ control.cs__is_a?(Hash) ? cache_control_to_s(control) : control
154
170
  end
155
171
 
156
- # Rebuilds the String value of the Cache-Control Header
157
- # from the hash build in the Rack::Response
172
+ # Rebuilds the String value of the Cache-Control Header from the hash build in the Rack::Response
158
173
  #
159
174
  # @param hsh [Hash]
160
175
  # @return [String]
@@ -46,7 +46,7 @@ module Contrast
46
46
  settings["#{ key }Secure"] = !value.nil? && value_secure?(value) && value_safe?(value)
47
47
  settings["#{ key }Value"] = value.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : value
48
48
  end
49
- evidence(settings) if settings.value?(false)
49
+ evidence(settings.to_json) if settings.value?(false)
50
50
  end
51
51
 
52
52
  # Get the CSP values from and transforms them to key value hash
@@ -12,7 +12,12 @@ module Contrast
12
12
  module RailsSupport
13
13
  RAILS_VERSION = Gem::Version.new('7.0.0')
14
14
 
15
- def framework_supported?
15
+ # Some rules have features or settings that make them unsupported, meaning unnecessary or unavailable
16
+ # in that framework. For now, the only distinction required is Rails 7 or not, so that's what we'll
17
+ # report here.
18
+ #
19
+ # @return [Boolean] if the rule is unsupported by the framework
20
+ def rails_seven?
16
21
  return false unless defined?(::Rails)
17
22
 
18
23
  rails_version = ::Rails.version
@@ -2,9 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'rack'
5
- require 'contrast/agent/reporting/reporting_utilities/dtm_message'
6
5
  require 'contrast/utils/hash_digest'
7
- require 'contrast/utils/preflight_util'
8
6
  require 'contrast/utils/string_utils'
9
7
  require 'contrast/agent/assess/rule/response/base_rule'
10
8
 
@@ -16,7 +14,7 @@ module Contrast
16
14
  # These rules check the content of the HTTP Response to determine if something was set incorrectly or
17
15
  # insecurely in it.
18
16
  class HeaderRule < BaseRule
19
- HEADER_TYPE = 'header'
17
+ HEADER_TYPE = 'Header'
20
18
 
21
19
  # Rules discern which responses they can/should analyze.
22
20
  #
@@ -44,11 +42,13 @@ module Contrast
44
42
  # @param response [Contrast::Agent::Response] the response of the application
45
43
  # @return [Boolean]
46
44
  def headers? response
47
- response.headers&.any?
45
+ !!response.headers&.any?
48
46
  end
49
47
 
50
48
  protected
51
49
 
50
+ # @param response [Contrast::Agent::Response]
51
+ # @return [Object]
52
52
  def get_header_value response
53
53
  response_headers = response.headers
54
54
  values = response_headers.values_at(*cs__class::HEADER_KEYS, *cs__class::HEADER_KEYS.map(&:to_sym))
@@ -57,7 +57,7 @@ module Contrast
57
57
 
58
58
  # Determine if the value of the Response Header has a valid value
59
59
  #
60
- # @param header [Contrast::Agent::Response] a response header
60
+ # @param header [String] a response header
61
61
  # @return [Boolean] whether the header value is valid
62
62
  def valid_header? header
63
63
  cs__class::ACCEPTED_VALUES.any? { |val| val.match(header) }
@@ -14,7 +14,7 @@ module Contrast
14
14
  class HSTSHeader < HeaderRule
15
15
  HEADER_KEYS = %w[Strict-Transport-Security].cs__freeze
16
16
  ACCEPTED_VALUES = [/max-age=(\.)?\d+(\.\d*)?/].cs__freeze
17
- DEFAULT_SAFE = true
17
+ DEFAULT_SAFE = false
18
18
 
19
19
  def rule_id
20
20
  'hsts-header-missing'
@@ -24,7 +24,7 @@ module Contrast
24
24
  protected
25
25
 
26
26
  def analyze_response? response
27
- !framework_supported? && super
27
+ !rails_seven? && super
28
28
  end
29
29
  end
30
30
  end
@@ -12,7 +12,6 @@ module Contrast
12
12
  # have tightly coupled dependencies on each other.
13
13
  class Tracker
14
14
  PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
15
- KEEP_AGE = 600_000.cs__freeze # 10 minutes
16
15
 
17
16
  class << self
18
17
  # Retrieve the properties of the given Object, iff they exist.
@@ -60,12 +59,7 @@ module Contrast
60
59
  # Clean PROPERTIES_HASH of any values older than KEEP_AGE ms or
61
60
  # have nil properties
62
61
  def cleanup!
63
- PROPERTIES_HASH.delete_if do |_k, properties|
64
- return true if properties.nil?
65
- return false unless (event = properties&.event)
66
-
67
- KEEP_AGE <= (Contrast::Utils::Timer.now_ms - event.time)
68
- end
62
+ PROPERTIES_HASH.cleanup!
69
63
  end
70
64
  end
71
65
  end
@@ -0,0 +1,206 @@
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
+ # Given an array of exclusion matcher instances provides methods to
7
+ # determine if the exclusions apply to particular urls.
8
+ class Excluder # rubocop:disable Metrics/ClassLength
9
+ attr_reader :exclusions
10
+
11
+ def initialize exclusions = []
12
+ @exclusions = exclusions
13
+ end
14
+
15
+ # If an assess URL exclusion rule applies to the current url, *and* is defined as "All Rules"
16
+ # then we can avoid any tracking for the request.
17
+ #
18
+ # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
19
+ # return [Boolean]
20
+ def assess_excluded_by_url? request
21
+ request_path = request.path
22
+
23
+ assess_url_exclusions_for_all_rules.any? do |exclusion|
24
+ path_match?(exclusion, request_path)
25
+ end
26
+ end
27
+
28
+ # If an assess URL exclusion rule applies to the current url, *and* also covers the
29
+ # provided rule_id, then we can avoid tracking this entry.
30
+ #
31
+ # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
32
+ # @param rule_id [String]
33
+ # return [Boolean]
34
+ def assess_excluded_by_url_and_rule? request, rule_id
35
+ request_path = request.path
36
+
37
+ assess_url_exclusions.any? do |exclusion|
38
+ path_match?(exclusion, request_path) &&
39
+ (exclusion.assessment_rules.empty? || exclusion.assessment_rules.include?(rule_id))
40
+ end
41
+ end
42
+
43
+ # If an assess INPUT exclusion rule applies to the current url, *and* also covers all
44
+ # rules, then we can avoid tracking this entry.
45
+ #
46
+ # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
47
+ # @param rule_id [String]
48
+ # return [Boolean]
49
+ def assess_excluded_by_input? request, source_type, source_name
50
+ request_path = request.path
51
+
52
+ assess_input_exclusions_for_all_rules.any? do |exclusion|
53
+ input_match?(exclusion, source_type, source_name) && path_match?(exclusion, request_path)
54
+ end
55
+ end
56
+
57
+ # If an assess INPUT exclusion rule covers the provided rule_id *for all finding event sources*, then we
58
+ # can avoid tracking this entry. If any event source *isn't excluded* then we don't exclude the finding.
59
+ #
60
+ # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
61
+ # @param finding [Contrast::Agent::Reporting::Finding]
62
+ # @param rule [String]
63
+ # return [Boolean]
64
+ def assess_excluded_by_input_and_rule? request, finding, rule
65
+ return false if finding.events.empty?
66
+
67
+ # We need to check for url exclusions here for the input rules as the url exclusions
68
+ # that have already been checked didn't include the INPUT exclusions. So we look for
69
+ # any INPUT exclusions that apply to the current url and the suppleid rule.
70
+ path = request.path
71
+ rule_input_exclusions = assess_input_exclusions.select do |exclusion|
72
+ (exclusion.protection_rules.empty? || exclusion.protection_rules.include?(rule)) && path_match?(exclusion,
73
+ path)
74
+ end
75
+ return false if rule_input_exclusions.empty?
76
+
77
+ event_sources = finding.events.flat_map(&:event_sources)
78
+ event_sources.each do |event_source|
79
+ return false unless rule_input_exclusions.any? do |exclusion|
80
+ input_match?(exclusion, event_source.type, event_source.name) # rubocop:disable Security/Module/Name
81
+ end
82
+ end
83
+
84
+ # If we reach here, and we have event sources then all of them matched so we should exclude
85
+ # this finding. On the other hand, if there were no event sources we have nothing to exclude.
86
+ event_sources.any?
87
+ end
88
+
89
+ # If a protect URL exclusion rule applies to the current url, *and* is defined as "All Rules"
90
+ # then we can avoid using the rule for the request.
91
+ #
92
+ # @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
93
+ # return [Boolean]
94
+ def protect_excluded_by_url? request
95
+ request_path = request.path
96
+
97
+ protect_url_exclusions_for_all_rules.any? do |exclusion|
98
+ path_match?(exclusion, request_path)
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def assess_url_exclusions_for_all_rules
105
+ @_assess_url_exclusions_for_all_rules ||= assess_url_exclusions.select do |exclusion|
106
+ exclusion.assessment_rules.empty?
107
+ end
108
+ end
109
+
110
+ def assess_url_exclusions
111
+ @_assess_url_exclusions ||= assess_exclusions.select do |exclusion|
112
+ exclusion.type == Contrast::Api::Settings::Exclusion::ExclusionType::URL
113
+ end
114
+ end
115
+
116
+ def assess_input_exclusions_for_all_rules
117
+ @_assess_input_exclusions_for_all_rules ||= assess_input_exclusions.select do |exclusion|
118
+ exclusion.assessment_rules.empty?
119
+ end
120
+ end
121
+
122
+ def assess_input_exclusions
123
+ @_assess_input_exclusions ||= assess_exclusions.select do |exclusion|
124
+ exclusion.type == Contrast::Api::Settings::Exclusion::ExclusionType::INPUT
125
+ end
126
+ end
127
+
128
+ def assess_exclusions
129
+ @_assess_exclusions ||= @exclusions.select(&:assess)
130
+ end
131
+
132
+ def protect_url_exclusions_for_all_rules
133
+ @_protect_url_exclusions_for_all_rules ||= protect_url_exclusions.select do |exclusion|
134
+ exclusion.protection_rules.empty?
135
+ end
136
+ end
137
+
138
+ def protect_url_exclusions
139
+ @_protect_url_exclusions ||= protect_exclusions.select do |exclusion|
140
+ exclusion.type == Contrast::Api::Settings::Exclusion::ExclusionType::URL
141
+ end
142
+ end
143
+
144
+ def protect_exclusions
145
+ @_protect_exclusions ||= @exclusions.select(&:protect)
146
+ end
147
+
148
+ def path_match? exclusion, path
149
+ exclusion.wildcard_url || exclusion.urls.any? { |url| url.match?(path) }
150
+ end
151
+
152
+ def input_match? exclusion, source_type, source_name
153
+ case exclusion.input_type
154
+ when Contrast::Api::Settings::Exclusion::InputType::PARAMETER
155
+ input_match_parameter?(exclusion, source_type, source_name)
156
+ when Contrast::Api::Settings::Exclusion::InputType::COOKIE
157
+ input_match_cookie?(exclusion, source_type, source_name)
158
+ when Contrast::Api::Settings::Exclusion::InputType::HEADER
159
+ input_match_header?(exclusion, source_type, source_name)
160
+ when Contrast::Api::Settings::Exclusion::InputType::BODY
161
+ Contrast::Agent::Assess::Policy::SourceMethod::BODY_TYPE == source_type
162
+ when Contrast::Api::Settings::Exclusion::InputType::QUERYSTRING
163
+ Contrast::Agent::Assess::Policy::SourceMethod::QUERYSTRING_TYPE == source_type
164
+ else
165
+ false
166
+ end
167
+ end
168
+
169
+ def input_match_parameter? exclusion, source_type, source_name
170
+ return false unless [
171
+ Contrast::Agent::Assess::Policy::SourceMethod::PARAMETER_TYPE,
172
+ Contrast::Agent::Assess::Policy::SourceMethod::PARAMETER_KEY_TYPE
173
+ ].include?(source_type)
174
+
175
+ exclusion.wildcard_input || (exclusion.input_name == source_name) || regexp_match?(exclusion.input_name,
176
+ source_name)
177
+ end
178
+
179
+ def input_match_cookie? exclusion, source_type, source_name
180
+ return false unless [
181
+ Contrast::Agent::Assess::Policy::SourceMethod::COOKIE_TYPE,
182
+ Contrast::Agent::Assess::Policy::SourceMethod::COOKIE_KEY_TYPE
183
+ ].include?(source_type)
184
+
185
+ exclusion.wildcard_input || exclusion.input_name == source_name || regexp_match?(exclusion.input_name,
186
+ source_name)
187
+ end
188
+
189
+ def input_match_header? exclusion, source_type, source_name
190
+ return false unless [
191
+ Contrast::Agent::Assess::Policy::SourceMethod::HEADER_TYPE,
192
+ Contrast::Agent::Assess::Policy::SourceMethod::HEADER_KEY_TYPE
193
+ ].include?(source_type)
194
+
195
+ exclusion.wildcard_input || exclusion.input_name.casecmp(source_name).zero? || regexp_match?(
196
+ exclusion.input_name, source_name)
197
+ end
198
+
199
+ def regexp_match? possible_pattern, source_name
200
+ Regexp.new("^#{ possible_pattern }$").match?(source_name)
201
+ rescue RegexpError
202
+ false
203
+ end
204
+ end
205
+ end
206
+ end
@@ -11,6 +11,12 @@ module Contrast
11
11
  class ExclusionMatcher
12
12
  include Contrast::Components::Logger::InstanceMethods
13
13
 
14
+ extend Forwardable
15
+
16
+ attr_reader :protect, :assess, :urls, :wildcard_url, :wildcard_input
17
+
18
+ def_delegators :@exclusion, :type, :assessment_rules, :protection_rules, :input_type, :input_name
19
+
14
20
  # Create a matcher around an exclusion sent from TeamServer.
15
21
  #
16
22
  # @param excl [Contrast::Api::Settings::Exclusion]
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/reporting/reporting_events/architecture_component'
5
- require 'contrast/api/decorators/architecture_component'
6
5
  require 'contrast/components/logger'
7
6
  require 'contrast/utils/object_share'
8
7
  require 'contrast/utils/timer'
@@ -25,10 +24,7 @@ module Contrast
25
24
  LOCALHOST = 'localhost'
26
25
 
27
26
  class << self
28
- # Append the available database connection information to the message being sent to TeamServer. This message
29
- # may be a Contrast::Api::Dtm::Activity or Contrast::Agent::Reporting::ApplicationUpdate.
30
- # Both report the same
31
- # Contrast::Api::Dtm::ArchitectureComponent, but have different names for their fields.
27
+ # Append the available database connection information to the message being sent to TeamServer.
32
28
  #
33
29
  # @param activity_or_update [Contrast::Agent::Reporting::ApplicationUpdate]
34
30
  # @param hash_or_str [Hash, String] the database connection information
@@ -73,8 +69,8 @@ module Contrast
73
69
  # The classes we instrument in order to determine which, if any, database(s) an application connects to take
74
70
  # either a Hash or a String as a parameter. We install this one patch into all of those methods, letting our
75
71
  # code here, rather than at the patch level, make the determination of which path to call. We'll make that
76
- # decision and then parse the given configuration to create a Contrast::Api::Dtm::ArchitectureComponent for
77
- # reporting.
72
+ # decision and then parse the given configuration to create a
73
+ # Contrast::Agent::Reporting::ArchitectureComponent for reporting.
78
74
  #
79
75
  # @param hash_or_str [Hash, String]
80
76
  # @return [Array<Contrast::Agent::Reporting::ArchitectureComponent>, nil]
@@ -93,8 +89,8 @@ module Contrast
93
89
  end
94
90
  end
95
91
 
96
- # Convert the Hash used to create a database connection into an Contrast::Api::Dtm::ArchitectureComponent
97
- # understandable by TeamServer.
92
+ # Convert the Hash used to create a database connection into a
93
+ # Contrast::Agent::Reporting::ArchitectureComponent understandable by TeamServer.
98
94
  #
99
95
  # @param hash [Hash] the information used to open a database connection
100
96
  # @return [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
@@ -150,7 +146,7 @@ module Contrast
150
146
  end
151
147
 
152
148
  # Parse the given string used to connect to a database into its composite components, allowing for the
153
- # generation of a Contrast::Api::Dtm::ArchitectureComponent
149
+ # generation of a Contrast::Agent::Reporting::ArchitectureComponent
154
150
  #
155
151
  # @param str [String] the DB connection string
156
152
  # @return [Array<String>, nil] the adapter, hosts, and database
@@ -33,6 +33,10 @@ module Contrast
33
33
  clazz = object.is_a?(Module) ? object : object.cs__class
34
34
  class_name = clazz.cs__name
35
35
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
36
+ # invoke cmdi sub-rules.
37
+ rule.sub_rules.each do |sub_rule|
38
+ sub_rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, class_name, method, command)
39
+ end
36
40
  end
37
41
 
38
42
  protected
@@ -30,6 +30,7 @@ module Contrast
30
30
 
31
31
  sql = args[index]
32
32
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, database, sql)
33
+ rule.sub_rules.each { |sub_rule| sub_rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, sql) }
33
34
  end
34
35
 
35
36
  protected