class Brakeman::CheckRedirect

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

For example:

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

Constants

DANGEROUS_KEYS

Public Instance Methods

allow_other_host?(call) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 281
def allow_other_host? call
  opt = call.last_arg

  hash? opt and true? hash_access(opt, :allow_other_host)
end
association?(model_name, meth) click to toggle source

Check if method is actually an association in a Model

# File lib/brakeman/checks/check_redirect.rb, line 237
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) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 142
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) click to toggle source

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

# File lib/brakeman/checks/check_redirect.rb, line 183
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) click to toggle source

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

# File lib/brakeman/checks/check_redirect.rb, line 224
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
disallow_other_host?(call) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 287
def disallow_other_host? call
  opt = call.last_arg

  hash? opt and false? hash_access(opt, :allow_other_host)
end
explicit_host?(arg) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 161
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) click to toggle source

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

# File lib/brakeman/checks/check_redirect.rb, line 218
def friendly_model? exp
  call? exp and model_name? exp.target and exp.method == :friendly
end
has_only_path?(arg) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 153
def has_only_path? arg
  if value = hash_access(arg, :only_path)
    return true if true?(value)
  end

  false
end
include_user_input?(opt, immediate = :immediate) click to toggle source

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.

# File lib/brakeman/checks/check_redirect.rb, line 81
def include_user_input? opt, immediate = :immediate
  Brakeman.debug "Checking if call includes user input"

  # 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? opt

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

  if res = has_immediate_model?(opt)
    unless call? opt and opt.method.to_s =~ /_path/
      return Match.new(immediate, res)
    end
  elsif call? opt
    if request_value? opt
      return Match.new(immediate, opt)
    elsif opt.method == :url_for and include_user_input? opt.first_arg
      return Match.new(immediate, opt)
      #Ignore helpers like some_model_url?
    elsif opt.method.to_s =~ /_(url|path)\z/
      return false
    elsif opt.method == :url_from
      return false
    end
  elsif request_value? opt
    return Match.new(immediate, opt)
  elsif node_type? opt, :or
    return (include_user_input?(opt.lhs) or include_user_input?(opt.rhs))
  end

  if tracker.options[:check_arguments] and call? opt
    include_user_input? opt.first_arg, false  #I'm doubting if this is really necessary...
  else
    false
  end
end
model_instance?(exp) click to toggle source

Returns true if exp is (probably) a model instance

# File lib/brakeman/checks/check_redirect.rb, line 196
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) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 209
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) click to toggle source

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

# File lib/brakeman/checks/check_redirect.rb, line 124
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) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 35
def process_result result
  return unless original? result

  call = result[:call]
  opt = call.first_arg

  # Location is specified with `fallback_location:`
  # otherwise the arguments do not contain a location and
  # the call can be ignored
  if call.method == :redirect_back
    if hash? opt and location = hash_access(opt, :fallback_location)
      opt = location
    else
      return
    end
  end

  if not protected_by_raise?(call) and
      not only_path?(call) and
      not explicit_host?(opt) and
      not slice_call?(opt) and
      not safe_permit?(opt) and
      not disallow_other_host?(call) and
      res = include_user_input?(opt)

    if res.type == :immediate and not allow_other_host?(call)
      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,
      :cwe_id => [601]
  end
end
protected_by_raise?(call) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 272
def protected_by_raise? call
  raise_on_redirects? and
    not allow_other_host? call
end
raise_on_redirects?() click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 277
def raise_on_redirects?
  @raise_on_redirects ||= true?(tracker.config.rails.dig(:action_controller, :raise_on_open_redirects))
end
run_check() click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 13
def run_check
  @model_find_calls = Set[:all, :create, :create!, :find, :find_by_sql, :first, :first!, :last, :last!, :new, :sole]

  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

  if version_between? "7.0.0", "9.9.9"
    @model_find_calls << :find_sole_by
  end

  methods = [:redirect_to, :redirect_back, :redirect_back_or_to]

  @tracker.find_call(:target => false, :methods => methods).each do |res|
    process_result res
  end
end
safe_permit?(exp) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 258
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) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 251
def slice_call? exp
  return unless call? exp
  exp.method == :slice
end
use_unsafe_hash_method?(arg) click to toggle source
# File lib/brakeman/checks/check_redirect.rb, line 138
def use_unsafe_hash_method? arg
  return call_has_param(arg, :to_unsafe_hash) || call_has_param(arg, :to_unsafe_h)
end