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