lib/contrast/agent/assess/policy/trigger_method.rb in contrast-agent-3.8.5 vs lib/contrast/agent/assess/policy/trigger_method.rb in contrast-agent-3.9.0
- old
+ new
@@ -21,187 +21,272 @@
access_component :logging, :analysis
# The level of TeamServer compliance our traces meet
CURRENT_FINDING_VERSION = 2
- def self.settings
- Contrast::Agent::FeatureState.instance
- end
+ class << self
+ # This is called from within our woven proc. It will be called as if it
+ # were inline in the Rack application.
+ #
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
+ # the node that applies to the method being called
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ def apply_trigger_rule trigger_node, object, ret, args
+ return if trigger_node.nil?
- def self.apply_trigger_rule trigger_node, object, ret, args
- return if trigger_node.nil?
+ current_context = Contrast::Agent::REQUEST_TRACKER.current
+ return unless current_context&.analyze_request? && ASSESS.enabled?
- current_context = Contrast::Agent::REQUEST_TRACKER.current
- return unless current_context&.analyze_request? && ASSESS.enabled?
-
- if trigger_node.sources&.any?
- trigger_node.sources.each do |marker|
- source = determine_source(marker, object, ret, args)
- TriggerMethod.cs__apply_trigger(current_context,
- trigger_node,
- source,
- object,
- ret,
- 1,
- *args)
+ if trigger_node.sources&.any?
+ trigger_node.sources.each do |marker|
+ source = determine_source(marker, object, ret, args)
+ apply_trigger(current_context,
+ trigger_node,
+ source,
+ object,
+ ret,
+ 1,
+ *args)
+ end
+ else
+ apply_trigger(current_context,
+ trigger_node,
+ nil,
+ object,
+ ret,
+ 1,
+ *args)
end
- else
- TriggerMethod.cs__apply_trigger(current_context,
- trigger_node,
- nil,
- object,
- ret,
- 1,
- *args)
end
- end
- def self.cs__apply_trigger context, trigger_node, source, object, ret, invoked, *args
- return unless context && trigger_node
- return if trigger_node.rule_disabled?
- return if trigger_node.dataflow? && source.nil?
+ def apply_eval_trigger context, trigger_node, source, object, ret, invoked, *args
+ apply_trigger(context, trigger_node, source, object, ret, invoked, *args)
+ end
- invoked += 1
- if trigger_node.regexp_rule?
- apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
- elsif trigger_node.custom_trigger?
- trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
- elsif trigger_node.dataflow?
- apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
- else # trigger rule - just calling the method is dangerous
- build_finding(context, trigger_node, source, object, ret, invoked, *args)
+ # This converts the source of the finding, and the events leading
+ # up to it into a Finding
+ #
+ # @param context [Contrast::Utils::ThreadTracker] the current request
+ # context
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
+ # the node to direct applying this trigger event
+ # @param source [Object] the source of the Trigger Event
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param invoked [Integer] the depth of this invocation from
+ # application code; often a lie.
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ # @return [Contrast::Api::Dtm::Finding, nil] the
+ # Contrast::Api::Dtm::Finding to send to TeamServer or nil if
+ # conditions were not met
+ def build_finding context, trigger_node, source, object, ret, invoked, *args
+ return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
+
+ request = context.request
+ env = request.env
+ return if defined?(ActionController::Live) &&
+ env &&
+ env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
+
+ finding = Contrast::Api::Dtm::Finding.new
+ finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
+ finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
+ finding.version = CURRENT_FINDING_VERSION
+
+ build_from_source(finding, source)
+ trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
+ finding.events << trigger_event
+ build_hash(finding, source)
+ build_tags(context)
+ finding.routes << context.route if context.route
+ context.activity.findings << finding
+ logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
+ rescue StandardError => e
+ logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
end
- rescue StandardError => e
- logger.warn(e, 'Unable to apply trigger.')
- end
- # Given the marker from the trigger_node (the pointer indicating the entity
- # from which the taint originated), return the entity on which this
- # trigger needs to operate.
- #
- # In an effort to speed up this lookup, we've changed the marker for
- # parameters to be implicit - if it is not a return or an object, it
- # must be a parameter, which we can reference either by index or by
- # name.
- def self.determine_source marker, object, ret, args
- case marker
- when Contrast::Utils::ObjectShare::RETURN_KEY
- ret
- when Contrast::Utils::ObjectShare::OBJECT_KEY
- object
- else # 'P'
- if marker.is_a?(Integer)
- args[marker]
- else
- arg = nil
- args.each do |search|
- next unless search.is_a?(Hash)
+ private
- arg = search[marker]
- break if arg
- end
- arg
- end
+ def settings
+ Contrast::Agent::FeatureState.instance
end
- end
- def self.apply_regexp_rule context, trigger_node, source, object, ret, invoked, *args
- return unless source.is_a?(String)
- return if trigger_node.good_value && source.match?(trigger_node.good_value)
- return if trigger_node.bad_value && source !~ trigger_node.bad_value
+ # This is our method that actually checks the taint on the object
+ # our trigger_node targets.
+ #
+ # @param context [Contrast::Utils::ThreadTracker] the current request
+ # context
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
+ # the node to direct applying this trigger event
+ # @param source [Object] the source of the Trigger Event
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param invoked [Integer] the depth of this invocation from
+ # application code; often a lie.
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ def apply_trigger context, trigger_node, source, object, ret, invoked, *args
+ return unless context && trigger_node
+ return if trigger_node.rule_disabled?
+ return if trigger_node.dataflow? && source.nil?
- invoked += 1
- build_finding(context, trigger_node, source, object, ret, invoked, *args)
- end
+ invoked += 1
+ if trigger_node.regexp_rule?
+ apply_regexp_rule(context, trigger_node, source, object, ret, invoked, *args)
+ elsif trigger_node.custom_trigger?
+ trigger_node.apply_custom_trigger(context, trigger_node, source, object, ret, invoked, *args)
+ elsif trigger_node.dataflow?
+ apply_dataflow_rule(context, trigger_node, source, object, ret, invoked, *args)
+ else # trigger rule - just calling the method is dangerous
+ build_finding(context, trigger_node, source, object, ret, invoked, *args)
+ end
+ rescue StandardError => e
+ logger.warn(e, 'Unable to apply trigger.')
+ end
- def self.apply_dataflow_rule context, trigger_node, source, object, ret, invoked, *args
- return unless source
+ # Given the marker from the trigger_node (the pointer indicating
+ # the entity from which the taint originated), return the entity on
+ # which this trigger needs to operate.
+ #
+ # In an effort to speed up this lookup, we've changed the marker
+ # for parameters to be implicit - if it is not a return or an
+ # object, it must be a parameter, which we can reference either by
+ # index or by name.
+ #
+ # @param marker [String] the source marker that indicates which
+ # Object
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ # @return [Object] the literal object that this Trigger Event
+ # verifies
+ def determine_source marker, object, ret, args
+ case marker
+ when Contrast::Utils::ObjectShare::RETURN_KEY
+ ret
+ when Contrast::Utils::ObjectShare::OBJECT_KEY
+ object
+ else # 'P'
+ if marker.is_a?(Integer)
+ args[marker]
+ else
+ arg = nil
+ args.each do |search|
+ next unless search.is_a?(Hash)
- invoked += 1
- if Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties)
- return unless source.cs__tracked?
- return unless trigger_node.violated?(source)
+ arg = search[marker]
+ break if arg
+ end
+ arg
+ end
+ end
+ end
+ # This is our method that actually checks the taint on the object
+ # our trigger_node targets for our Regexp based rules.
+ #
+ # @param context [Contrast::Utils::ThreadTracker] the current request
+ # context
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
+ # the node to direct applying this trigger event
+ # @param source [Object] the source of the Trigger Event
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param invoked [Integer] the depth of this invocation from
+ # application code; often a lie.
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ def apply_regexp_rule context, trigger_node, source, object, ret, invoked, *args
+ return unless source.is_a?(String)
+ return if trigger_node.good_value && source.match?(trigger_node.good_value)
+ return if trigger_node.bad_value && source !~ trigger_node.bad_value
+
+ invoked += 1
build_finding(context, trigger_node, source, object, ret, invoked, *args)
- elsif Contrast::Utils::DuckUtils.quacks_like_tracked_hash?(source)
- invoked += 2 # the each & the block
- source.each_pair do |key, value|
- apply_dataflow_rule(context, trigger_node, key, object, ret, invoked, *args)
- apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
- end
- elsif Contrast::Utils::DuckUtils.quacks_like_tracked_enumerable?(source)
- invoked += 2 # the each & the block
- source.each do |value|
- apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
- end
- else
- logger.warn(
- nil,
- "Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
- logger.debug(nil, source.to_s[0..99])
end
- end
- def self.build_finding context, trigger_node, source, object, ret, invoked, *args
- return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
+ # This is our method that actually checks the taint on the object
+ # our trigger_node targets for our Dataflow based rules.
+ #
+ # @param context [Contrast::Utils::ThreadTracker] the current request
+ # context
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
+ # the node to direct applying this trigger event
+ # @param source [Object] the source of the Trigger Event
+ # @param object [Object] the Object on which the method was invoked
+ # @param ret [Object] the Return of the invoked method
+ # @param invoked [Integer] the depth of this invocation from
+ # application code; often a lie.
+ # @param args [Array<Object>] the Arguments with which the method
+ # was invoked
+ def apply_dataflow_rule context, trigger_node, source, object, ret, invoked, *args
+ return unless source
- request = context.request
- env = request.env
- return if defined?(ActionController::Live) &&
- env &&
- env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)
+ invoked += 1
+ if Contrast::Utils::DuckUtils.quacks_to?(source, :cs__properties)
+ return unless source.cs__tracked?
+ return unless trigger_node.violated?(source)
- finding = Contrast::Api::Dtm::Finding.new
- finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
- finding.session_id = Contrast::Agent::FeatureState.instance.current_session_id
- finding.version = CURRENT_FINDING_VERSION
+ build_finding(context, trigger_node, source, object, ret, invoked, *args)
+ elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
+ invoked += 2 # the each & the block
+ source.each_pair do |key, value|
+ apply_dataflow_rule(context, trigger_node, key, object, ret, invoked, *args)
+ apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
+ end
+ elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
+ invoked += 2 # the each & the block
+ source.each do |value|
+ apply_dataflow_rule(context, trigger_node, value, object, ret, invoked, *args)
+ end
+ else
+ logger.warn(
+ nil,
+ "Target is a #{ source.cs__class.name } -- not sure how to inspect: #{ trigger_node.inspect }")
+ logger.debug(nil, source.to_s[0..99])
+ end
+ end
- build_from_source(finding, source)
- trigger_event = Contrast::Agent::Assess::ContrastEvent.new(trigger_node, source, object, ret, args, invoked + 1).to_dtm_event
- finding.events << trigger_event
- build_hash(finding, source)
- build_tags(context)
- finding.routes << context.route if context.route
- context.activity.findings << finding
- logger.debug(nil, "Trigger #{ trigger_node.id } detected: #{ source.__id__ } triggered #{ trigger_node.rule_id }")
- rescue StandardError => e
- logger.error(e, "Unable to build a finding for #{ trigger_node.id }")
- end
+ def build_from_source finding, source
+ return unless source
+ return unless Contrast::Utils::DuckUtils.quacks_to?(
+ source,
+ :cs__properties)
+ return unless source.cs__properties
- def self.build_from_source finding, source
- return unless source
- return unless Contrast::Utils::DuckUtils.quacks_to?(
- source,
- :cs__properties)
- return unless source.cs__properties
+ # events could technically be nil, but we would have failed
+ # the rule check before getting here. not worth the nil check
+ source.cs__properties.events.each do |event|
+ finding.events << event.to_dtm_event
+ end
- # events could technically be nil, but we would have failed
- # the rule check before getting here. not worth the nil check
- source.cs__properties.events.each do |event|
- finding.events << event.to_dtm_event
+ # Google::Protobuf::Map doesn't support merge!, so we have to do this
+ # long form
+ source_props = source.cs__properties.properties
+ return unless source_props
+
+ source_props.each_pair do |key, value|
+ key = Contrast::Utils::StringUtils.force_utf8(key)
+ finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
+ end
end
- # Google::Protobuf::Map doesn't support merge!, so we have to do this
- # long form
- source_props = source.cs__properties.properties
- return unless source_props
-
- source_props.each_pair do |key, value|
- key = Contrast::Utils::StringUtils.force_utf8(key)
- finding.properties[key] = Contrast::Utils::StringUtils.force_utf8(value)
+ def build_hash finding, source
+ hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
+ finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
+ finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
end
- end
- def self.build_hash finding, source
- hash_code = Contrast::Utils::HashDigest.generate_event_hash(finding, source)
- finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash_code)
- finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
- end
+ def build_tags context
+ return unless ASSESS.tags
- def self.build_tags context
- return unless ASSESS.tags
-
- context.activity.finding_tags = Contrast::Utils::StringUtils.force_utf8(ASSESS.tags)
+ context.activity.finding_tags = Contrast::Utils::StringUtils.force_utf8(ASSESS.tags)
+ end
end
end
end
end
end