require 'brakeman/checks/check_cross_site_scripting' #Checks for calls to link_to which pass in potentially hazardous data #to the second argument. While this argument must be html_safe to not break #the html, it must also be url safe as determined by calling a #:url_safe_method. This prevents attacks such as javascript:evil() or #data: which is html_safe, but not safe as an href #Props to Nick Green for the idea. class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo Brakeman::Checks.add self @description = "Checks to see if values used for hrefs are sanitized using a :url_safe_method to protect against javascript:/data: XSS" def run_check @ignore_methods = Set[:button_to, :check_box, :field_field, :fields_for, :hidden_field, :hidden_field, :hidden_field_tag, :image_tag, :label, :mail_to, :polymorphic_url, :radio_button, :select, :submit_tag, :text_area, :text_field, :text_field_tag, :url_encode, :url_for, :will_paginate].merge(tracker.options[:url_safe_methods] || []) @models = tracker.models.keys @inspect_arguments = tracker.options[:check_arguments] methods = tracker.find_call :target => false, :method => :link_to methods.each do |call| process_result call end end def process_result result #Have to make a copy of this, otherwise it will be changed to #an ignored method call by the code above. call = result[:call] = result[:call].dup @matched = false url_arg = process call.second_arg #Ignore situations where the href is an interpolated string #with something before the user input return if string_interp?(url_arg) && !url_arg[1].chomp.empty? return if call? url_arg and ignore_call? url_arg.target, url_arg.method if input = has_immediate_user_input?(url_arg) message = "Unsafe #{friendly_type_of input} in link_to href" unless duplicate? result add_result result warn :result => result, :warning_type => "Cross Site Scripting", :warning_code => :xss_link_to_href, :message => message, :user_input => input, :confidence => CONFIDENCE[:high], :link_path => "link_to_href" end elsif has_immediate_model? url_arg or model_find_call? url_arg # Decided NOT warn on models. polymorphic_path is called it a model is # passed to link_to (which passes it to url_for) elsif array? url_arg # Just like models, polymorphic path/url is called if the argument is # an array elsif hash? url_arg # url_for uses the key/values pretty carefully and I don't see a risk. # IF you have default routes AND you accept user input for :controller # and :only_path, then MAYBE you could trigger a javascript:/data: # attack. elsif @matched if @matched.type == :model and not tracker.options[:ignore_model_output] message = "Unsafe model attribute in link_to href" elsif @matched.type == :params message = "Unsafe parameter value in link_to href" end if message and not duplicate? result add_result result warn :result => result, :warning_type => "Cross Site Scripting", :warning_code => :xss_link_to_href, :message => message, :user_input => @matched, :confidence => CONFIDENCE[:med], :link_path => "link_to_href" end end end def ignore_call? target, method decorated_model? method or super end def decorated_model? method tracker.config.has_gem? :draper and method == :decorate end def ignored_method? target, method @ignore_methods.include? method or method.to_s =~ /_path$/ or (target.nil? and method.to_s =~ /_url$/) end def model_find_call? exp return unless call? exp MODEL_METHODS.include? exp.method or exp.method.to_s =~ /^find_by_/ end end