contrast-agent 6.1.0 → 6.1.1
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/.simplecov +1 -1
- data/Rakefile +1 -1
- data/ext/build_funchook.rb +3 -3
- data/ext/cs__assess_basic_object/cs__assess_basic_object.c +5 -1
- data/ext/extconf_common.rb +1 -1
- data/lib/contrast/agent/assess/finalizers/hash.rb +2 -2
- data/lib/contrast/agent/assess/policy/policy.rb +9 -10
- data/lib/contrast/agent/assess/policy/policy_node.rb +9 -10
- data/lib/contrast/agent/assess/policy/propagation_method.rb +3 -3
- data/lib/contrast/agent/assess/policy/propagation_node.rb +2 -3
- data/lib/contrast/agent/assess/policy/propagator/base.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/buffer.rb +2 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/split.rb +2 -2
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +1 -1
- data/lib/contrast/agent/assess/policy/source_node.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +7 -7
- data/lib/contrast/agent/assess/policy/trigger_node.rb +16 -16
- data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/evented.rb +2 -2
- data/lib/contrast/agent/assess/property/tagged.rb +2 -2
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +6 -8
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +6 -7
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +5 -5
- data/lib/contrast/agent/assess/rule/response/base_rule.rb +2 -3
- data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +8 -9
- data/lib/contrast/agent/assess/rule/response/click_jacking_header_rule.rb +4 -4
- data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +6 -6
- data/lib/contrast/agent/assess/rule/response/csp_header_missing_rule.rb +4 -4
- data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +4 -4
- data/lib/contrast/agent/assess/rule/response/x_content_type_header_rule.rb +4 -4
- data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +3 -4
- data/lib/contrast/agent/assess/tag.rb +13 -14
- data/lib/contrast/agent/at_exit_hook.rb +12 -1
- data/lib/contrast/agent/middleware.rb +6 -3
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -3
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +2 -2
- data/lib/contrast/agent/patching/policy/method_policy_extend.rb +4 -4
- data/lib/contrast/agent/patching/policy/patch.rb +9 -9
- data/lib/contrast/agent/patching/policy/patch_status.rb +10 -3
- data/lib/contrast/agent/patching/policy/policy.rb +13 -15
- data/lib/contrast/agent/patching/policy/policy_node.rb +19 -21
- data/lib/contrast/agent/patching/policy/trigger_node.rb +1 -1
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +125 -125
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +2 -2
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -1
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +4 -4
- data/lib/contrast/agent/protect/rule/base.rb +30 -18
- data/lib/contrast/agent/protect/rule/base_service.rb +31 -14
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +16 -9
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +3 -3
- data/lib/contrast/agent/protect/rule/default_scanner.rb +2 -1
- data/lib/contrast/agent/protect/rule/deserialization.rb +18 -7
- data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +74 -74
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +71 -53
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +3 -3
- data/lib/contrast/agent/protect/rule/no_sqli.rb +15 -16
- data/lib/contrast/agent/protect/rule/path_traversal.rb +13 -3
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +1 -1
- data/lib/contrast/agent/protect/rule/sqli.rb +16 -23
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +61 -61
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_matcher.rb +29 -29
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +32 -32
- data/lib/contrast/agent/protect/rule/xss.rb +17 -0
- data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +14 -13
- data/lib/contrast/agent/protect/rule/xxe.rb +25 -3
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +36 -36
- data/lib/contrast/agent/reporting/masker/masker.rb +10 -10
- data/lib/contrast/agent/reporting/masker/masker_utils.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +8 -10
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +53 -5
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +25 -19
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +129 -17
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +20 -21
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_stack.rb +22 -0
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +26 -12
- data/lib/contrast/agent/reporting/reporting_events/application_inventory.rb +5 -5
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +7 -5
- data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +4 -10
- data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +6 -6
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +7 -7
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +15 -15
- data/lib/contrast/agent/reporting/settings/application_settings.rb +1 -1
- data/lib/contrast/agent/reporting/settings/assess.rb +5 -5
- data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +3 -3
- data/lib/contrast/agent/reporting/settings/exclusions.rb +3 -3
- data/lib/contrast/agent/reporting/settings/protect.rb +20 -5
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +5 -5
- data/lib/contrast/agent/reporting/settings/reaction.rb +3 -3
- data/lib/contrast/agent/reporting/settings/sensitive_data_masking.rb +2 -2
- data/lib/contrast/agent/reporting/settings/sensitive_data_masking_rule.rb +2 -2
- data/lib/contrast/agent/reporting/settings/server_features.rb +2 -2
- data/lib/contrast/agent/request.rb +2 -2
- data/lib/contrast/agent/request_context.rb +23 -19
- data/lib/contrast/agent/request_context_extend.rb +10 -23
- data/lib/contrast/agent/request_handler.rb +1 -1
- data/lib/contrast/agent/rule_set.rb +2 -2
- data/lib/contrast/agent/scope.rb +1 -1
- data/lib/contrast/agent/telemetry/base.rb +9 -5
- data/lib/contrast/agent/telemetry/events/exceptions/obfuscate.rb +119 -0
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -2
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +1 -1
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +1 -1
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +16 -18
- data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +2 -2
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +1 -1
- data/lib/contrast/api/communication/service_lifecycle.rb +1 -1
- data/lib/contrast/api/communication/socket.rb +1 -1
- data/lib/contrast/api/communication/socket_client.rb +1 -1
- data/lib/contrast/api/communication/speedracer.rb +2 -2
- data/lib/contrast/api/decorators/agent_startup.rb +10 -9
- data/lib/contrast/api/decorators/application_settings.rb +1 -1
- data/lib/contrast/api/decorators/application_startup.rb +4 -4
- data/lib/contrast/api/decorators/response_type.rb +4 -17
- data/lib/contrast/components/agent.rb +1 -1
- data/lib/contrast/components/base.rb +1 -1
- data/lib/contrast/components/config.rb +6 -6
- data/lib/contrast/components/contrast_service.rb +4 -1
- data/lib/contrast/components/sampling.rb +1 -1
- data/lib/contrast/components/settings.rb +52 -28
- data/lib/contrast/config/assess_rules_configuration.rb +1 -1
- data/lib/contrast/config/protect_rules_configuration.rb +1 -1
- data/lib/contrast/config/root_configuration.rb +1 -1
- data/lib/contrast/configuration.rb +4 -4
- data/lib/contrast/extension/assess/array.rb +1 -1
- data/lib/contrast/extension/assess/erb.rb +1 -1
- data/lib/contrast/extension/assess/marshal.rb +1 -1
- data/lib/contrast/extension/assess/string.rb +1 -1
- data/lib/contrast/extension/extension.rb +2 -2
- data/lib/contrast/framework/base_support.rb +8 -8
- data/lib/contrast/framework/grape/support.rb +3 -3
- data/lib/contrast/framework/manager.rb +5 -5
- data/lib/contrast/framework/manager_extend.rb +1 -1
- data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +14 -3
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +3 -3
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/support.rb +2 -2
- data/lib/contrast/framework/sinatra/support.rb +1 -1
- data/lib/contrast/logger/aliased_logging.rb +29 -22
- data/lib/contrast/logger/cef_log.rb +14 -14
- data/lib/contrast/logger/format.rb +1 -1
- data/lib/contrast/logger/log.rb +8 -8
- data/lib/contrast/tasks/config.rb +12 -12
- data/lib/contrast/tasks/service.rb +2 -2
- data/lib/contrast/utils/assess/tracking_util.rb +4 -4
- data/lib/contrast/utils/class_util.rb +4 -4
- data/lib/contrast/utils/findings.rb +3 -3
- data/lib/contrast/utils/hash_digest.rb +6 -7
- data/lib/contrast/utils/head_dump_utils_extend.rb +1 -1
- data/lib/contrast/utils/invalid_configuration_util.rb +1 -1
- data/lib/contrast/utils/log_utils.rb +4 -4
- data/lib/contrast/utils/lru_cache.rb +1 -1
- data/lib/contrast/utils/metrics_hash.rb +1 -1
- data/lib/contrast/utils/middleware_utils.rb +5 -5
- data/lib/contrast/utils/net_http_base.rb +4 -4
- data/lib/contrast/utils/os.rb +1 -1
- data/lib/contrast/utils/patching/policy/patch_utils.rb +2 -2
- data/lib/contrast/utils/request_utils.rb +2 -2
- data/lib/contrast/utils/sha256_builder.rb +4 -4
- data/lib/contrast/utils/stack_trace_utils.rb +31 -13
- data/lib/contrast/utils/telemetry.rb +6 -9
- data/lib/contrast/utils/telemetry_client.rb +5 -5
- data/lib/contrast/utils/telemetry_hash.rb +1 -1
- data/lib/contrast/utils/telemetry_identifier.rb +2 -2
- data/lib/contrast/utils/timer.rb +1 -1
- data/resources/assess/policy.json +1 -1
- metadata +15 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e548b3df856f888229a5125c351404c3f5b04fcd7ecb5c91937f480ce91c9d42
|
|
4
|
+
data.tar.gz: 00c6ee55996e75602d1b80da133a362d3205cb5ef5de0f5c1a35b214528fea2a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad8d0f64dc798a22072e977cf0ff83ba47df54a3f24802386a52d2d338f365a02cac5db943868a55ecdf7641e44384f580e55e5d1b05a78522e88c5244013346
|
|
7
|
+
data.tar.gz: 804a464d38a9e023da53ce82009573571a6197c9f3f00642988457ac32b8ecc8631ea0a3cfd8bc678918df8afbb1cb24c51fa79dff1fe8fb278c5e966bff74e1
|
data/.simplecov
CHANGED
|
@@ -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
|
-
SimpleCov.minimum_coverage
|
|
4
|
+
SimpleCov.minimum_coverage(line: 94)
|
|
5
5
|
SimpleCov.start do
|
|
6
6
|
add_filter '/spec/'
|
|
7
7
|
enable_coverage :branch
|
data/Rakefile
CHANGED
data/ext/build_funchook.rb
CHANGED
|
@@ -52,13 +52,13 @@ unless find_header('funchook.h', ext_path)
|
|
|
52
52
|
|
|
53
53
|
TARGET_PATHS.each do |target_path|
|
|
54
54
|
unless File.writable?(target_path)
|
|
55
|
-
puts
|
|
55
|
+
puts("Unable to copy into #{ target_path } - directory not writable")
|
|
56
56
|
next
|
|
57
57
|
end
|
|
58
|
-
puts
|
|
58
|
+
puts("Copying #{ source_file_path } into #{ target_path }")
|
|
59
59
|
FileUtils.cp(source_file_path, target_path)
|
|
60
60
|
rescue StandardError
|
|
61
|
-
puts
|
|
61
|
+
puts("Error while copying #{ source_file } to #{ target_path }")
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* }
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
VALUE contrast_check_and_register_instance_patch(
|
|
21
|
+
const char *module_name, const char *method_name,
|
|
22
|
+
VALUE(c_fn)(const int, VALUE *, const VALUE));
|
|
23
|
+
|
|
20
24
|
void contrast_assess_instance_eval_trigger_check(VALUE self, VALUE source,
|
|
21
25
|
VALUE ret) {
|
|
22
26
|
rb_funcall(basic_eval_trigger, instance_trigger_check_method, 3, self,
|
|
@@ -61,6 +65,6 @@ void Init_cs__assess_basic_object(void) {
|
|
|
61
65
|
* but if someone else patched BasicObject#instance_eval,
|
|
62
66
|
* IDK if this is intentional... noting it. -ajm
|
|
63
67
|
*/
|
|
64
|
-
|
|
68
|
+
contrast_check_and_register_instance_patch("BasicObject", "instance_eval",
|
|
65
69
|
contrast_assess_basic_object_instance_eval);
|
|
66
70
|
}
|
data/ext/extconf_common.rb
CHANGED
|
@@ -22,11 +22,11 @@ module Contrast
|
|
|
22
22
|
else
|
|
23
23
|
ObjectSpace.define_finalizer(key, finalizing_proc)
|
|
24
24
|
end
|
|
25
|
-
super
|
|
25
|
+
super(key.__id__, obj)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def [] key
|
|
29
|
-
super
|
|
29
|
+
super(key.__id__)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Something is trackable if it is not a collection and either not frozen or it was frozen after we put a
|
|
@@ -18,11 +18,20 @@ module Contrast
|
|
|
18
18
|
# This is just a holder for our policy. Takes the policy JSON and
|
|
19
19
|
# converts it into hashes that we can access nicely
|
|
20
20
|
class Policy < Contrast::Agent::Patching::Policy::Policy
|
|
21
|
+
PROVIDER_CLASSES = [
|
|
22
|
+
Contrast::Agent::Assess::Rule::Provider::HardcodedKey,
|
|
23
|
+
Contrast::Agent::Assess::Rule::Provider::HardcodedPassword
|
|
24
|
+
].cs__freeze
|
|
21
25
|
# Indicates the folder in `resources` where this policy lives.
|
|
22
26
|
def self.policy_folder
|
|
23
27
|
'assess'
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
def initialize
|
|
31
|
+
super
|
|
32
|
+
load_providers
|
|
33
|
+
end
|
|
34
|
+
|
|
26
35
|
# Indicates is this feature has been disabled by the configuration,
|
|
27
36
|
# read at startup, and therefore can never be enabled.
|
|
28
37
|
def disabled_globally?
|
|
@@ -33,11 +42,6 @@ module Contrast
|
|
|
33
42
|
Contrast::Agent::Assess::Policy::TriggerNode
|
|
34
43
|
end
|
|
35
44
|
|
|
36
|
-
def initialize
|
|
37
|
-
super
|
|
38
|
-
load_providers
|
|
39
|
-
end
|
|
40
|
-
|
|
41
45
|
# Our policy for dataflow rules is a 'dope ass' JSON file. Rather than
|
|
42
46
|
# hard code in a bunch of things to monkey patch, we let the JSON file
|
|
43
47
|
# define the conditions in which sources, propagators, and triggers are
|
|
@@ -88,11 +92,6 @@ module Contrast
|
|
|
88
92
|
providers[instance.rule_id] = instance
|
|
89
93
|
end
|
|
90
94
|
end
|
|
91
|
-
|
|
92
|
-
PROVIDER_CLASSES = [
|
|
93
|
-
Contrast::Agent::Assess::Rule::Provider::HardcodedKey,
|
|
94
|
-
Contrast::Agent::Assess::Rule::Provider::HardcodedPassword
|
|
95
|
-
].cs__freeze
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
97
|
end
|
|
@@ -14,6 +14,14 @@ module Contrast
|
|
|
14
14
|
# Ruby object, allowing for dynamic patching over hardcoded patching.
|
|
15
15
|
class PolicyNode < Contrast::Agent::Patching::Policy::PolicyNode
|
|
16
16
|
include PolicyNodeUtils
|
|
17
|
+
JSON_TAGS = 'tags'
|
|
18
|
+
JSON_DATAFLOW = 'dataflow'
|
|
19
|
+
# The keys used to read from policy.json to create the individual
|
|
20
|
+
# policy nodes. These are common across node types
|
|
21
|
+
JSON_SOURCE = 'source'
|
|
22
|
+
ALL_TYPE = 'A'
|
|
23
|
+
JSON_TARGET = 'target'
|
|
24
|
+
TO_MARKER = '2'
|
|
17
25
|
|
|
18
26
|
attr_accessor :tags, :type
|
|
19
27
|
attr_reader :sources, :targets, :source_string, :target_string
|
|
@@ -45,7 +53,7 @@ module Contrast
|
|
|
45
53
|
@sources = convert_policy_markers(source_string)
|
|
46
54
|
@targets = convert_policy_markers(target_string)
|
|
47
55
|
@_use_original_object = ORIGINAL_OBJECT_METHODS.include?(@method_name)
|
|
48
|
-
@_use_original_on_bang_method = assign_on_bang_check
|
|
56
|
+
@_use_original_on_bang_method = assign_on_bang_check(policy_hash)
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
def assign_on_bang_check policy_hash
|
|
@@ -116,8 +124,6 @@ module Contrast
|
|
|
116
124
|
end
|
|
117
125
|
end
|
|
118
126
|
|
|
119
|
-
ALL_TYPE = 'A'
|
|
120
|
-
TO_MARKER = '2'
|
|
121
127
|
# Convert our action, built from our source and target, into
|
|
122
128
|
# the TS appropriate action. That's a single source to single
|
|
123
129
|
# target marker (A,O,P,R)
|
|
@@ -149,13 +155,6 @@ module Contrast
|
|
|
149
155
|
@event_action
|
|
150
156
|
end
|
|
151
157
|
|
|
152
|
-
# The keys used to read from policy.json to create the individual
|
|
153
|
-
# policy nodes. These are common across node types
|
|
154
|
-
JSON_SOURCE = 'source'
|
|
155
|
-
JSON_TARGET = 'target'
|
|
156
|
-
JSON_TAGS = 'tags'
|
|
157
|
-
JSON_DATAFLOW = 'dataflow'
|
|
158
|
-
|
|
159
158
|
# This method will check if a method is fit to use it's original object and
|
|
160
159
|
# that the method is without bang - it does not change the source, but rather
|
|
161
160
|
# creates a copy of it.
|
|
@@ -216,8 +216,8 @@ module Contrast
|
|
|
216
216
|
# If we are using the original object tracking, the preshift object is not created.
|
|
217
217
|
# Instead identify the source as the original object itself and propagate with it.
|
|
218
218
|
source = propagation_node.use_original_object? ? propagation_data.object : preshift
|
|
219
|
-
handle_propagation
|
|
220
|
-
update_properties
|
|
219
|
+
handle_propagation(propagation_class, propagation_node, source, target)
|
|
220
|
+
update_properties(restore_frozen_state, propagation_node, target, propagation_data, ret)
|
|
221
221
|
end
|
|
222
222
|
|
|
223
223
|
def handle_propagation propagation_class, propagation_node, source, target
|
|
@@ -230,7 +230,7 @@ module Contrast
|
|
|
230
230
|
|
|
231
231
|
def update_properties restore_frozen_state, propagation_node, target, propagation_data, ret
|
|
232
232
|
if propagation_node.use_original_on_bang_method?
|
|
233
|
-
properties = use_original_object_properties
|
|
233
|
+
properties = use_original_object_properties(propagation_data)
|
|
234
234
|
|
|
235
235
|
return unless properties
|
|
236
236
|
else
|
|
@@ -22,6 +22,8 @@ module Contrast
|
|
|
22
22
|
attr_reader :untags, :patch_method
|
|
23
23
|
attr_accessor :action, :patch_class
|
|
24
24
|
|
|
25
|
+
TAGGER = 'Tagger'
|
|
26
|
+
PROPAGATOR = 'Propagator'
|
|
25
27
|
# Most things here carry over from PolicyNode.
|
|
26
28
|
# A couple things are new / have new rules
|
|
27
29
|
#
|
|
@@ -41,9 +43,6 @@ module Contrast
|
|
|
41
43
|
validate
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
TAGGER = 'Tagger'
|
|
45
|
-
PROPAGATOR = 'Propagator'
|
|
46
|
-
|
|
47
46
|
def node_class
|
|
48
47
|
@_node_class ||= tagger? ? TAGGER : PROPAGATOR
|
|
49
48
|
end
|
|
@@ -26,7 +26,7 @@ module Contrast
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def propagate _propagation_node, _preshift, _target
|
|
29
|
-
raise
|
|
29
|
+
raise(NoMethodError("Expected Base propagator subclass: #{ cs__class } to implement #propagate"))
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -106,7 +106,8 @@ module Contrast
|
|
|
106
106
|
ret
|
|
107
107
|
else
|
|
108
108
|
# SELECT
|
|
109
|
-
Contrast::Agent::Assess::Policy::Propagator::Select.select_tagger
|
|
109
|
+
Contrast::Agent::Assess::Policy::Propagator::Select.select_tagger(propagation_node, preshift, ret,
|
|
110
|
+
nil)
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
end
|
|
@@ -31,7 +31,7 @@ module Contrast
|
|
|
31
31
|
::Contrast::ASSESS.tainted_columns[class_name] = tainted_columns.keys
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
Contrast::Agent::Assess::Policy::DynamicSourceFactory.create_sources
|
|
34
|
+
Contrast::Agent::Assess::Policy::DynamicSourceFactory.create_sources(class_type, tainted_columns)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
private
|
|
@@ -41,7 +41,7 @@ module Contrast
|
|
|
41
41
|
source = find_source(propagation_node.sources[0], preshift)
|
|
42
42
|
return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
|
|
43
43
|
|
|
44
|
-
update_element_properties
|
|
44
|
+
update_element_properties(propagation_node, target, preshift, source_properties)
|
|
45
45
|
nil
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -97,7 +97,7 @@ module Contrast
|
|
|
97
97
|
def instrument_string_split
|
|
98
98
|
@_instrument_string_split ||= begin
|
|
99
99
|
if ::Contrast::AGENT.patch_yield? && Funchook.available?
|
|
100
|
-
require
|
|
100
|
+
require('cs__assess_yield_track/cs__assess_yield_track')
|
|
101
101
|
end
|
|
102
102
|
true
|
|
103
103
|
rescue StandardError => e
|
|
@@ -40,7 +40,7 @@ module Contrast
|
|
|
40
40
|
source = preshift.object
|
|
41
41
|
args = preshift.args
|
|
42
42
|
properties.splat_from(source, ret)
|
|
43
|
-
event_data = Contrast::Agent::Assess::Events::EventData.new
|
|
43
|
+
event_data = Contrast::Agent::Assess::Events::EventData.new(patcher, ret, source, ret, args)
|
|
44
44
|
properties.build_event(event_data)
|
|
45
45
|
ret
|
|
46
46
|
end
|
|
@@ -15,13 +15,13 @@ module Contrast
|
|
|
15
15
|
|
|
16
16
|
JSON_TYPE = 'type'
|
|
17
17
|
SOURCE_TAG = 'UNTRUSTED'
|
|
18
|
+
SOURCE = 'Source'
|
|
18
19
|
def initialize source_hash = {}
|
|
19
20
|
super(source_hash)
|
|
20
21
|
@type = source_hash[JSON_TYPE]
|
|
21
22
|
@tags << SOURCE_TAG
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
SOURCE = 'Source'
|
|
25
25
|
def node_class
|
|
26
26
|
SOURCE
|
|
27
27
|
end
|
|
@@ -93,7 +93,7 @@ module Contrast
|
|
|
93
93
|
content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
|
|
94
94
|
|
|
95
95
|
if content_type.nil? && trigger_node.collectable?
|
|
96
|
-
Contrast::Agent::FINDINGS.collect_finding
|
|
96
|
+
Contrast::Agent::FINDINGS.collect_finding(trigger_node, source, object, ret, *args)
|
|
97
97
|
return
|
|
98
98
|
end
|
|
99
99
|
|
|
@@ -106,8 +106,8 @@ module Contrast
|
|
|
106
106
|
handle_new_finding(trigger_node, source, object, ret, request, *args)
|
|
107
107
|
else # TODO: RUBY-1438 -- remove
|
|
108
108
|
finding = Contrast::Api::Dtm::Finding.new
|
|
109
|
-
event_data = Contrast::Agent::Assess::Events::EventData.new
|
|
110
|
-
append_to_finding
|
|
109
|
+
event_data = Contrast::Agent::Assess::Events::EventData.new(trigger_node, source, object, ret, args)
|
|
110
|
+
append_to_finding(finding, event_data, request)
|
|
111
111
|
logger.trace('Finding created', node_id: trigger_node.id, source_id: source.__id__,
|
|
112
112
|
rule: trigger_node.rule_id)
|
|
113
113
|
report_finding(finding, request)
|
|
@@ -143,14 +143,14 @@ module Contrast
|
|
|
143
143
|
|
|
144
144
|
# If we're out of request context, then we need to report this finding ourselves,
|
|
145
145
|
# so we'll send it in the one-off activity we created.
|
|
146
|
-
Contrast::Agent.messaging_queue
|
|
146
|
+
Contrast::Agent.messaging_queue&.send_event_eventually(activity)
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
def handle_new_finding trigger_node, source, object, ret, request, *args
|
|
150
150
|
# sent to reporter
|
|
151
151
|
# here we will generate new type of finding
|
|
152
|
-
ruby_finding = Contrast::Agent::Reporting::Finding.new
|
|
153
|
-
ruby_finding.attach_data
|
|
152
|
+
ruby_finding = Contrast::Agent::Reporting::Finding.new(trigger_node.rule_id)
|
|
153
|
+
ruby_finding.attach_data(trigger_node, source, object, ret, request, *args)
|
|
154
154
|
hash_code = Contrast::Utils::HashDigest.generate_event_hash(ruby_finding, source, request)
|
|
155
155
|
ruby_finding.hash_code = hash_code
|
|
156
156
|
|
|
@@ -209,7 +209,7 @@ module Contrast
|
|
|
209
209
|
properties = Contrast::Agent::Assess::Tracker.properties(source)
|
|
210
210
|
return unless properties
|
|
211
211
|
|
|
212
|
-
build_events
|
|
212
|
+
build_events(finding, properties.event) if properties.event
|
|
213
213
|
|
|
214
214
|
# Google::Protobuf::Map doesn't support merge!, so we have to do this long form
|
|
215
215
|
source_props = properties.properties
|
|
@@ -28,6 +28,21 @@ module Contrast
|
|
|
28
28
|
|
|
29
29
|
attr_reader :rule_id, :required_tags, :disallowed_tags, :good_value, :bad_value
|
|
30
30
|
|
|
31
|
+
ENCODER_START = 'CUSTOM_ENCODED_'
|
|
32
|
+
# By default, any rule will be triggered if the source
|
|
33
|
+
# of the rule event has an untrusted tag range that is
|
|
34
|
+
# not covered by one of its disallowed tags.
|
|
35
|
+
UNTRUSTED = 'UNTRUSTED'
|
|
36
|
+
TRIGGER = 'Trigger'
|
|
37
|
+
VALIDATOR_START = 'CUSTOM_VALIDATED_'
|
|
38
|
+
# If a level 1 rule comes from TeamServer, it will have the
|
|
39
|
+
# tag 'custom-encoder-#{ name }' or 'custom-validator-#{ name }'.
|
|
40
|
+
# All rules should take this into account.
|
|
41
|
+
# Additionally, if something is marked 'limited-chars' it means
|
|
42
|
+
# it has been properly vetted to not contain dangerous input.
|
|
43
|
+
LIMITED_CHARS = 'LIMITED_CHARS'
|
|
44
|
+
CUSTOM_ENCODED = 'CUSTOM_ENCODED'
|
|
45
|
+
CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
|
|
31
46
|
def initialize trigger_hash = {}, rule_hash = {}
|
|
32
47
|
super(trigger_hash)
|
|
33
48
|
good_value = trigger_hash[JSON_GOOD_VALUE]
|
|
@@ -46,7 +61,6 @@ module Contrast
|
|
|
46
61
|
validate
|
|
47
62
|
end
|
|
48
63
|
|
|
49
|
-
TRIGGER = 'Trigger'
|
|
50
64
|
def node_class
|
|
51
65
|
TRIGGER
|
|
52
66
|
end
|
|
@@ -138,10 +152,6 @@ module Contrast
|
|
|
138
152
|
|
|
139
153
|
private
|
|
140
154
|
|
|
141
|
-
# By default, any rule will be triggered if the source
|
|
142
|
-
# of the rule event has an untrusted tag range that is
|
|
143
|
-
# not covered by one of its disallowed tags.
|
|
144
|
-
UNTRUSTED = 'UNTRUSTED'
|
|
145
155
|
def populate_tags required_tags
|
|
146
156
|
return unless dataflow?
|
|
147
157
|
|
|
@@ -150,16 +160,6 @@ module Contrast
|
|
|
150
160
|
@required_tags << UNTRUSTED
|
|
151
161
|
end
|
|
152
162
|
|
|
153
|
-
ENCODER_START = 'CUSTOM_ENCODED_'
|
|
154
|
-
VALIDATOR_START = 'CUSTOM_VALIDATED_'
|
|
155
|
-
# If a level 1 rule comes from TeamServer, it will have the
|
|
156
|
-
# tag 'custom-encoder-#{ name }' or 'custom-validator-#{ name }'.
|
|
157
|
-
# All rules should take this into account.
|
|
158
|
-
# Additionally, if something is marked 'limited-chars' it means
|
|
159
|
-
# it has been properly vetted to not contain dangerous input.
|
|
160
|
-
LIMITED_CHARS = 'LIMITED_CHARS'
|
|
161
|
-
CUSTOM_ENCODED = 'CUSTOM_ENCODED'
|
|
162
|
-
CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
|
|
163
163
|
def populate_disallowed disallowed_tags
|
|
164
164
|
return unless dataflow?
|
|
165
165
|
|
|
@@ -195,7 +195,7 @@ module Contrast
|
|
|
195
195
|
chunking = false
|
|
196
196
|
ranges = []
|
|
197
197
|
# find the start and end range of required tags:
|
|
198
|
-
search_ranges = find_required_ranges
|
|
198
|
+
search_ranges = find_required_ranges(properties, required_tags)
|
|
199
199
|
start_range = search_ranges.first
|
|
200
200
|
end_range = search_ranges.last + 1
|
|
201
201
|
|
|
@@ -42,8 +42,8 @@ module Contrast
|
|
|
42
42
|
return unless event.source_type
|
|
43
43
|
return unless (current_request = Contrast::Agent::REQUEST_TRACKER.current)
|
|
44
44
|
|
|
45
|
-
append_to_dtm
|
|
46
|
-
append_to_ruby_object
|
|
45
|
+
append_to_dtm(current_request, event)
|
|
46
|
+
append_to_ruby_object(current_request, event)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def append_to_dtm current_request, event
|
|
@@ -91,7 +91,7 @@ module Contrast
|
|
|
91
91
|
value.each do |tag|
|
|
92
92
|
comparison = tag.compare_range(range.begin, range.end)
|
|
93
93
|
# ABOVE and BELOW are not affected by this check
|
|
94
|
-
tags_remove_comparison
|
|
94
|
+
tags_remove_comparison(comparison, tag, remove, add, range)
|
|
95
95
|
end
|
|
96
96
|
value.delete_if { |tag| remove.include?(tag) }
|
|
97
97
|
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
@@ -166,7 +166,7 @@ module Contrast
|
|
|
166
166
|
comparison = tag.compare_range(range.begin, range.end)
|
|
167
167
|
length = range.end - range.begin
|
|
168
168
|
# BELOW is not affected by this check
|
|
169
|
-
shift_tags_comparison
|
|
169
|
+
shift_tags_comparison(comparison, add, tag, length, range)
|
|
170
170
|
end
|
|
171
171
|
Contrast::Utils::TagUtil.ordered_merge(value, add)
|
|
172
172
|
end
|
|
@@ -12,28 +12,27 @@ module Contrast
|
|
|
12
12
|
# 2) the value is a non-empty array of only Fixnums
|
|
13
13
|
class HardcodedKey
|
|
14
14
|
include Contrast::Agent::Assess::Rule::Provider::HardcodedValueRule
|
|
15
|
-
|
|
15
|
+
REDACTED_MARKER = ' = [**REDACTED**]'
|
|
16
16
|
NAME = 'hardcoded-key'
|
|
17
|
-
def rule_id
|
|
18
|
-
NAME
|
|
19
|
-
end
|
|
20
|
-
|
|
21
17
|
# These are names, determined by the security team (Matt & Ar), that
|
|
22
18
|
# indicate a field is likely to be a password or secret token of some
|
|
23
19
|
# sort.
|
|
24
20
|
KEY_FIELD_NAMES = %w[KEY AES DES IV SECRET].cs__freeze
|
|
25
|
-
|
|
26
21
|
# These are markers whose presence indicates that a field is more
|
|
27
22
|
# likely to be a descriptor or requirement than an actual key.
|
|
28
23
|
# We should ignore fields that contain them.
|
|
29
24
|
NON_KEY_PARTIAL_NAMES = %w[CONTENT_CODES RESPONSE_CODES ERROR_CODES].cs__freeze
|
|
25
|
+
BYTE_HOLDERS = %i[ARRAY LIST].cs__freeze
|
|
26
|
+
|
|
27
|
+
def rule_id
|
|
28
|
+
NAME
|
|
29
|
+
end
|
|
30
30
|
|
|
31
31
|
def name_passes? constant_string
|
|
32
32
|
KEY_FIELD_NAMES.any? { |name| constant_string.index(name) } &&
|
|
33
33
|
NON_KEY_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
BYTE_HOLDERS = %i[ARRAY LIST].cs__freeze
|
|
37
36
|
# Determine if the given value node violates the hardcode key rule
|
|
38
37
|
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
|
39
38
|
# evaluate
|
|
@@ -66,7 +65,6 @@ module Contrast
|
|
|
66
65
|
true
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
REDACTED_MARKER = ' = [**REDACTED**]'
|
|
70
68
|
def redacted_marker
|
|
71
69
|
REDACTED_MARKER
|
|
72
70
|
end
|
|
@@ -15,12 +15,9 @@ module Contrast
|
|
|
15
15
|
# mixing the characters counts as a violation of this rule
|
|
16
16
|
class HardcodedPassword
|
|
17
17
|
include Contrast::Agent::Assess::Rule::Provider::HardcodedValueRule
|
|
18
|
-
|
|
19
18
|
NAME = 'hardcoded-password'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
23
|
-
|
|
19
|
+
REDACTED_MARKER = ' = "**REDACTED**"'
|
|
20
|
+
PROPERTY_NAME_PATTERN = /^[a-z]+[._][._a-z]*[a-z]+$/.cs__freeze
|
|
24
21
|
# These are names, determined by the security team (Matt & Ar), that
|
|
25
22
|
# indicate a field is likely to be a password or secret token of some
|
|
26
23
|
# sort.
|
|
@@ -34,6 +31,10 @@ module Contrast
|
|
|
34
31
|
URI
|
|
35
32
|
].cs__freeze
|
|
36
33
|
|
|
34
|
+
def rule_id
|
|
35
|
+
NAME
|
|
36
|
+
end
|
|
37
|
+
|
|
37
38
|
# If the constant looks like a password and it doesn't look like a
|
|
38
39
|
# password descriptor, it passes for this rule
|
|
39
40
|
def name_passes? constant_string
|
|
@@ -62,12 +63,10 @@ module Contrast
|
|
|
62
63
|
# characters are probably more likely to appear together in a
|
|
63
64
|
# default placeholder than in a password. Note this is opposite of
|
|
64
65
|
# the behavior in Java
|
|
65
|
-
PROPERTY_NAME_PATTERN = /^[a-z]+[._][._a-z]*[a-z]+$/.cs__freeze
|
|
66
66
|
def probably_property_name? value
|
|
67
67
|
value.match?(PROPERTY_NAME_PATTERN)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
REDACTED_MARKER = ' = "**REDACTED**"'
|
|
71
70
|
def redacted_marker
|
|
72
71
|
REDACTED_MARKER
|
|
73
72
|
end
|
|
@@ -86,9 +86,9 @@ module Contrast
|
|
|
86
86
|
return unless value_node_passes?(value)
|
|
87
87
|
|
|
88
88
|
if Contrast::Agent::Reporter.enabled?
|
|
89
|
-
new_finding_and_reporting
|
|
89
|
+
new_finding_and_reporting(mod, name)
|
|
90
90
|
else # TODO: RUBY-1438 -- remove
|
|
91
|
-
build_finding
|
|
91
|
+
build_finding(mod, name)
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
|
|
@@ -112,7 +112,7 @@ module Contrast
|
|
|
112
112
|
def build_finding clazz, constant_string
|
|
113
113
|
class_name = clazz.cs__name
|
|
114
114
|
|
|
115
|
-
finding = assign_finding
|
|
115
|
+
finding = assign_finding(class_name, constant_string)
|
|
116
116
|
activity = Contrast::Api::Dtm::Activity.new
|
|
117
117
|
activity.findings << finding
|
|
118
118
|
Contrast::Agent.messaging_queue.send_event_eventually(activity, force: true)
|
|
@@ -153,12 +153,12 @@ module Contrast
|
|
|
153
153
|
|
|
154
154
|
# extract to new method
|
|
155
155
|
# here we will generate new type of finding
|
|
156
|
-
ruby_finding = Contrast::Agent::Reporting::Finding.new
|
|
156
|
+
ruby_finding = Contrast::Agent::Reporting::Finding.new(rule_id)
|
|
157
157
|
ruby_finding.hash_code = hash
|
|
158
158
|
ruby_finding.properties[SOURCE_KEY] = clazz.cs__name
|
|
159
159
|
ruby_finding.properties[CONSTANT_NAME_KEY] = constant_string
|
|
160
160
|
ruby_finding.properties[CODE_SOURCE_KEY] = constant_string + redacted_marker
|
|
161
|
-
save_and_report_finding
|
|
161
|
+
save_and_report_finding(ruby_finding, new_preflight)
|
|
162
162
|
end
|
|
163
163
|
|
|
164
164
|
def save_and_report_finding ruby_finding, new_preflight
|
|
@@ -17,6 +17,7 @@ module Contrast
|
|
|
17
17
|
# These rules check the content of the HTTP Response to determine if something was set incorrectly or
|
|
18
18
|
# insecurely in it.
|
|
19
19
|
class BaseRule
|
|
20
|
+
DATA = 'data'.cs__freeze
|
|
20
21
|
# Analyze a given application response to determine if it violates the rule
|
|
21
22
|
#
|
|
22
23
|
# TODO: RUBY-999999 either extract the response's body or memoize it to some degree so that it's not
|
|
@@ -47,8 +48,6 @@ module Contrast
|
|
|
47
48
|
|
|
48
49
|
protected
|
|
49
50
|
|
|
50
|
-
DATA = 'data'.cs__freeze
|
|
51
|
-
|
|
52
51
|
# Rules discern which responses they can/should analyze.
|
|
53
52
|
#
|
|
54
53
|
# @param response [Contrast::Agent::Response] the response of the application
|
|
@@ -82,7 +81,7 @@ module Contrast
|
|
|
82
81
|
finding.rule_id = rule_id
|
|
83
82
|
context = Contrast::Agent::REQUEST_TRACKER.current
|
|
84
83
|
finding.routes << context.route if context&.route
|
|
85
|
-
build_evidence
|
|
84
|
+
build_evidence(evidence, finding)
|
|
86
85
|
hash = Contrast::Utils::HashDigest.generate_config_hash(finding)
|
|
87
86
|
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
|
|
88
87
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|