Class: Brakeman::CheckRedirect

Inherits:
BaseCheck show all
Defined in:
lib/brakeman/checks/check_redirect.rb

Overview

Reports any calls to redirect_to which include parameters in the arguments.

For example:

redirect_to params.merge(:action => :elsewhere)

Constant Summary

Constants inherited from BaseCheck

BaseCheck::CONFIDENCE

Constants included from Util

Util::ALL_PARAMETERS, Util::COOKIES, Util::COOKIES_SEXP, Util::PARAMETERS, Util::PARAMS_SEXP, Util::PATH_PARAMETERS, Util::QUERY_PARAMETERS, Util::REQUEST_ENV, Util::REQUEST_PARAMETERS, Util::REQUEST_PARAMS, Util::SESSION, Util::SESSION_SEXP

Constants inherited from SexpProcessor

SexpProcessor::VERSION

Instance Attribute Summary

Attributes inherited from BaseCheck

#tracker, #warnings

Attributes inherited from SexpProcessor

#context, #env, #expected

Instance Method Summary collapse

Methods inherited from BaseCheck

#add_result, inherited, #initialize, #process_call, #process_cookies, #process_default, #process_dstr, #process_if, #process_params

Methods included from Util

#array?, #block?, #call?, #camelize, #class_name, #constant?, #contains_class?, #context_for, #cookies?, #false?, #file_by_name, #file_for, #github_url, #hash?, #hash_access, #hash_insert, #hash_iterate, #integer?, #make_call, #node_type?, #number?, #params?, #pluralize, #rails_version, #regexp?, #relative_path, #request_env?, #request_value?, #result?, #set_env_defaults, #sexp?, #string?, #string_interp?, #symbol?, #table_to_csv, #template_path_to_name, #true?, #truncate_table, #underscore

Methods included from ProcessorHelper

#process_all, #process_all!, #process_call_args, #process_call_defn?, #process_class, #process_module

Methods inherited from SexpProcessor

#in_context, #initialize, #process, processors, #scope

Constructor Details

This class inherits a constructor from Brakeman::BaseCheck

Instance Method Details

#association?(model_name, meth) ⇒ Boolean

Check if method is actually an association in a Model

Returns:

  • (Boolean)


197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/brakeman/checks/check_redirect.rb', line 197

def association? model_name, meth
  if call? model_name
    return association? model_name.target, meth
  elsif model_name? model_name
    model = tracker.models[class_name(model_name)]
  else
    return false
  end

  return false unless model

  model.association? meth
end

#check_url_for(call) ⇒ Object

url_for is only_path => true by default. This checks to see if it is set to false for some reason.



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/brakeman/checks/check_redirect.rb', line 143

def check_url_for call
  arg = call.first_arg

  if hash? arg
    if value = hash_access(arg, :only_path)
      return false if false?(value)
    end
  end

  true
end

#decorated_model?(exp) ⇒ Boolean

Returns true if exp is (probably) a decorated model instance using the Draper gem

Returns:

  • (Boolean)


184
185
186
187
188
189
190
191
192
193
194
# File 'lib/brakeman/checks/check_redirect.rb', line 184

def decorated_model? exp
  if node_type? exp, :or
    decorated_model? exp.lhs or decorated_model? exp.rhs
  else
    tracker.config.has_gem? :draper and
    call? exp and
    node_type?(exp.target, :const) and
    exp.target.value.to_s.match(/Decorator$/) and
    exp.method == :decorate
  end
end

#explicit_host?(arg) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/brakeman/checks/check_redirect.rb', line 121

def explicit_host? arg
  return unless sexp? arg

  if hash? arg
    if value = hash_access(arg, :host)
      return !has_immediate_user_input?(value)
    end
  elsif call? arg
    target = arg.target

    if hash? target and value = hash_access(target, :host)
      return !has_immediate_user_input?(value)
    elsif call? arg
      return explicit_host? target
    end
  end

  false
end

#friendly_model?(exp) ⇒ Boolean

Returns true if exp is (probably) a friendly model instance using the FriendlyId gem

Returns:

  • (Boolean)


178
179
180
# File 'lib/brakeman/checks/check_redirect.rb', line 178

