# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/assess/event_limit_utils' module Contrast module Utils module Assess # This module will include all methods for some internal validations/appliers in the TriggerMethod module # and some other module methods from the same place, so we can ease the main module module TriggerMethodUtils extend Contrast::Utils::Assess::EventLimitUtils # A request is reportable if it is not from ActionController::Live # # @param env [Hash] the env of the Request # @return [Boolean] def reportable? env !(defined?(ActionController::Live) && env && env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live)) end # Find the request for this finding. This assumes, for now, that if there is an active request, then that # is the request to report. Otherwise, we'll use the first request found in the events of the # source_object if the non request tracking flag is set. # # @param source [Object,nil] some Object used as the source of a trigger event # @return [Contrast::Agent::Request,nil] the request from which the dataflow on the request originated. def find_request source return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current return unless ::Contrast::ASSESS.non_request_tracking? return unless (properties = Contrast::Agent::Assess::Tracker.properties(source)) find_event_request(properties.event) end # Finds the first request along the left most tree of parent events # # @param event [Contrast::Agent::Reporting::FindingEvent] # @return [Contrast::Agent::Request, nil] def find_event_request event return event.request if event&.source_type idx = 0 while idx <= event.parent_events.length found = find_event_request(event.parent_events[idx]) return found if found idx += 1 end event.request end # ===== APPLIERS ===== # This is our method that actually checks the taint on the object our trigger_node targets. # # @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 args [Array<Object>] the Arguments with which the method was invoked def apply_trigger trigger_node, source, object, ret, *args return unless trigger_node return if trigger_node.rule_disabled? return if trigger_node.dataflow? && source.nil? if trigger_node.regexp_rule? apply_regexp_rule(trigger_node, source, object, ret, *args) elsif trigger_node.custom_trigger? trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args) elsif trigger_node.dataflow? apply_dataflow_rule(trigger_node, source, object, ret, *args) else # trigger rule - just calling the method is dangerous finding = build_finding(trigger_node, source, object, ret, *args) Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding end rescue StandardError => e logger.warn('Unable to apply trigger', e, node_id: trigger_node.id) end # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp # based rules. # # @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 args [Array<Object>] the Arguments with which the method was invoked def apply_regexp_rule trigger_node, source, object, ret, *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 finding = build_finding(trigger_node, source, object, ret, *args) Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding end # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow # based rules. # # @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 args [Array<Object>] the Arguments with which the method was invoked def apply_dataflow_rule trigger_node, source, object, ret, *args return unless source && Contrast::Agent::Assess::Tracker.tracked?(source) if Contrast::Agent::Assess::Tracker.trackable?(source) return unless trigger_node.violated?(source) finding = build_finding(trigger_node, source, object, ret, *args) report_finding(finding) if finding elsif Contrast::Utils::DuckUtils.iterable_hash?(source) source.each_pair do |key, value| apply_dataflow_rule(trigger_node, key, object, ret, *args) apply_dataflow_rule(trigger_node, value, object, ret, *args) end elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source) source.each do |value| apply_dataflow_rule(trigger_node, value, object, ret, *args) end end end end end end end