# 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) 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 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 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