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 collapse

DANGEROUS_KEYS =
[:host, :subdomain, :domain, :port]

Constants inherited from BaseCheck

BaseCheck::CONFIDENCE

Constants included from Util

Util::ALL_COOKIES, Util::ALL_PARAMETERS, Util::COOKIES, Util::COOKIES_SEXP, Util::DIR_CONST, Util::LITERALS, Util::PARAMETERS, Util::PARAMS_SEXP, Util::PATH_PARAMETERS, Util::QUERY_PARAMETERS, Util::REQUEST_COOKIES, Util::REQUEST_ENV, Util::REQUEST_PARAMETERS, Util::REQUEST_PARAMS, Util::REQUEST_REQUEST_PARAMETERS, Util::SAFE_LITERAL, 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_array, #process_call, #process_cookies, #process_default, #process_dstr, #process_if, #process_params

Methods included from Messages

#msg, #msg_code, #msg_cve, #msg_file, #msg_input, #msg_lit, #msg_plain, #msg_version

Methods included from Util

#array?, #block?, #call?, #camelize, #class_name, #constant?, #contains_class?, #cookies?, #dir_glob?, #false?, #hash?, #hash_access, #hash_insert, #hash_iterate, #integer?, #kwsplat?, #literal?, #make_call, #node_type?, #number?, #params?, #pluralize, #rails_version, #regexp?, #remove_kwsplat, #request_env?, #request_value?, #result?, #safe_literal, #safe_literal?, #safe_literal_target?, #set_env_defaults, #sexp?, #string?, #string_interp?, #symbol?, #template_path_to_name, #true?, #underscore

Methods included from ProcessorHelper

#current_file, #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)

222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/brakeman/checks/check_redirect.rb', line 222

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

#call_has_param(arg, key) ⇒ Object


127
128
129
130
131
132
133
134
135
136
# File 'lib/brakeman/checks/check_redirect.rb', line 127

def call_has_param arg, key
  if call? arg and call? arg.target
    target = arg.target
    method = target.method

    node_type? target.target, :params and method == key
  else
    false
  end
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.


168
169
170
171
172
173
174
175
176
177
178
# File 'lib/brakeman/checks/check_redirect.rb', line 168

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)

209
210
211
212
213
214
215
216
217
218
219
# File 'lib/brakeman/checks/check_redirect.rb', line 209

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)

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/brakeman/checks/check_redirect.rb', line 146

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)

203
204
205
# File 'lib/brakeman/checks/check_redirect.rb', line 203

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

#has_only_path?(arg) ⇒ Boolean

Returns:

  • (Boolean)

138
139
140
141
142
143
144
# File 'lib/brakeman/checks/check_redirect.rb', line 138

def has_only_path? arg
  if value = hash_access(arg, :only_path)
    return true if true?(value)
  end

  false
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
104
105
# 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)
    unless call? arg and arg.method.to_s =~ /_path/
      return Match.new(immediate, res)
    end
  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)

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

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)

194
195
196
197
198
199
# File 'lib/brakeman/checks/check_redirect.rb', line 194

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)

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

def only_path? call
  arg = call.first_arg

  if hash? arg
    return has_only_path? arg
  elsif call? arg and arg.method == :url_for
    return check_url_for(arg)
  elsif call? arg and hash? arg.first_arg and use_unsafe_hash_method? arg
    return has_only_path? arg.first_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 unless original? result

  call = result[:call]
  method = call.method

  opt = call.first_arg

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

    if res.type == :immediate
      confidence = :high
    else
      confidence = :weak
    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

#safe_permit?(exp) ⇒ Boolean

Returns:

  • (Boolean)

243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/brakeman/checks/check_redirect.rb', line 243

def safe_permit? exp
  if call? exp and params? exp.target and exp.method == :permit
    exp.each_arg do |opt|
      if symbol? opt and DANGEROUS_KEYS.include? opt.value
        return false 
      end
    end

    return true
  end

  false
end

#slice_call?(exp) ⇒ Boolean

Returns:

  • (Boolean)

236
237
238
239
# File 'lib/brakeman/checks/check_redirect.rb', line 236

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

#use_unsafe_hash_method?(arg) ⇒ Boolean

Returns:

  • (Boolean)

123
124
125
# File 'lib/brakeman/checks/check_redirect.rb', line 123

def use_unsafe_hash_method? arg
  return call_has_param(arg, :to_unsafe_hash) || call_has_param(arg, :to_unsafe_h)
end