check_redirect.rb 3.3 KB
Newer Older
J
Justin Collins 已提交
1
require 'brakeman/checks/base_check'
J
Justin 已提交
2 3 4 5 6 7

#Reports any calls to +redirect_to+ which include parameters in the arguments.
#
#For example:
#
# redirect_to params.merge(:action => :elsewhere)
J
Justin Collins 已提交
8 9
class Brakeman::CheckRedirect < Brakeman::BaseCheck
  Brakeman::Checks.add self
J
Justin 已提交
10

11 12
  @description = "Looks for calls to redirect_to with user input as arguments"

J
Justin 已提交
13
  def run_check
14
    Brakeman.debug "Finding calls to redirect_to()"
J
Justin Collins 已提交
15

16 17 18 19 20 21
    @model_find_calls = Set[:all, :find, :find_by_sql, :first, :last, :new]

    if tracker.options[:rails3]
      @model_find_calls.merge [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
    end

22 23
    @tracker.find_call(:target => false, :method => :redirect_to).each do |res|
      process_result res
J
Justin 已提交
24 25 26
    end
  end

27
  def process_result result
28 29
    return if duplicate? result

30
    call = result[:call]
J
Justin 已提交
31

J
Justin Collins 已提交
32
    method = call.method
J
Justin 已提交
33 34

    if method == :redirect_to and not only_path?(call) and res = include_user_input?(call)
35 36
      add_result result

J
Justin Collins 已提交
37
      if res.type == :immediate
J
Justin 已提交
38 39 40 41 42
        confidence = CONFIDENCE[:high]
      else
        confidence = CONFIDENCE[:low]
      end

43
      warn :result => result,
J
Justin 已提交
44 45 46
        :warning_type => "Redirect",
        :message => "Possible unprotected redirect",
        :code => call,
J
Justin Collins 已提交
47
        :user_input => res.match,
J
Justin 已提交
48 49 50 51 52
        :confidence => confidence
    end
  end

  #Custom check for user input. First looks to see if the user input
53
  #is being output directly. This is necessary because of tracker.options[:check_arguments]
J
Justin 已提交
54 55 56
  #which can be used to enable/disable reporting output of method calls which use
  #user input as arguments.
  def include_user_input? call
57
    Brakeman.debug "Checking if call includes user input"
J
Justin 已提交
58

J
Justin Collins 已提交
59
    args = call.args
60
    first_arg = call.first_arg
61

J
Justin Collins 已提交
62 63 64
    if tracker.options[:ignore_redirect_to_model] and call? first_arg and
      (@model_find_calls.include? first_arg.method or first_arg.method.to_s.match(/^find_by_/)) and
      model_name? first_arg.target
65 66

      return false
J
Justin 已提交
67 68
    end

69
    args.each do |arg|
70 71 72
      if res = has_immediate_model?(arg)
        return Match.new(:immediate, res)
      elsif call? arg
J
Justin Collins 已提交
73 74 75 76
        if request_value? arg
          return Match.new(:immediate, arg)
        elsif request_value? arg[1]
          return Match.new(:immediate, arg[1])
J
Justin 已提交
77
        elsif arg[2] == :url_for and include_user_input? arg
J
Justin Collins 已提交
78
          return Match.new(:immediate, arg)
J
Justin 已提交
79 80 81 82
          #Ignore helpers like some_model_url?
        elsif arg[2].to_s =~ /_(url|path)$/
          return false
        end
83
      elsif request_value? arg
J
Justin Collins 已提交
84
        return Match.new(:immediate, arg)
J
Justin 已提交
85 86 87
      end
    end

88
    if tracker.options[:check_arguments]
J
Justin 已提交
89 90 91 92 93 94 95 96 97
      super
    else
      false
    end
  end

  #Checks +redirect_to+ arguments for +only_path => true+ which essentially
  #nullifies the danger posed by redirecting with user input
  def only_path? call
98 99 100 101 102
    arg = call.first_arg

    if hash? arg
      if value = hash_access(arg, :only_path)
        return true if true?(value)
J
Justin 已提交
103
      end
104 105
    elsif call? arg and arg.method == :url_for
      return check_url_for(arg)
J
Justin 已提交
106 107 108 109
    end

    false
  end
110 111 112 113

  #+url_for+ is only_path => true by default. This checks to see if it is
  #set to false for some reason.
  def check_url_for call
114 115 116 117 118
    arg = call.first_arg

    if hash? arg
      if value = hash_access(arg, :only_path)
        return false if false?(value)
119 120 121 122 123
      end
    end

    true
  end
J
Justin 已提交
124
end