Why is it safe to use before_action :authenticate_admin? - ruby-on-rails

I am new to both web and Rails, and I found that it is a common usage to put something like this in the beginning of a controller:
before_action :authenticate_admin, only: [:only_admin]
where
def authenticate_admin
unless current_user.admin?
flash[:alert] = "You are not admin!"
redirect_to '/'
end
end
Considering if it's possible to somehow ignore this redirection from the client side, I feel really unsafe. How is redirect_to implemented? Why can this usage assure safety?

redirect_to will - well terminate your current HTTP request and require the client to initiate a new call to /, which is supposed to be a public page. You could redirect wherever you want.
In other words, it's a HTTP redirect.
The code first checks if the user is an admin. If not, it "alerts" the message and redirects the client to /.
If you are asking if you can ignore this redirect on the server by removing this call, you shouldn't ignore the redirect, otherwise you just check if the user is admin but are not taking any action. Effectively, the code is obligating the client to go to /. Assuming that that URL is safe to use for non-admins, this is safe.
If you are asking if the client (the browser) is able to ignore the redirect - it's not possible for the client to ignore this redirect; rails will terminate the connection and issue a HTTP redirect, which the browser can't ignore, because it's on HTTP level.
Consider reframing your question to be more precise

Related

How to fix Brakeman redirect issue with multiple rest endpoints

