contrast-agent 6.6.5 → 6.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.gitmodules +0 -3
- data/ext/cs__scope/cs__scope.c +1 -1
- data/lib/contrast/agent/assess/contrast_event.rb +2 -24
- data/lib/contrast/agent/assess/events/source_event.rb +7 -61
- data/lib/contrast/agent/assess/finalizers/hash.rb +11 -0
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +0 -55
- data/lib/contrast/agent/assess/policy/policy_node.rb +3 -3
- data/lib/contrast/agent/assess/policy/policy_node_utils.rb +0 -1
- data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
- data/lib/contrast/agent/assess/policy/source_method.rb +24 -1
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +7 -5
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +6 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +36 -132
- data/lib/contrast/agent/assess/policy/trigger_node.rb +3 -3
- data/lib/contrast/agent/assess/property/evented.rb +2 -12
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +42 -84
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -27
- data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -3
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +77 -62
- data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +1 -1
- data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +6 -1
- data/lib/contrast/agent/assess/rule/response/header_rule.rb +5 -5
- data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +1 -1
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +1 -7
- data/lib/contrast/agent/excluder.rb +206 -0
- data/lib/contrast/agent/exclusion_matcher.rb +6 -0
- data/lib/contrast/agent/inventory/database_config.rb +6 -10
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +4 -0
- data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +1 -0
- data/lib/contrast/agent/protect/rule/base.rb +49 -5
- data/lib/contrast/agent/protect/rule/base_service.rb +1 -0
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +18 -105
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +129 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +169 -0
- data/lib/contrast/agent/protect/rule/deserialization.rb +2 -1
- data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +51 -0
- data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +67 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +6 -31
- data/lib/contrast/agent/protect/rule/xxe.rb +2 -0
- data/lib/contrast/agent/protect/rule.rb +3 -1
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +6 -0
- data/lib/contrast/agent/reporting/details/sqli_dangerous_functions.rb +22 -0
- data/lib/contrast/agent/reporting/reporter.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -4
- data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -23
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +19 -49
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +12 -9
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +23 -21
- data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +5 -18
- data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +1 -0
- data/lib/contrast/{api/decorators/trace_taint_range_tags.rb → agent/reporting/reporting_events/finding_event_taint_range_tags.rb} +7 -6
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +10 -14
- data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +11 -0
- data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +3 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +11 -23
- data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +8 -26
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +4 -7
- data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +3 -3
- data/lib/contrast/agent/request.rb +2 -2
- data/lib/contrast/agent/request_context.rb +8 -20
- data/lib/contrast/agent/request_context_extend.rb +15 -36
- data/lib/contrast/agent/request_handler.rb +0 -8
- data/lib/contrast/agent/response.rb +0 -18
- data/lib/contrast/agent/telemetry/events/event.rb +1 -1
- data/lib/contrast/agent/telemetry/events/metric_event.rb +1 -1
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +3 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +2 -3
- data/lib/contrast/api/communication/socket_client.rb +4 -4
- data/lib/contrast/api/communication/speedracer.rb +4 -8
- data/lib/contrast/api/decorators/agent_startup.rb +5 -6
- data/lib/contrast/api/decorators/application_settings.rb +2 -1
- data/lib/contrast/api/decorators/application_startup.rb +6 -6
- data/lib/contrast/api/decorators/message.rb +0 -4
- data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -6
- data/lib/contrast/api/decorators.rb +0 -6
- data/lib/contrast/api/dtm.pb.rb +0 -489
- data/lib/contrast/components/agent.rb +16 -12
- data/lib/contrast/components/api.rb +10 -10
- data/lib/contrast/components/app_context.rb +3 -3
- data/lib/contrast/components/app_context_extend.rb +1 -1
- data/lib/contrast/components/assess.rb +92 -38
- data/lib/contrast/components/assess_rules.rb +36 -0
- data/lib/contrast/components/config.rb +54 -12
- data/lib/contrast/components/contrast_service.rb +8 -8
- data/lib/contrast/components/heap_dump.rb +1 -1
- data/lib/contrast/components/protect.rb +5 -5
- data/lib/contrast/components/ruby_component.rb +81 -0
- data/lib/contrast/components/sampling.rb +1 -1
- data/lib/contrast/components/security_logger.rb +23 -0
- data/lib/contrast/components/service.rb +55 -0
- data/lib/contrast/components/settings.rb +12 -4
- data/lib/contrast/config/base_configuration.rb +1 -1
- data/lib/contrast/config/protect_rules_configuration.rb +17 -3
- data/lib/contrast/config/server_configuration.rb +1 -1
- data/lib/contrast/config.rb +0 -6
- data/lib/contrast/configuration.rb +81 -17
- data/lib/contrast/extension/assess/exec_trigger.rb +3 -1
- data/lib/contrast/extension/assess/marshal.rb +3 -2
- data/lib/contrast/extension/assess/string.rb +0 -1
- data/lib/contrast/extension/extension.rb +1 -1
- data/lib/contrast/framework/base_support.rb +0 -5
- data/lib/contrast/framework/grape/support.rb +1 -23
- data/lib/contrast/framework/manager.rb +0 -10
- data/lib/contrast/framework/rails/support.rb +5 -58
- data/lib/contrast/framework/sinatra/support.rb +2 -21
- data/lib/contrast/logger/cef_log.rb +21 -3
- data/lib/contrast/logger/log.rb +1 -11
- data/lib/contrast/tasks/config.rb +4 -2
- data/lib/contrast/utils/assess/event_limit_utils.rb +5 -8
- data/lib/contrast/utils/assess/trigger_method_utils.rb +10 -18
- data/lib/contrast/utils/findings.rb +6 -5
- data/lib/contrast/utils/hash_digest.rb +9 -24
- data/lib/contrast/utils/hash_digest_extend.rb +6 -6
- data/lib/contrast/utils/invalid_configuration_util.rb +21 -58
- data/lib/contrast/utils/log_utils.rb +32 -8
- data/lib/contrast/utils/net_http_base.rb +2 -2
- data/lib/contrast/utils/patching/policy/patch_utils.rb +3 -2
- data/lib/contrast/utils/stack_trace_utils.rb +0 -25
- data/lib/contrast/utils/string_utils.rb +9 -0
- data/lib/contrast/utils/telemetry_client.rb +13 -7
- data/lib/contrast.rb +5 -10
- metadata +22 -28
- data/lib/contrast/agent/reporting/reporting_events/trace_event_source.rb +0 -30
- data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -36
- data/lib/contrast/api/decorators/activity.rb +0 -33
- data/lib/contrast/api/decorators/architecture_component.rb +0 -36
- data/lib/contrast/api/decorators/finding.rb +0 -29
- data/lib/contrast/api/decorators/route_coverage.rb +0 -91
- data/lib/contrast/api/decorators/trace_event.rb +0 -120
- data/lib/contrast/api/decorators/trace_event_object.rb +0 -63
- data/lib/contrast/api/decorators/trace_event_signature.rb +0 -69
- data/lib/contrast/api/decorators/trace_taint_range.rb +0 -52
- data/lib/contrast/config/assess_configuration.rb +0 -93
- data/lib/contrast/config/assess_rules_configuration.rb +0 -32
- data/lib/contrast/config/root_configuration.rb +0 -90
- data/lib/contrast/config/ruby_configuration.rb +0 -81
- data/lib/contrast/config/service_configuration.rb +0 -49
- 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/
|
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
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
return true if meta_cache_tag?(tag[HTML_PROP])
|
63
|
+
evidence << tag_evidence
|
65
64
|
end
|
66
65
|
|
67
|
-
|
66
|
+
# Otherwise, we'll report the violation.
|
67
|
+
{ DATA => evidence.to_json }
|
68
68
|
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
78
|
-
# @return [Hash, nil] the evidence hash or nil
|
79
|
-
def header_evidence
|
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?(
|
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,
|
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
|
91
|
-
# @return [Hash, nil] the evidence hash or nil
|
92
|
-
def tag_evidence
|
93
|
-
|
94
|
-
|
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
|
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
|
-
|
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
|
-
#
|
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
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
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 = '
|
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 [
|
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) }
|
@@ -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.
|
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.
|
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
|
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
|
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::
|
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
|