check_link_to_href.rb 3.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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:<encoded XSS> 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
15
    @ignore_methods = Set[:button_to, :check_box,
N
Neil Matatall 已提交
16 17 18 19 20
                           :field_field, :fields_for, :hidden_field,
                           :hidden_field, :hidden_field_tag, :image_tag, :label,
                           :mail_to, :radio_button, :select,
                           :submit_tag, :text_area, :text_field,
                           :text_field_tag, :url_encode, :url_for,
21
                           :will_paginate].merge(tracker.options[:url_safe_methods] || [])
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

    @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[3][2]
38 39 40 41 42

    #Ignore situations where the href is an interpolated string
    #with something before the user input
    return if node_type?(url_arg, :string_interp) && !url_arg[1].chomp.empty?

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    type, match = has_immediate_user_input? url_arg

    if type
      case type
      when :params
        message = "Unsafe parameter value in link_to href"
      when :cookies
        message = "Unsafe cookie value in link_to href"
      else
        message = "Unsafe user input value in link_to href"
      end

      unless duplicate? result
        add_result result
        warn :result => result,
          :warning_type => "Cross Site Scripting", 
          :message => message,
          :confidence => CONFIDENCE[:high]
      end
    elsif has_immediate_model? 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 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 == :model and not tracker.options[:ignore_model_output]
        message = "Unsafe model attribute in link_to href"
      elsif @matched == :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", 
          :message => message,
          :confidence => CONFIDENCE[:med]
      end
    end
  end
end