Class: Brakeman::CheckRedirect
- Inherits:
-
BaseCheck
- Object
- SexpProcessor
- BaseCheck
- Brakeman::CheckRedirect
- 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
Constants included from Util
Util::ALL_COOKIES, Util::ALL_PARAMETERS, Util::COOKIES, Util::COOKIES_SEXP, Util::PARAMETERS, Util::PARAMS_SEXP, Util::PATH_PARAMETERS, Util::QUERY_PARAMETERS, Util::REQUEST_COOKIES, Util::REQUEST_ENV, Util::REQUEST_PARAMETERS, Util::REQUEST_PARAMS, Util::SESSION, Util::SESSION_SEXP
Constants inherited from SexpProcessor
Instance Attribute Summary
Attributes inherited from BaseCheck
Attributes inherited from SexpProcessor
Instance Method Summary collapse
-
#association?(model_name, meth) ⇒ Boolean
Check if method is actually an association in a Model.
- #call_has_param(arg, key) ⇒ Object
-
#check_url_for(call) ⇒ Object
url_for
is only_path => true by default. -
#decorated_model?(exp) ⇒ Boolean
Returns true if exp is (probably) a decorated model instance using the Draper gem.
- #explicit_host?(arg) ⇒ Boolean
-
#friendly_model?(exp) ⇒ Boolean
Returns true if exp is (probably) a friendly model instance using the FriendlyId gem.
- #has_only_path?(arg) ⇒ Boolean
-
#include_user_input?(call, immediate = :immediate) ⇒ Boolean
Custom check for user input.
-
#model_instance?(exp) ⇒ Boolean
Returns true if exp is (probably) a model instance.
- #model_target?(exp) ⇒ Boolean
-
#only_path?(call) ⇒ Boolean
Checks
redirect_to
arguments for only_path => true which essentially nullifies the danger posed by redirecting with user input. - #process_result(result) ⇒ Object
- #run_check ⇒ Object
- #safe_permit?(exp) ⇒ Boolean
- #slice_call?(exp) ⇒ Boolean
- #use_unsafe_hash_method?(arg) ⇒ Boolean
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
#current_file_name, #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
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
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
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
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
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.
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.[: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.[: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
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
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
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_check ⇒ Object
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.[: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
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
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
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 |