I'm currently working on a solution for doing redirects in RoR because I got an error within the brakeman report saying that I have to fix redirects in a proper way.
I understand what the message says and how to solve it within one controller action.
But now I got the following. During the instantiation of the new method I set the HTTP_REFERER header which can be used in the create action.
This is giving me a Brakeman warning which can be found on the following link
Suppose I got the following controller with multiple endpoints:
def new
#my_model_set = MyModel.new
#referer = request.env['HTTP_REFERER'] # We want to redirect to this referer after a create
end
def create
...
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
if params[:referer].present?
redirect_to params[:referer]
else
redirect_to admin_my_model_set_path
end
else
...
end
end
I already tried to fix this by using the redirect_back method from RoR but that's using the referer link of the create method which I don't want to use.
if #my_model_set.save
flash_message :success, t('notification.item_created', type: #my_model_set.model_name.human)
redirect_back(fallback_location: admin_my_model_set_path)
else
...
end
The main problem in your code is that params[:referer] can be set by your user (or an attacker forging a link for your user) to an arbitrary value by appending ?referer=https://malicious.site to the url. You will then redirect to that, which is an open redirect vulnerability.
You could also argue that the referer header is technically user input, and you will be redirecting to it, but I would say in most cases and modern browsers that would probably be an acceptable risk, because an attacker does not really have a way to exploit it (but it might depend on the exact circumstances).
One solution that immediately comes to mind for similar cases would be the session - but on the one hand this is a rest api if I understand correctly, so there is no session, and on the other hand, it would still not be secure against an attacker linking to your #new endpoint from a malicious domain.
I think you should validate the domain before you redirect to it. If there is a common pattern (like for example if all of these are subdomains of yourdomain.com), validate for that. Or you could have your users register their domains first before you redirect to it (see how OAuth2 works for example, you have to register your app domain first before the user can get redirected there with a token).
If your user might just come from anywhere to #new and you want to send them back wherever they came from - that I think is not a good requirement, you should probably not do that, or you should carefully assess the risk and consciously accept it if you want to for some reason. In most cases there is a more secure solution.

Redirecting in before_filter and go on with original request

In my app I use OAuth to authenticate in other sources, so that I need to keep their tokens for each user. I store tokens in entities I call openids. In that way, I want to check if openid is valid (token is not expired) before creating and updating some models' objects. If token is expired, I want to redirect user to logging in and then go on with the action that was interrupted by login (creating, updating, etc).
My idea was to save request.fullpath in cookies and then, when the authorization is over, in the method that called when callback of login method initiated, redirect user to previous action by using redirect_to cookies[:auth_back_path].
The thing is for example, update method needs PATCH request to be called. redirect_to provides only GET methods.
So that in one of the controllers I have (ones_controller.rb):
before_filter :check_openids, except: [:relogin, :show]
....
def check_openids
unless current_user.openid_actual?
cookies[:auth_back_path] = request.fullpath
redirect_to '/auth'
end
end
Can, please anyone tell, how can I make it in a right way? I really want to realise how it all's going.
P.S.:
I read here that force doing patch or delete (when I will call destroy method) is not a good idea.

Is it safe to accept URL parameters for populating the `url_for` method?

I am using Ruby on Rails 4.1.1 and I am thinking to accept parameters (through URL query strings) that are passed directly to the url_for method, this way:
# URL in the browser
http://www.myapp.com?redirect_to[controller]=users&redirect_to[action]=show&redirect_to[id]=1
# Controller
...
redirect_to url_for(params[:redirect_to].merge(:only_path => true))
Adopting the above approach users can be redirected after performing an action. However, I think people can enter arbitraryparams that can lead to security issues...
Is it safe to accept URL parameters for populating the url_for method? What are pitfalls? What can happen in the worst case?
By logging params during requests to my application I noted Rails adds always :controller and action parameters. Maybe that confirms url_for can be used the above way since it is protected internally and works as-like Rails is intended to.
This it is safe internally as Ruby On Rails will only be issuing a HTTP redirect response.
As you are using only_path this will protect you from an Open redirect vulnerability. This is where an email is sent by an attacker containing a link in the following format (say your site is example.com).
https://example.com?foo=bar&bar=foo&redirect=http://evil.com
As the user checks the URL and sees it is on the example.com domain they beleive it is safe so click the link. However, if there's an open redirect then the user ends up on evil.com which could ask for their example.com password without the user noticing.
Redirecting to a relative path only on your site fixes any vulnerability.
In your case you are giving users control of your controller, action and parameters. As long as your GET methods are safe (i.e. no side-effects), an attacker could not use this by creating a crafted link that the user opens.
In summary, from the information provided I don't see any risk from phishing URLs to your application.
Rails redirect_to sets the HTTP status code to 302 Found which tells the browser to GET the new path as you defined it by url_for. GET is a considered a safe method in contrast to
... methods such as POST, PUT, DELETE and PATCH [which] are intended for
actions that may cause side effects either on the server, or external
side effects ...
The only problem would have been if someone could gain access to methods such as create and destroy. Since these methods use HTTP methods other than GET (respectively POST and DELETE) it should be no problem.
Another danger here is if you go beyond CRUD methods of REST and have a custom method which responses to GET and changes the database state:
routes.rb
resources something do
member do
get :my_action
end
end
SomethingController
def my_action
# delte some records
end
For future ref:
Rails has a number of security measurements which may also interest you.
It's not exactly an answer, just wanted to point out that you shouldn't use something like
url_for(params)
because one could pass host and port as params and thus the url could lead to another site and it can get worse if it gets cached or something.
Don't know if it threatens anything, but hey, it's worth pointing out

Redirecting to an external URL with Rails doesn't work

In a controller I attempt to do
redirect_to #url
#url is an https url which is correctly formatted
When I do it nothing happens in the browser
Logs show that there is a redirection, but in chrome inspect I see
Request URL:https://..myurl...
Request Headers CAUTION: Provisional headers are shown.
Origin:http://localhost:3000
Referer:http://localhost:3000/cars/105
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36
Query String Parametersview sourceview URL encoded
tId:6f6bfeaf-fd47-42ae-8e43-6b7118d21b0b
The network tab shows that it was canceled..
Am I missing something? Why isn't the redirection happening?
Based on your statement that you saw the request was canceled in the network tab in Chrome, it sounds like you're trying to do this in an AJAX request. Cross-domain AJAX requests are not supported by default. If you have control over the https://..myurl... application, you can implement CORS or JSONP.
Edit
A third approach, which is what you mentioned doing in one of your comments, is to get around the restriction on cross-site redirects for AJAX requests by redirecting on the client side, after the AJAX request completes (by setting window.location). However, if your ultimate goal is to have the redirect happen transparently—as part of the AJAX request rather than after it—you will need to implement CORS or JSONP.
After searching a while on (CAUTION: Provisional headers are shown)
I think it is related to some chrome extensions such as ad blocker or net-internals
Quoting another stackoverflow question"The message is there because the request to retrieve that resource was never made, so the headers being shown are not the real thing. As explained in the issue you referenced, the real headers are updated when the server responds, but there is no response if the request was blocked."
You can make sure that all the extension that are capable of blocking requests are disabled and try it again.
I think you need to specify that the protocol you want to use is using SSL. The ActionController::Base#redirect_to method takes an options hash, one of the parameters of which is :protocol which allows you to define:
redirect_to :protocol => 'https://'
If all of your redirects are going to be done via SSL it would make sense to put this in a before_action in your controller. You could define a method like:
def redirect_via_https
redirect_to :protocol => "https://" unless (request.ssl? || request.local?)
end
And add this to your controller:
class SpecificController
before_action :redirect_via_https
end
Can you visit the page and see it in Chrome without any security warnings?
I've had css etc. not load because I was requesting it via https to a local server with a unverified certificate. It's possible Chrome is checking behind the scenes before going to the url but I don't know that for sure.
Try redirect_to https://www.google.com and see if that works.
what is your rails version, because redirect_to method is working for external urls.
class HomeController < ApplicationController
def index
#url = "https://facebook.com"
redirect_to #url
end
It works.
I would suggest several troubleshooting steps:
Try another browser (Safari, Firefox), if it works on that browser, then perhaps a Chrome extension is blocking your visiting that redirected URL.
Try redirecting to other URLs, try both with and without https.
Check if your Rails action is an AJAX call or not. If it's an ajax request then redirect_to wouldn't work.
I think that the suspicions around AJAX are well founded. It's really hard to guess at what might be going on, given the narrow view into your application that we are presented with. Try changing
redirect_to #url
to
if request.xhr?
render :update do |page|
page.redirect_to #url
end
else
redirect_to #url
end
Also, as an aside, #url probably has excessive scope, given the redirect. You can most likely use url just as effectively.
Edit:
In light of information I previously overlooked, I am certain that the previous solution will correct the issue. Basically, some browsers are stricter about how they handle responses to xhr requests. With a standard redirect_to, you can potentially send a "regular" response for a xhr request. This can happen even if you have a *.js.rjs or *.js.erb that would typically contain the redirect code, as the redirect_to call will prevent that code from even being executed. If you have an app that has js that gracefully degrades, there is a likelihood that you will end up running into this issue sooner or later, as you appear to have a couple of times. I've found a couple of good ways to handle the basic issue.
Option 1:
One way is to simply adjust the redirect to look like this: redirect_to( #url ) unless request.xhr?, and then add the appropriate redirection logic to your *.js.erb file. This method is nice if you have additional javascript that needs to be run, in addition to the redirect.
Option 2:
Another solution that works well is to add something like this to your application controller:
def redirect_to(args)
if request.xhr?
render js: "window.location = '#{ _compute_redirect_to_location args }'"
else
super args
end
end
This is nice, because you don't need to sift through and modify your existing code, it just starts working everywhere. The other nice thing is that you can still qualify the redirect as the previous option, to allow for more complex javascript. You can even go crazy and add some additional application specific options, like a flash message, if you want to further extend the method and really make it work for you.
Option 3:
If you're not down with overriding existing methods, you could add something like:
def firm_redirect_to(url)
if request.xhr?
render js: "window.location = '#{ url }'"
else
redirect_to url
end
end
But then you have to modify all of your code.
I personally prefer a variation of option 2... Anyway, hope this helps - good luck.

getting the flash hash to persist through redirects

My basic use case is do some processing, set flash[:notice], and then redirect to a new page. From what I can tell, redirects reset the flash tag (please correct me if I'm wrong). Is there a way to gain persistence? Using sessions isn't an option, and I have hacked around the problem using cookies, but I think there's got to be a better way.
The flash hash persists for exactly one redirect or render. So you should be fine with the default settings.
If you need to keep the flash hash for another request/redirect, you can call flash.keep.
flash.keep # keep the entire flash hash around for an extra request.
flash.keep(:notice) # keep just flash[:notice] for an extra request.
Something to be aware of in at least Rails v3.2.1 is that the flash will persist through a redirect if its not referenced at all through at least 1 redirect and load the same view after. This is a pseudo code of my recent experience:
def some_action
(code that may set a flag to redirect 1 time)
redirect_to action_path if(redirect_flag)
....
end
Running this would result in the flash[:message] being present regardless of the redirect.
def some_action
logger.debug("Flash[:message] #{flash[:message]}")
(code that may set a flag to redirect 1 time)
redirect_to action_path if(redirect_flag)
....
end
During debugging with the logger referencing flash[] it would only show up when the redirect didn't happen. I could see this being problematic if you added a reference to flash before a redirect and lost it down the line for no apparent reason.
See ruby docs here (Instance protected method: Use at the bottom)

Resources