# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Utils module Assess # This module will include all methods for some internal validations in the SourceMethod module # and some other module methods from the same place, so we can ease the main module module SourceMethodUtils # Find the name of the source # # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source # event # @param object [Object] the Object on which the method was invoked # @param ret [Object] the Return of the invoked method # @param args [Array] the Arguments with which the method was invoked # @return [String, nil] the human readable name of the target to which this source event applies, or nil if # none provided by the node def determine_source_name source_node, object, ret, *args return source_node.get_property('dynamic_source_name') if source_node.type == 'UNTRUSTED_DATABASE' source_node_source = source_node.sources[0] case source_node_source when nil nil when Contrast::Utils::ObjectShare::RETURN_KEY ret when Contrast::Utils::ObjectShare::OBJECT_KEY object else args[source_node_source] end end # Determine if we should analyze this method invocation for a Source or not. We should if we have enough # information to build the context of this invocation, we're not disabled, and we can't immediately # determine the invocation was done safely. # # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy 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] the Arguments with which the method was invoked # @return [boolean] if the invocation of this method should be analyzed def analyze? method_policy, object, ret, args return false unless method_policy&.source_node return false unless ::Contrast::ASSESS.enabled? return false unless Contrast::Agent::REQUEST_TRACKER.current&.analyze_request? !safe_invocation?(method_policy.source_node, object, ret, args) end # Determine if the method was invoked safely. # # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the node to direct applying this source # event # @param _object [Object] the Object on which the method was invoked # @param _ret [Object] the Return of the invoked method # @param args [Array] the Arguments with which the method was invoked # @return [boolean] if the invocation of this method was safe def safe_invocation? source_node, _object, _ret, args # According the the Rack Specification https://github.com/rack/rack/blob/master/SPEC.rdoc, any header # from the Request will start with HTTP_. As such, only Headers with that key should be considered for # tracking, as the others have come from the Framework or Middleware stashing in the ENV. Rails, for # instance, uses action_dispatch. to store several values. Technically, you can't call # Rack::Request#get_header without a parameter, and that parameter should be a String, but trust no one. source_node.id == 'Assess:Source:Rack::Request::Env#get_header' && args&.any? && !args[0].to_s.start_with?('HTTP_') end end end end end