# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/agent/assess/policy/propagation_node' require 'contrast/components/logger' require 'contrast/components/scope' require 'contrast/agent/assess/events/event_data' module Contrast module Extension module Assess # This Class provides us with a way to invoke String propagation for those # methods which are too complex to fit into one of the standard # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the # String Class or exposing our methods there. class StringPropagator NODE_HASH = { 'class_name' => 'String', 'instance_method' => true, 'method_name' => 'interpolate', 'method_visibility' => 'public', 'action' => 'CUSTOM', 'source' => 'O,P0', 'target' => 'R', 'patch_class' => 'NOOP', 'patch_method' => 'track_interpolation' }.cs__freeze INTERPOLATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH) class << self include Contrast::Components::Logger::InstanceMethods include Contrast::Components::Scope::InstanceMethods # We call this method from C, and the Scope check is happening there. If we are in # Contrast Scope the method won't be invoked. # # @param inputs [Array] Inputs for interpolation. # @param result [String] The result from the interpolation. def track_interpolation inputs, result return unless inputs.any? { |input| Contrast::Agent::Assess::Tracker.tracked?(input) } return unless (properties = Contrast::Agent::Assess::Tracker.properties!(result)) parent_events = [] offset = 0 inputs.each do |input| properties.copy_from(input, result, offset) add_dynamic_sources_info(input, result) offset += input.length parent_event = Contrast::Agent::Assess::Tracker.properties(input)&.event parent_events << parent_event if parent_event end event_data = Contrast::Agent::Assess::Events::EventData.new(INTERPOLATION_NODE, result, inputs, result, inputs) properties.build_event(event_data) properties.event.instance_variable_set(:@_parent_events, parent_events) rescue StandardError => e logger.error('Unable to track interpolation', e) end private # When there is a string interpolation on input coming from tainted database, # the Contrast::Agent::Assess::Properties::Updated.copy_from method won't copy # the dynamic source properties needed in the build findings from TS to display # the column and Table information as database source information. # # @param source [Object] the source object with the required properties. # @param target [Object] the result form the interpolation and the object # that needs to keep the source properties, in order to be reporter on # trigger event. # @return updated_properties [Hash, nil] def add_dynamic_sources_info source, target return unless (dynamic_props = Contrast::Agent::Assess::Tracker.properties(source)&.properties) Contrast::Agent::Assess::Tracker.properties(target)&.add_properties(dynamic_props) rescue StandardError => e logger.error('Unable to copy Dynamic track interpolation', e) end end end end end end