check_link_to_href.rb 3.7 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
                           :field_field, :fields_for, :hidden_field,
                           :hidden_field, :hidden_field_tag, :image_tag, :label,
J
Justin Collins 已提交
18
                           :mail_to, :polymorphic_url, :radio_button, :select,
N
Neil Matatall 已提交
19 20
                           :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

    @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
37
    url_arg = process call.second_arg
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
    return if call? url_arg and ignore_call? url_arg.target, url_arg.method
44

45
    if input = has_immediate_user_input?(url_arg)
46
      message = "Unsafe #{friendly_type_of input} in link_to href"
47 48 49 50 51

      unless duplicate? result
        add_result result
        warn :result => result,
          :warning_type => "Cross Site Scripting", 
J
Justin Collins 已提交
52
          :warning_code => :xss_link_to_href,
53
          :message => message,
J
Justin Collins 已提交
54
          :user_input => input.match,
55 56
          :confidence => CONFIDENCE[:high],
          :link_path => "link_to_href"
57 58 59 60 61 62
      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)

63 64 65 66
    elsif array? url_arg
      # Just like models, polymorphic path/url is called if the argument is 
      # an array      

67 68 69 70 71 72 73 74
    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
75
      if @matched.type == :model and not tracker.options[:ignore_model_output]
76
        message = "Unsafe model attribute in link_to href"
77
      elsif @matched.type == :params
78 79 80 81 82 83 84
        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", 
J
Justin Collins 已提交
85
          :warning_code => :xss_link_to_href,
86
          :message => message,
J
Justin Collins 已提交
87
          :user_input => @matched.match,
88 89
          :confidence => CONFIDENCE[:med],
          :link_path => "link_to_href"
90 91 92
      end
    end
  end
J
Justin Collins 已提交
93

J
Justin Collins 已提交
94 95 96 97
  def ignored_method? target, method
    @ignore_methods.include? method or
      method.to_s =~ /_path$/ or
      (target.nil? and method.to_s =~ /_url$/)
J
Justin Collins 已提交
98
  end
99
end