contrast-agent 3.8.5 → 3.9.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/ext/cs__assess_array/cs__assess_array.c +1 -1
- data/ext/cs__assess_module/cs__assess_module.c +0 -1
- data/ext/cs__assess_yield_track/cs__assess_yield_track.c +34 -0
- data/ext/cs__assess_yield_track/cs__assess_yield_track.h +12 -0
- data/ext/{cs__scope → cs__assess_yield_track}/extconf.rb +0 -0
- data/ext/cs__common/cs__common.c +6 -6
- data/ext/cs__common/cs__common.h +3 -1
- data/ext/cs__contrast_patch/cs__contrast_patch.c +142 -119
- data/ext/cs__contrast_patch/cs__contrast_patch.h +3 -0
- data/funchook/autom4te.cache/requests +48 -48
- data/funchook/config.log +2 -2
- data/lib/contrast/agent.rb +15 -5
- data/lib/contrast/agent/assess.rb +0 -1
- data/lib/contrast/agent/assess/contrast_event.rb +9 -8
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +68 -18
- data/lib/contrast/agent/assess/policy/policy.rb +0 -14
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
- data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagation_method.rb +4 -2
- data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -2
- data/lib/contrast/agent/assess/policy/propagator/split.rb +166 -1
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +1 -0
- data/lib/contrast/agent/assess/policy/source_method.rb +199 -140
- data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +30 -0
- data/lib/contrast/agent/assess/policy/source_validation/source_validation.rb +36 -0
- data/lib/contrast/agent/assess/policy/trigger_method.rb +238 -153
- data/lib/contrast/agent/assess/policy/trigger_node.rb +54 -9
- data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +13 -0
- data/lib/contrast/agent/assess/properties.rb +29 -0
- data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +35 -31
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +1 -1
- data/lib/contrast/agent/class_reopener.rb +98 -55
- data/lib/contrast/agent/feature_state.rb +1 -1
- data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
- data/lib/contrast/agent/logger_manager.rb +2 -2
- data/lib/contrast/agent/middleware.rb +1 -3
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +40 -4
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +33 -8
- data/lib/contrast/agent/patching/policy/method_policy.rb +20 -7
- data/lib/contrast/agent/patching/policy/patch.rb +54 -23
- data/lib/contrast/agent/patching/policy/patch_status.rb +0 -2
- data/lib/contrast/agent/patching/policy/patcher.rb +10 -11
- data/lib/contrast/agent/patching/policy/policy.rb +4 -0
- data/lib/contrast/agent/patching/policy/policy_node.rb +14 -1
- data/lib/contrast/agent/patching/policy/trigger_node.rb +2 -1
- data/lib/contrast/agent/protect/policy/policy.rb +6 -6
- data/lib/contrast/agent/protect/rule/base.rb +1 -1
- data/lib/contrast/agent/protect/rule/deserialization.rb +3 -25
- data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
- data/lib/contrast/agent/railtie.rb +11 -5
- data/lib/contrast/agent/request.rb +1 -19
- data/lib/contrast/agent/request_context.rb +1 -1
- data/lib/contrast/agent/rewriter.rb +4 -3
- data/lib/contrast/agent/scope.rb +116 -19
- data/lib/contrast/agent/service_heartbeat.rb +5 -2
- data/lib/contrast/agent/settings_state.rb +12 -8
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api.rb +1 -0
- data/lib/contrast/api/speedracer.rb +2 -2
- data/lib/contrast/components/agent.rb +26 -7
- data/lib/contrast/components/app_context.rb +8 -45
- data/lib/contrast/components/contrast_service.rb +3 -4
- data/lib/contrast/components/interface.rb +1 -1
- data/lib/contrast/components/scope.rb +56 -26
- data/lib/contrast/config/ruby_configuration.rb +8 -3
- data/lib/contrast/delegators.rb +9 -0
- data/lib/contrast/delegators/application_update.rb +32 -0
- data/lib/contrast/extensions/framework/rack/cookie.rb +24 -0
- data/lib/contrast/extensions/framework/rack/request.rb +24 -0
- data/lib/contrast/extensions/framework/rack/response.rb +23 -0
- data/lib/contrast/extensions/framework/rails/action_controller_railties_helper_inherited.rb +20 -0
- data/lib/contrast/extensions/framework/rails/active_record.rb +26 -0
- data/lib/contrast/extensions/framework/rails/active_record_named.rb +53 -0
- data/lib/contrast/extensions/framework/rails/active_record_time_zone_inherited.rb +21 -0
- data/lib/contrast/extensions/framework/rails/buffer.rb +28 -0
- data/lib/contrast/extensions/framework/rails/configuration.rb +27 -0
- data/lib/contrast/extensions/framework/sinatra/base.rb +59 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess.rb +12 -11
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/array.rb +4 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/assess_extension.rb +0 -2
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/basic_object.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/erb.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/exec_trigger.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/fiber.rb +3 -4
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/hash.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/kernel.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/module.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/regexp.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/string.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/tilt_template_trigger.rb +0 -0
- data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +40 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/delegator.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/eval_trigger.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory/datastores.rb +1 -1
- data/lib/contrast/extensions/ruby_core/module.rb +17 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb +8 -6
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb +7 -5
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_no_sqli_rule.rb +5 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb +31 -27
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_sqli_rule.rb +5 -3
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_xxe_rule.rb +9 -7
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/kernel.rb +0 -0
- data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/psych.rb +1 -1
- data/lib/contrast/{core_extensions → extensions/ruby_core}/thread.rb +0 -0
- data/lib/contrast/framework/base_support.rb +63 -0
- data/lib/contrast/framework/manager.rb +109 -0
- data/lib/contrast/framework/platform_version.rb +21 -0
- data/lib/contrast/framework/rails_support.rb +88 -0
- data/lib/contrast/framework/sinatra_application_helper.rb +49 -0
- data/lib/contrast/framework/sinatra_support.rb +94 -0
- data/lib/contrast/framework/view_technologies_descriptor.rb +20 -0
- data/lib/contrast/utils/assess/tracking_util.rb +2 -4
- data/lib/contrast/utils/class_util.rb +92 -37
- data/lib/contrast/utils/duck_utils.rb +59 -39
- data/lib/contrast/utils/environment_util.rb +5 -75
- data/lib/contrast/utils/freeze_util.rb +3 -7
- data/lib/contrast/utils/invalid_configuration_util.rb +5 -5
- data/lib/contrast/utils/job_servers_running.rb +39 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +2 -2
- data/lib/contrast/utils/service_response_util.rb +0 -6
- data/lib/contrast/utils/sinatra_helper.rb +6 -0
- data/lib/contrast/utils/stack_trace_utils.rb +1 -1
- data/resources/assess/policy.json +74 -23
- data/resources/inventory/policy.json +1 -1
- data/resources/protect/policy.json +11 -9
- data/resources/rubocops/object/frozen_cop.rb +1 -1
- data/ruby-agent.gemspec +2 -0
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +94 -57
- data/ext/cs__scope/cs__scope.c +0 -96
- data/ext/cs__scope/cs__scope.h +0 -33
- data/lib/contrast/agent/assess/class_reverter.rb +0 -82
- data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +0 -28
- data/lib/contrast/core_extensions/module.rb +0 -42
- data/lib/contrast/core_extensions/object.rb +0 -27
- data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +0 -48
- data/lib/contrast/rails_extensions/assess/active_record.rb +0 -32
- data/lib/contrast/rails_extensions/assess/active_record_named.rb +0 -61
- data/lib/contrast/rails_extensions/assess/configuration.rb +0 -26
- data/lib/contrast/rails_extensions/buffer.rb +0 -30
- data/lib/contrast/rails_extensions/rack.rb +0 -45
- data/lib/contrast/sinatra_extensions/assess/cookie.rb +0 -26
- data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +0 -59
- data/lib/contrast/utils/operating_environment.rb +0 -38
- data/lib/contrast/utils/path_util.rb +0 -151
- data/lib/contrast/utils/scope_util.rb +0 -99
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'contrast/agent/assess/policy/source_method'
|
|
5
|
+
|
|
6
|
+
module Contrast
|
|
7
|
+
module Agent
|
|
8
|
+
module Assess
|
|
9
|
+
module Policy
|
|
10
|
+
module SourceValidation
|
|
11
|
+
# Validator used to assert a CROSS_SITE tag is actually applicable to
|
|
12
|
+
# the given method before applying the tag to its target
|
|
13
|
+
module CrossSiteValidator
|
|
14
|
+
# prevent the application of a tag if it is from a source known to
|
|
15
|
+
# not apply a tag in a provided context.
|
|
16
|
+
# https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
|
|
17
|
+
def self.valid? tag, source_type, source_name
|
|
18
|
+
return true unless tag == 'CROSS_SITE'
|
|
19
|
+
return false if source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_KEY_TYPE
|
|
20
|
+
return true unless source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_TYPE
|
|
21
|
+
return false unless source_name
|
|
22
|
+
|
|
23
|
+
source_name.casecmp?('referer')
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
cs__scoped_require 'contrast/agent/assess/policy/source_validation/cross_site_validator'
|
|
5
|
+
|
|
6
|
+
module Contrast
|
|
7
|
+
module Agent
|
|
8
|
+
module Assess
|
|
9
|
+
module Policy
|
|
10
|
+
# Some of our sources require validation to prevent them from applying
|
|
11
|
+
# certain tags in a given context. This provides a single place from
|
|
12
|
+
# which those validations can be called.
|
|
13
|
+
module SourceValidation
|
|
14
|
+
VALIDATORS = [
|
|
15
|
+
Contrast::Agent::Assess::Policy::SourceValidation::CrossSiteValidator
|
|
16
|
+
].cs__freeze
|
|
17
|
+
|
|
18
|
+
# Determines if the conditions in which this source was called are
|
|
19
|
+
# valid and should result in the application of a tag
|
|
20
|
+
#
|
|
21
|
+
# @param tag [String] the tag which the Source is attempting to apply
|
|
22
|
+
# @param source_type [String] the type of the source
|
|
23
|
+
# @param source_name [String] the name of the source
|
|
24
|
+
# @return [Boolean] if the conditions are valid for the application
|
|
25
|
+
# of a tag
|
|
26
|
+
def self.valid? tag, source_type, source_name
|
|
27
|
+
VALIDATORS.each do |validator|
|
|
28
|
+
return false unless validator.valid?(tag, source_type, source_name)
|
|
29
|
+
end
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -23,184 +23,269 @@ module Contrast
|
|
|
23
23
|
# The level of TeamServer compliance our traces meet
|
|
24
24
|
CURRENT_FINDING_VERSION = 2
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
class << self
|
|
27
|
+
# This is called from within our woven proc. It will be called as if it
|
|
28
|
+
# were inline in the Rack application.
|
|
29
|
+
#
|
|
30
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
|
31
|
+
# the node that applies to the method being called
|
|
32
|
+
# @param object [Object] the Object on which the method was invoked
|
|
33
|
+
# @param ret [Object] the Return of the invoked method
|
|
34
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
35
|
+
# was invoked
|
|
36
|
+
def apply_trigger_rule trigger_node, object, ret, args
|
|
37
|
+
return if trigger_node.nil?
|
|
38
|
+
|
|
39
|
+
current_context = Contrast::Agent::REQUEST_TRACKER.current
|
|
40
|
+
return unless current_context&.analyze_request? && ASSESS.enabled?
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
if trigger_node.sources&.any?
|
|
43
|
+
trigger_node.sources.each do |marker|
|
|
44
|
+
source = determine_source(marker, object, ret, args)
|
|
45
|
+
apply_trigger(current_context,
|
|
46
|
+
trigger_node,
|
|
47
|
+
source,
|
|
48
|
+
object,
|
|
49
|
+
ret,
|
|
50
|
+
1,
|
|
51
|
+
*args)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
apply_trigger(current_context,
|
|
55
|
+
trigger_node,
|
|
56
|
+
nil,
|
|
57
|
+
object,
|
|
58
|
+
ret,
|
|
59
|
+
1,
|
|
60
|
+
*args)
|
|
46
61
|
end
|
|
47
|
-
else
|
|
48
|
-
TriggerMethod.cs__apply_trigger(current_context,
|
|
49
|
-
trigger_node,
|
|
50
|
-
nil,
|
|
51
|
-
object,
|
|
52
|
-
ret,
|
|
53
|
-
1,
|
|
54
|
-
*args)
|
|
55
62
|
end
|
|
56
|
-
end
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return if trigger_node.rule_disabled?
|
|
61
|
-
return if trigger_node.dataflow? && source.nil?
|
|
62
|
-
|
|
63
|
-
invoked += 1
|
|
64
|
-
if trigger_node.regexp_rule?
|
|
65
|
-
apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
|
|
66
|
-
elsif trigger_node.custom_trigger?
|
|
67
|
-
trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
|
|
68
|
-
elsif trigger_node.dataflow?
|
|
69
|
-
apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
|
|
70
|
-
else # trigger rule - just calling the method is dangerous
|
|
71
|
-
build_finding(context, trigger_node, source, object, ret, invoked, *args)
|
|
64
|
+
def apply_eval_trigger context, trigger_node, source, object, ret, invoked, *args
|
|
65
|
+
apply_trigger(context, trigger_node, source, object, ret, invoked, *args)
|
|
72
66
|
end
|
|
73
|
-
rescue StandardError => e
|
|
74
|
-
logger.warn(e, 'Unable to apply trigger.')
|
|
75
|
-
end
|
|
76
67
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
68
|
+
# This converts the source of the finding, and the events leading
|
|
69
|
+
# up to it into a Finding
|
|
70
|
+
#
|
|
71
|
+
# @param context [Contrast::Utils::ThreadTracker] the current request
|
|
72
|
+
# context
|
|
73
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
|
74
|
+
# the node to direct applying this trigger event
|
|
75
|
+
# @param source [Object] the source of the Trigger Event
|
|
76
|
+
# @param object [Object] the Object on which the method was invoked
|
|
77
|
+
# @param ret [Object] the Return of the invoked method
|
|
78
|
+
# @param invoked [Integer] the depth of this invocation from
|
|
79
|
+
# application code; often a lie.
|
|
80
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
81
|
+
# was invoked
|
|
82
|
+
# @return [Contrast::Api::Dtm::Finding, nil] the
|
|
83
|
+
# Contrast::Api::Dtm::Finding to send to TeamServer or nil if
|
|
84
|
+
# conditions were not met
|
|
85
|
+
def build_finding context, trigger_node, source, object, ret, invoked, *args
|
|
86
|
+
return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
|
|
87
|
+
|
|
88
|
+
request = context.request
|
|
89
|
+
env = request.env
|
|
90
|
+
return if defined?(ActionController::Live) &&
|
|
91
|
+
env &&
|
|
92
|
+
env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
|
|
93
|
+
|
|
94
|
+
finding = Contrast::Api::Dtm::Finding.new
|
|
95
|
+
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
|
|
96
|
+
finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
|
|
97
|
+
finding.version = CURRENT_FINDING_VERSION
|
|
98
|
+
|
|
99
|
+
build_from_source(finding, source)
|
|
100
|
+
trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
|
|
101
|
+
finding.events << trigger_event
|
|
102
|
+
build_hash(finding, source)
|
|
103
|
+
build_tags(context)
|
|
104
|
+
finding.routes << context.route if context.route
|
|
105
|
+
context.activity.findings << finding
|
|
106
|
+
logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
|
|
107
|
+
rescue StandardError => e
|
|
108
|
+
logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def settings
|
|
114
|
+
Contrast::Agent::FeatureState.instance
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# This is our method that actually checks the taint on the object
|
|
118
|
+
# our trigger_node targets.
|
|
119
|
+
#
|
|
120
|
+
# @param context [Contrast::Utils::ThreadTracker] the current request
|
|
121
|
+
# context
|
|
122
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
|
123
|
+
# the node to direct applying this trigger event
|
|
124
|
+
# @param source [Object] the source of the Trigger Event
|
|
125
|
+
# @param object [Object] the Object on which the method was invoked
|
|
126
|
+
# @param ret [Object] the Return of the invoked method
|
|
127
|
+
# @param invoked [Integer] the depth of this invocation from
|
|
128
|
+
# application code; often a lie.
|
|
129
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
130
|
+
# was invoked
|
|
131
|
+
def apply_trigger context, trigger_node, source, object, ret, invoked, *args
|
|
132
|
+
return unless context && trigger_node
|
|
133
|
+
return if trigger_node.rule_disabled?
|
|
134
|
+
return if trigger_node.dataflow? && source.nil?
|
|
135
|
+
|
|
136
|
+
invoked += 1
|
|
137
|
+
if trigger_node.regexp_rule?
|
|
138
|
+
apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
|
|
139
|
+
elsif trigger_node.custom_trigger?
|
|
140
|
+
trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
|
|
141
|
+
elsif trigger_node.dataflow?
|
|
142
|
+
apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
|
|
143
|
+
else # trigger rule - just calling the method is dangerous
|
|
144
|
+
build_finding(context, trigger_node, source, object, ret, invoked, *args)
|
|
145
|
+
end
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
logger.warn(e, 'Unable to apply trigger.')
|
|
148
|
+
end
|
|
98
149
|
|
|
99
|
-
|
|
100
|
-
|
|
150
|
+
# Given the marker from the trigger_node (the pointer indicating
|
|
151
|
+
# the entity from which the taint originated), return the entity on
|
|
152
|
+
# which this trigger needs to operate.
|
|
153
|
+
#
|
|
154
|
+
# In an effort to speed up this lookup, we've changed the marker
|
|
155
|
+
# for parameters to be implicit - if it is not a return or an
|
|
156
|
+
# object, it must be a parameter, which we can reference either by
|
|
157
|
+
# index or by name.
|
|
158
|
+
#
|
|
159
|
+
# @param marker [String] the source marker that indicates which
|
|
160
|
+
# Object
|
|
161
|
+
# @param object [Object] the Object on which the method was invoked
|
|
162
|
+
# @param ret [Object] the Return of the invoked method
|
|
163
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
164
|
+
# was invoked
|
|
165
|
+
# @return [Object] the literal object that this Trigger Event
|
|
166
|
+
# verifies
|
|
167
|
+
def determine_source marker, object, ret, args
|
|
168
|
+
case marker
|
|
169
|
+
when Contrast::Utils::ObjectShare::RETURN_KEY
|
|
170
|
+
ret
|
|
171
|
+
when Contrast::Utils::ObjectShare::OBJECT_KEY
|
|
172
|
+
object
|
|
173
|
+
else # 'P'
|
|
174
|
+
if marker.is_a?(Integer)
|
|
175
|
+
args[marker]
|
|
176
|
+
else
|
|
177
|
+
arg = nil
|
|
178
|
+
args.each do |search|
|
|
179
|
+
next unless search.is_a?(Hash)
|
|
180
|
+
|
|
181
|
+
arg = search[marker]
|
|
182
|
+
break if arg
|
|
183
|
+
end
|
|
184
|
+
arg
|
|
101
185
|
end
|
|
102
|
-
arg
|
|
103
186
|
end
|
|
104
187
|
end
|
|
105
|
-
end
|
|
106
188
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
189
|
+
# This is our method that actually checks the taint on the object
|
|
190
|
+
# our trigger_node targets for our Regexp based rules.
|
|
191
|
+
#
|
|
192
|
+
# @param context [Contrast::Utils::ThreadTracker] the current request
|
|
193
|
+
# context
|
|
194
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
|
195
|
+
# the node to direct applying this trigger event
|
|
196
|
+
# @param source [Object] the source of the Trigger Event
|
|
197
|
+
# @param object [Object] the Object on which the method was invoked
|
|
198
|
+
# @param ret [Object] the Return of the invoked method
|
|
199
|
+
# @param invoked [Integer] the depth of this invocation from
|
|
200
|
+
# application code; often a lie.
|
|
201
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
202
|
+
# was invoked
|
|
203
|
+
def apply_regexp_rule context, trigger_node, source, object, ret, invoked, *args
|
|
204
|
+
return unless source.is_a?(String)
|
|
205
|
+
return if trigger_node.good_value && source.match?(trigger_node.good_value)
|
|
206
|
+
return if trigger_node.bad_value && source !~ trigger_node.bad_value
|
|
111
207
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
208
|
+
invoked += 1
|
|
209
|
+
build_finding(context, trigger_node, source, object, ret, invoked, *args)
|
|
210
|
+
end
|
|
115
211
|
|
|
116
|
-
|
|
117
|
-
|
|
212
|
+
# This is our method that actually checks the taint on the object
|
|
213
|
+
# our trigger_node targets for our Dataflow based rules.
|
|
214
|
+
#
|
|
215
|
+
# @param context [Contrast::Utils::ThreadTracker] the current request
|
|
216
|
+
# context
|
|
217
|
+
# @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
|
|
218
|
+
# the node to direct applying this trigger event
|
|
219
|
+
# @param source [Object] the source of the Trigger Event
|
|
220
|
+
# @param object [Object] the Object on which the method was invoked
|
|
221
|
+
# @param ret [Object] the Return of the invoked method
|
|
222
|
+
# @param invoked [Integer] the depth of this invocation from
|
|
223
|
+
# application code; often a lie.
|
|
224
|
+
# @param args [Array<Object>] the Arguments with which the method
|
|
225
|
+
# was invoked
|
|
226
|
+
def apply_dataflow_rule context, trigger_node, source, object, ret, invoked, *args
|
|
227
|
+
return unless source
|
|
118
228
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
229
|
+
invoked += 1
|
|
230
|
+
if Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties)
|
|
231
|
+
return unless source.cs__tracked?
|
|
232
|
+
return unless trigger_node.violated?(source)
|
|
123
233
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
234
|
+
build_finding(context, trigger_node, source, object, ret, invoked, *args)
|
|
235
|
+
elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
|
|
236
|
+
invoked += 2 # the each & the block
|
|
237
|
+
source.each_pair do |key, value|
|
|
238
|
+
apply_dataflow_rule(context, trigger_node, key, object, ret, invoked, *args)
|
|
239
|
+
apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
|
|
240
|
+
end
|
|
241
|
+
elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
|
|
242
|
+
invoked += 2 # the each & the block
|
|
243
|
+
source.each do |value|
|
|
244
|
+
apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
logger.warn(
|
|
248
|
+
nil,
|
|
249
|
+
"Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
|
|
250
|
+
logger.debug(nil, source.to_s[0..99])
|
|
135
251
|
end
|
|
136
|
-
else
|
|
137
|
-
logger.warn(
|
|
138
|
-
nil,
|
|
139
|
-
"Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
|
|
140
|
-
logger.debug(nil, source.to_s[0..99])
|
|
141
252
|
end
|
|
142
|
-
end
|
|
143
253
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
env &&
|
|
151
|
-
env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
|
|
152
|
-
|
|
153
|
-
finding = Contrast::Api::Dtm::Finding.new
|
|
154
|
-
finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
|
|
155
|
-
finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
|
|
156
|
-
finding.version = CURRENT_FINDING_VERSION
|
|
157
|
-
|
|
158
|
-
build_from_source(finding, source)
|
|
159
|
-
trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
|
|
160
|
-
finding.events << trigger_event
|
|
161
|
-
build_hash(finding, source)
|
|
162
|
-
build_tags(context)
|
|
163
|
-
finding.routes << context.route if context.route
|
|
164
|
-
context.activity.findings << finding
|
|
165
|
-
logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
|
|
166
|
-
rescue StandardError => e
|
|
167
|
-
logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
|
|
168
|
-
end
|
|
254
|
+
def build_from_source finding, source
|
|
255
|
+
return unless source
|
|
256
|
+
return unless Contrast::Utils::DuckUtils.quacks_to?(
|
|
257
|
+
source,
|
|
258
|
+
:cs__properties)
|
|
259
|
+
return unless source.cs__properties
|
|
169
260
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return unless source.cs__properties
|
|
176
|
-
|
|
177
|
-
# events could technically be nil, but we would have failed
|
|
178
|
-
# the rule check before getting here. not worth the nil check
|
|
179
|
-
source.cs__properties.events.each do |event|
|
|
180
|
-
finding.events << event.to_dtm_event
|
|
181
|
-
end
|
|
261
|
+
# events could technically be nil, but we would have failed
|
|
262
|
+
# the rule check before getting here. not worth the nil check
|
|
263
|
+
source.cs__properties.events.each do |event|
|
|
264
|
+
finding.events << event.to_dtm_event
|
|
265
|
+
end
|
|
182
266
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
267
|
+
# Google::Protobuf::Map doesn't support merge!, so we have to do this
|
|
268
|
+
# long form
|
|
269
|
+
source_props = source.cs__properties.properties
|
|
270
|
+
return unless source_props
|
|
187
271
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
272
|
+
source_props.each_pair do |key, value|
|
|
273
|
+
key = Contrast::Utils::StringUtils.force_utf8(key)
|
|
274
|
+
finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
|
|
275
|
+
end
|
|
191
276
|
end
|
|
192
|
-
end
|
|
193
277
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
278
|
+
def build_hash finding, source
|
|
279
|
+
hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
|
|
280
|
+
finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
|
|
281
|
+
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
|
282
|
+
end
|
|
199
283
|
|
|
200
|
-
|
|
201
|
-
|
|
284
|
+
def build_tags context
|
|
285
|
+
return unless ASSESS.tags
|
|
202
286
|
|
|
203
|
-
|
|
287
|
+
context.activity.finding_tags = Contrast::Utils::StringUtils.force_utf8(ASSESS.tags)
|
|
288
|
+
end
|
|
204
289
|
end
|
|
205
290
|
end
|
|
206
291
|
end
|