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.
- 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
|