Drawbacks and support for adopting a custom `redir

2019-08-31 08:29发布

问题:

In order to redirect users to a custom web page each time params contain a redirect_uri value I am evaluating to use a custom redirect_to method this way (by @kiddorails):

class ApplicationController < ActionController::Base
  def custom_redirect_to(url)
    redirect_to (params[:redirect_uri].present? ? params[:redirect_uri] : url)
  end
end

def create
  @post = Post.new(params)
  if @post.save
    custom_redirect_to @post
  else
    render 'new'
  end
end

However I would like to be aware of possible drawbacks and to receive support for adopting the above solution.

回答1:

Allowing a redirect target to be set through a URL parameter without any validation is potentially dangerous for your users, because it makes fishing attempts easier.

An attacker can send links to your users such as

http://my-trusted-site.com/some/action/path?redirect_uri=malicious-site-that-looks-like-trusted-site.com

, and many users will only see the domain part and fail to realize where they end up after clicking that link.

The Open Web Application Security Project (OWASP) therefor considers this a vulnerability:

Unvalidated redirects and forwards are possible when a web application accepts untrusted input that could cause the web application to redirect the request to a URL contained within untrusted input. By modifying untrusted URL input to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. Because the server name in the modified link is identical to the original site, phishing attempts may have a more trustworthy appearance. Unvalidated redirect and forward attacks can also be used to maliciously craft a URL that would pass the application’s access control check and then forward the attacker to privileged functions that they would normally not be able to access.

It's important that you check the redirect_uri parameter carefully before executing the redirect.

But since proper validation is tricky and prone to errors, an even better idea is not to accept URI parameters in the first place, but to allow certain keywords instead that specify where the user will be redirected.

class ApplicationController < ActionController::Base

    protected

  def dynamic_redirect_to(default_route, options)
    options.stringify_keys!

    redirect_to options.fetch(params[:redirect_to], default_route)
  end
end

You can now define any number of allowed keywords in advance which may be used as the ?redirect_to= parameter:

def create
  @post = Post.new(params)
  if @post.save
    dynamic_redirect_to post_path(@post), edit: edit_post_path(@post)
  else
    render 'new'
  end
end

If ?redirect_to=edit is set, the user is redirected back to the edit page. If the parameter is not set or contains an unspecified keyword, she is redirected to the default post_path(@post) instead.



回答2:

I agree with what janfoeh said above. But to implement your requirement, I hacked around in Rails code of redirection to make it simpler.

Make a file config/initializers/redirecting.rb with:

require 'abstract_controller/base'
module ActionController
  module Redirecting
    extend ActiveSupport::Concern

    include AbstractController::Logger
    include ActionController::RackDelegation
    include ActionController::UrlFor

    def _compute_redirect_to_location(options)
      if redirect_url = session.delete(:redirect_uri)
        options = redirect_url
      end
      case options
        # The scheme name consist of a letter followed by any combination of
        # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
        # characters; and is terminated by a colon (":").
        # See http://tools.ietf.org/html/rfc3986#section-3.1
        # The protocol relative scheme starts with a double slash "//".
      when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
        options
      when String
        request.protocol + request.host_with_port + options
      when :back
        request.headers["Referer"] or raise RedirectBackError
      when Proc
        _compute_redirect_to_location request, options.call
      else
        url_for(options)
      end.delete("\0\r\n")
    end
  end
end

In your app/controllers/application_controller.rb :

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.

  before_action :set_redirect_uri
  protect_from_forgery with: :exception

  def set_redirect_uri
    session[:redirect_uri] = params[:redirect_uri] if params[:redirect_uri].present?
  end
end

And Voila! Now you can continue to use the original redirect_to, and whenever redirect_uri is supplied in the url, it will set that url in session and automatically override. :)

Note: I am clearing session[:redirect_uri] only when redirect_to is called. You can easily modify that behavior to reset this session depending upon the requirement.