def friendly_model? exp
  call? exp and model_name? exp.target and exp.method == :friendly
end

#include_user_input?(call, immediate = :immediate) ⇒ Boolean

Custom check for user input. First looks to see if the user input is being output directly. This is necessary because of tracker.options which can be used to enable/disable reporting output of method calls which use user input as arguments.

Returns:

  • (Boolean)


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
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/brakeman/checks/check_redirect.rb', line 66

def include_user_input? call, immediate = :immediate
  Brakeman.debug "Checking if call includes user input"

  arg = call.first_arg

  # if the first argument is an array, rails assumes you are building a
  # polymorphic route, which will never jump off-host
  return false if array? arg

  if tracker.options[:ignore_redirect_to_model]
    if model_instance?(arg) or decorated_model?(arg)
      return false
    end
  end

  if res = has_immediate_model?(arg)
    return Match.new(immediate, res)
  elsif call? arg
    if request_value? arg
      return Match.new(immediate, arg)
    elsif request_value? arg.target
      return Match.new(immediate, arg.target)
    elsif arg.method == :url_for and include_user_input? arg
      return Match.new(immediate, arg)
      #Ignore helpers like some_model_url?
    elsif arg.method.to_s =~ /_(url|path)\z/
      return false
    end
  elsif request_value? arg
    return Match.new(immediate, arg)
  end

  if tracker.options[:check_arguments] and call? arg
    include_user_input? arg, false  #I'm doubting if this is really necessary...
  else
    false
  end
end

#model_instance?(exp) ⇒ Boolean

Returns true if exp is (probably) a model instance

Returns:

  • (Boolean)


156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/brakeman/checks/check_redirect.rb', line 156

def model_instance? exp
  if node_type? exp, :or
    model_instance? exp.lhs or model_instance? exp.rhs
  elsif call? exp
    if model_target? exp and
      (@model_find_calls.include? exp.method or exp.method.to_s.match(/^find_by_/))
      true
    else
      association?(exp.target, exp.method)
    end
  end
end

#model_target?(exp) ⇒ Boolean

Returns:

  • (Boolean)


169
170
171
172
173
174
# File 'lib/brakeman/checks/check_redirect.rb', line 169

def model_target? exp
  return false unless call? exp
  model_name? exp.target or
  friendly_model? exp.target or
  model_target? exp.target
end

#only_path?(call) ⇒ Boolean

Checks redirect_to arguments for only_path => true which essentially nullifies the danger posed by redirecting with user input

Returns:

  • (Boolean)


107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/brakeman/checks/check_redirect.rb', line 107

def only_path? call
  arg = call.first_arg

  if hash? arg
    if value = hash_access(arg, :only_path)
      return true if true?(value)
    end
  elsif call? arg and arg.method == :url_for
    return check_url_for(arg)
  end

  false
end

#process_result(result) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/brakeman/checks/check_redirect.rb', line 31

def process_result result
  return if duplicate? result

  call = result[:call]

  method = call.method

  if method == :redirect_to and
      not only_path?(call) and
      not explicit_host?(call.first_arg) and
      not slice_call?(call.first_arg) and
      res = include_user_input?(call)

    add_result result

    if res.type == :immediate
      confidence = CONFIDENCE[:high]
    else
      confidence = CONFIDENCE[:low]
    end

    warn :result => result,
      :warning_type => "Redirect",
      :warning_code => :open_redirect,
      :message => "Possible unprotected redirect",
      :code => call,
      :user_input => res,
      :confidence => confidence
  end
end

#run_checkObject



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/brakeman/checks/check_redirect.rb', line 13

def run_check
  Brakeman.debug "Finding calls to redirect_to()"

  @model_find_calls = Set[:all, :create, :create!, :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

  if version_between? "4.0.0", "9.9.9"
    @model_find_calls.merge [:find_by, :find_by!, :take]
  end

  @tracker.find_call(:target => false, :method => :redirect_to).each do |res|
    process_result res
  end
end

#slice_call?(exp) ⇒ Boolean

Returns:

  • (Boolean)


211
212
213
214
# File 'lib/brakeman/checks/check_redirect.rb', line 211

def slice_call? exp
  return unless call? exp
  exp.method == :slice
end