class Railroader::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

association?(model_name, meth) click to toggle source

Check if method is actually an association in a Model

# File lib/railroader/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) click to toggle source
# File lib/railroader/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) 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/railroader/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) click to toggle source

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

# File lib/railroader/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) click to toggle source
# File lib/railroader/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) click to toggle source

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

# File lib/railroader/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) click to toggle source
# File lib/railroader/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) 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/railroader/checks/check_redirect.rb, line 66
def include_user_input? call, immediate = :immediate
  Railroader.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) click to toggle source

Returns true if exp is (probably) a model instance

# File lib/railroader/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) click to toggle source
# File lib/railroader/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) 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/railroader/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) click to toggle source
# File lib/railroader/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_check() click to toggle source
# File lib/railroader/checks/check_redirect.rb, line 13
def run_check
  Railroader.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) click to toggle source
# File lib/railroader/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) click to toggle source
# File lib/railroader/checks/check_redirect.rb, line 236
def slice_call? exp
  return unless call? exp
  exp.method == :slice
end
use_unsafe_hash_method?(arg) click to toggle source
# File lib/railroader/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