How to fix Brakeman redirect issue with multiple rest endpoints - ruby-on-rails

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.

Related

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

Preventing discoverability in a RESTfully routed model

I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.

Is it possible to specify two root pages in Rails (one for anonymous user another for logged in user)

I am building a product that has static pages and dynamic pages(product related). Both category of pages have different release life cycle. The marketing team working with the designer, release the static pages and the product pages are released by the engineering team.
The static pages reside in public/home and they are self contained. They don't need access to the Rails infrastructure other than providing links.
In this setup, I am trying to implement the following behavior:
When an un-authenticated visitor launches http://www.xyz.com, the user should be taken to the static landing page.
When an authenticated visitor launches http://www.xyz.com, the user should be taken to the product landing page (LandingsController, index action).
In my current implementation, I check if the user is authenticated in the Rails world and render the static page OR the product page.
I want to know the following:
1) How do you handle such scenarios?
2) Is there a way to avoid entering the Rails stack for static home page.
3) Is there a customization for the root_path method to return different root based on the context
1) How do you handle such scenarios?
The common answer would look like this:
class LandingsController < ApplicationController
before_filter :login_required
def index
...
end
...
private
def login_required
if not_logged_in? # This methods depends on your authentication strategy
send_file "/your/static/path/#{params[:action]}", :type => "application/html charset=utf8;"
return false # Halt chain
end
end
send_file documentation
And, depending on the correspondence between each of your actions and your templates, you can further abstract the login_required method into the ApplicationController, and validate if the file exists.
2) Is there a way to avoid entering the Rails stack for static pages
Yes. You have to take my word for it, because I haven't done it myself, but you can use a Rack middleware to do that. Here is an example of how to do something similar, with the exception that instead of a redirect, you would serve the file statically (just set the headers and the results of File.read as content) This depends on the authentication library you're working with, though.
3) Is there a customization for the root_path method to return different
root based on the context
You cannot define a conditional route (that is, defining multiple routes in the routes.rb file), but you can override the root_url method in ApplicationController, assuming you are using a named path root in your route definitions. Something like
class ApplicationController
def root_url(*options)
if logged_in?
"/return/something/custom"
else
super(*options)
end
end
end
This, however, sound really a bad idea, since 1) You should point to the same url, and let the controller handle the request (your links should be blind of where to take you), and 2) It may potentially break other stuff that rely on the root_url and root_path methods.
Unfortunately, Rails' routing can only route requests to different controllers based on something in the request, making the per-request session data just out of reach. Your current implementation is certainly the most common strategy. I am guessing something like this:
def index
if logged_in?
# any logged in logic you need.
else
render :file => 'public/home', :layout => false
end
end
The only way to refactor this to make it feel less "icky" is to move that render call to a before_filter. Since the filter will have rendered?, your action won't get invoked at all. Of course, you could also choose to redirect_to another location for authenticated (or non-authenticated) requests in a before filter, which would solve the problem entirely.
The only thing you could do would be based on the non-existence of the session cookie.
Write a middleware component or Rack application (etc.) that explicitly handles the request if no session cookie is present. Similarly, you could use middleware to re-write the request, and then pass it onto the application layer.
Use a similar strategy as #1, but do it via web server configuration (Apache or nginx), avoiding the Rails app entirely.
But, it's definitely possible for someone to have a session and yet not be logged in (e.g. if they went to another page which you didn't handle this way), or even have invalid session data, so you wouldn't be able to actually eliminate the code you have now. These changes would only serve to increase the performance of the session-less requests, but unless those pages are causing a significant problem (which I doubt), so I would not recommend doing so.

redirect_to doesn't work well for RESTful apps?

As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.

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