I have a test in rspec for a destroy that returns the following:
ActionController::InvalidCrossOriginRequest:
Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you're doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.
I can run the destroy just fine thru the UI but when it gets tested I get the above warning. Why is that?
Is your destroy request supposed to be an AJAX request? If so, you can assign the xhr option to true.
get some_path, xhr: true
Related
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
If I send a remote: true request to our subdomain controller, our layout renders (which it shouldn't)
Having tested the request.xhr? method in the action, it's returning nil (not the true / false) that you'd expect. This works for non-CORS ajax. It only stops working with CORS-ajax (for subdomain)
Here's the code:
#app/views/controller/view.html.haml
= link_to "test", new_user_session_url, remote: :true
#routes
new_user_session_path GET /login(.:format) admin/users/sessions#new {:subdomain=>"test"}
The response occurs & we get a layout
We want no layout, which works without CORS:
#app/controllers/application_controller.rb
layout :layout_select
def layout_select
if request.xhr?
false
else
devise_controller? ? "admin" : "application"
end
end
We have CORS policy set up & working. However, it seems our CORS-request is not being treated as xhr. Any ideas?
In light of the comments from Mike Campbell, I was able to determine that CORS requests are not treated as ajax (xhr) by default
The problem lies in the headers which are passed on an Ajax request. Standard ajax passes the following header:
#actionpack/lib/action_dispatch/http/request.rb
def xhr?
#env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/
end
This is passed with a "naked" Ajax request, which allows the xhr? method to return either true or false. The issue is in a CORS ajax call, this header is not passed. I don't know why, but it just sends an ORIGIN header instead
The proposed fix from Github suggested to include this:
uri.host != #env['HTTP_HOST'] || uri.scheme != #env['rack.url_scheme'] || uri.port != #env['HTTP_PORT'].to_i
This would basically return a boolean response based on whether the request was from the same domain as the script. Although this was prevented for being unsafe, it lead me to two fixes:
Append a new cors? method to the Rails core
Send the http_x_requested_with header through a CORS request
Considering we are just trying to access a subdomain with the request, we felt that editing the Rails core was slightly overkill, although right. Instead, we found that using the inbuilt headers argument in jquery would help:
$.ajaxSetup({ headers: {"X-Requested-With": "XMLHttpRequest"}});
Whilst this works, I am eager to hear about any security issues it may present. And plus, whether we can access json and other formats with it
It's because of the Same-Origin Policy. Please read carefully this article: HTTP access control (CORS).
The possible solutions are:
make simple request – instead of remote: true do usual form post or maybe $.getJSON() will work (not sure).
implement preflight request – you'll need to implement on your server response for OPTIONS request.
if you need to maintain cookies then you'll also need to write custom ajax code, eg. $.ajax({url: a_cross_domain_url, crossDomain: true, xhrFields: {withCredentials: true}}); and return some specific headers from the server.
I has some features which settings I save in session. But after 1 times reload they reset and session values doesn't exist there.
First load I get session
{"session_id"=>"xxx"}
After save value I get
{"session_id"=>"xxx", "value"=>"100"}
And when I reload my page again I get reset session
{"session_id"=>"xxx"}
Why it can be?
Igor is exactly right. If that happens to be the case in your application, do something like this:
$(document).ajaxSend((event, jqxhr, settings) ->
jqxhr.setRequestHeader("X-CSRF-Token", $('meta[name="csrf-token"]').attr('content'))
jqxhr.setRequestHeader("X-CSRF-Param", $('meta[name="csrf-param"]').attr('content'))
return
)
Now that's CoffeScript, and that assumes you are using JQuery. Regardless, the point is you need to send the CSRF metadata along for the ride with your Ajax requests.
Most likely you're not passing the CSRF token in one of your (probably AJAX) requests. If rails receives invalid CSRF token - it resets the session.
Check out: http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
Update: Vidya is right. You may also want to add the following code to your ApplicationController - to set the XSRF token for AJAX calls
after_filter :set_csrf_cookie
def set_csrf_cookie
cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?
end
Thanks for every one, I find problem but I can't explain it.
Later I remove row from js file app/assets/javascripts/application.js
//= require jquery_ujs
When I put it back, it's stop reset session values.
Originally I had quite usual ajax form with json response:
def create
# created logic omitted as most likely irrelevant
render :json => {:success => true} #over simplified JSON for debug purposes
end
So far so good, works as expected. I've added security on the create action via ssl_requirement gem:
class RegistrationsController < Devise::RegistrationsController
ssl_required :create
# rest of the code omitted, 'create' action as above
end
All of a sudden I get the following in my form response (observing in HttpFox):
Error loading content (NS_ERROR_DOCUMENT_NOT_CACHED)
The create action runs as expected (enforces HTTPS, creates an object but... fails in the browser. To be specific, fails in Firefox (works on chrome). Any clues and ideas will be greatly appreciated.
Regards,
I'm not certain, but I believe your problem has to do with cross-site AJAX requests.
The fact that you are using a different protocol is making firefox believe you are making a cross-site request. Chrome, I believe, is less strict with this restriction when on local. Try visiting the site itself over https and see if the AJAX request goes through.
I tried the auto_complete text field in rails 2.3.3 and the server says it denied request because of no authenticity token. I can see that the helper doesn't automatically create a parameter for it.
How I can manually do this? And I do not want to disable forgery prevention for this autocomplete.
Honestly, disabling the forgery protection isn't a bad idea if you scope this to just JS.
def index
respond_to |format| do
format.html
format.js do
# your autocomplete code
end
end
end
Make your autocomplete call /things.js instead of /things.
As far as I understand it, forgery protection is not needed for JS responses, and ensuring your autocomplete uses a GET method should also solve your problem. You're displaying a list, you're not modifying state, so use a GET and use the js response.
The forgery prevention is part of the form helper, not the field helper. If you use the full RoR form helper it should work. If it doesn't, please edit your question to include the form code and I'll try to help.
having had a similar problem I found just adding ":method => :get" to the "text_field_with_auto_complete" tag fixed it (as per Brian) - I didn't need to disable forgery protection