# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Assess module Policy module Trigger # This acts a trigger to handle the special cases of the Tilt # library gem. Reflected XSS data may come into the trigger methods # from these classes. class ReflectedXss class << self NODE_HASH = { 'class_name' => 'Tilt::Template', 'instance_method' => true, 'method_name' => 'render', 'method_visibility' => 'public', 'action' => 'CUSTOM', 'source' => 'O,P0', 'target' => 'R', 'patch_class' => 'Contrast::Agent::Assess::Policy::Trigger::ReflectedXss', 'patch_method' => 'xss_tilt_trigger' }.cs__freeze TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH) # @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this # propagation event. # @param _source [Object] the source of the propagation # @param object [Object] the object to which the source is being appended # @param args [Array] the arguments to the method # @param ret [Object] the return value of the method def xss_tilt_trigger trigger_node, _source, object, ret, *args return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret)) scope = args[0] erb_template_prerender = object.instance_variable_get(:@data) interpolated_inputs = [] handle_binding_variables(scope, erb_template_prerender, ret, properties, interpolated_inputs) handle_local_variables(args, erb_template_prerender, ret, properties, interpolated_inputs) event_data = Contrast::Agent::Assess::Events::EventData.new(TEMPLATE_PROPAGATION_NODE, ret, erb_template_prerender, ret, interpolated_inputs) properties.build_event(event_data) properties.copy_from(erb_template_prerender, ret, 0) unless interpolated_inputs.empty? current_event = properties.event interpolated_inputs.each do |input| input_properties = Contrast::Agent::Assess::Tracker.properties(input) next unless input_properties&.event current_event.parent_events << input_properties.event end end if Contrast::Agent::Assess::Tracker.tracked?(ret) finding = Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(trigger_node, ret, erb_template_prerender, ret, interpolated_inputs) Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding end ret end private # @param scope [Object] the scope of the template # @param erb_template_prerender [String] the template string # @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value # @param interpolated_inputs [Array<String>] the interpolated inputs # @param ret [String] the return value of the method def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs binding_variables = scope.instance_variables binding_variables.each do |bound_variable_sym| bound_variable_value = scope.instance_variable_get(bound_variable_sym) next unless Contrast::Agent::Assess::Tracker.tracked?(bound_variable_value) next unless erb_template_prerender.include?(bound_variable_sym.to_s) next unless ret.cs__respond_to?(:index) start_index = ret.index(bound_variable_value) next if start_index.nil? properties.copy_from(bound_variable_value, ret, start_index) interpolated_inputs << bound_variable_sym end end # @param args [Array<String>] the arguments to the method # @param erb_template_prerender [String] the template string # @param ret [String] the return value of the method # @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value # @param interpolated_inputs [Array<String>] the interpolated inputs def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs locals = args[1] locals.each do |local_name, local_value| next unless Contrast::Agent::Assess::Tracker.tracked?(local_value) next unless erb_template_prerender.include?(local_name.to_s) start_index = ret.index(local_value) next if start_index.nil? properties.copy_from(local_value, ret, start_index) interpolated_inputs << local_name end end end end end end end end end