protect_from_forgery does not protect PUT/DELETE requests - ruby-on-rails

I made a demo application with rails new demo and then generated a scaffolded user controller with rails generate scaffold User name:string email:string. The scaffolded code has an ApplicationController with protect_from_forgery, so does UserController which derives from ApplicationController.
I run webrick, add a user, cool. Authenticity token works as promised with the POST on /users.
Yet still with Rails 3.0.5 I am able to do a:
niedakh#twettek-laptop:~$ telnet 10.0.0.4 3000
PUT /users/3 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 39
user[name]=vvvvv&user[email]=shiaus.pl
And have the user 3 modified without giving a token:
Started PUT "/users/3" for 10.0.0.4 at 2011-04-02 14:51:24 +0200
Processing by UsersController#update as HTML
Parameters: {"user"=>{"name"=>"vvvvv", "email"=>"shiaus.pl\r"}, "id"=>"3"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
', "updated_at" = '2011-04-02 12:51:24.437267' WHERE "users"."id" = 3s.pl
Redirected to http://10.0.0.4:3000/users/3
Completed 302 Found in 92ms
Also I can do the same with DELETE:
DELETE /users/3 HTTP/1.1
Which gives me:
Started DELETE "/users/3" for 10.0.0.4 at 2011-04-02 15:43:30 +0200
Processing by UsersController#destroy as HTML
Parameters: {"id"=>"3"}
SQL (0.7ms) SELECT name
FROM sqlite_master
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
AREL (0.5ms) DELETE FROM "users" WHERE "users"."id" = 3
Redirected to http://10.0.0.4:3000/users
Completed 302 Found in 180ms
Could you explain to me why can I do those things when I never send any token alongside those requests?

Very Short Version: protect_from_forgery is designed to protect against XSRF attacks from forged HTML FORM elements. PUT and DELETE are not vulnerable to XSRF attacks because HTML forms cannot use PUT or DELETE.
An XSRF (cross site request forgery) attack is where the victim browser is tricked into submitting a forged request to the server without interaction from the user.
Longer version: The reason you are able to do this is you either:
Have no security/login required, or
Have already logged in and are making the requests from script hosted on the same domain, or
Are making the requests via Fiddler or similar, (bypassing the browser's built-in protections).
These are not the scenario protect_from_forgery is designed to protect against.
The purpose of the protect_from_forgery is to protect against XSRF attacks - Cross Site Request Forgery. This occurs when a user visiting an evil website (or a good website with added evil) is tricked into submitting a request to another website. For example you can trick a visitor into making any GET request, like this:
<img src="http://victim.com/victimPage?action=delete&id=ID12345" />
As soon as the victim visits the Evil site, his browser will automatically attempt to retrieve the image. This will obviously not retrieve an image, but meanwhile victim.com will execute the request deleting item ID12345. POST can be forged in a similar way, just create a form, and submit it to the foreign site using script, or else trick the user into clicking on it to submit.
That is where protect_from_forgery comes in: The server sends the token to the client in a hidden field with the form. If no valid token appears, the server concludes that the form which was submitted isn't a submission of a genuine form sent by the server, so the request is rejected as potentially forged.
But you knew that.
The point is that HTTP forms can only use methods GET and POST, not PUT or DELETE. This has two effects:
First, if you get a PUT or DELETE, there is nowhere to put the protect_from_forgery token. PUT or DELETE are not the result of a form submitting, so there is no way for the server to send the token to the client, therefore the client has no token to send back.
Second, since HTML forms can only use POST and GET, if the request is a PUT or DELETE the attacker cannot use a HTML form to force or trick the user into submitting the request. They can use XMLHttpRequest, but XMLHttpRequest does not allow cross-site requests (unless enabled by security settings on both sites).
This means that, provided the domain you host it on does not contain evil code itself, it is not necessary to protect PUT and DELETE from forgery. If the server does contain evil code, the attacker can make arbitrary XMLHttpRequest requests to get a valid token, and therefore easily circumvent the forgery protection anyway.
For a quick description of XSRF try here:
http://blogs.msdn.com/b/bryansul/archive/2008/08/15/rest-and-xsrf-part-one.aspx

Related

What's the actual URL Rails generated for HTTP Verb PATCH?

Please bear with a newbie. I understand how Rails provides simple request with GET for simple URL links like localhost:3000/rooms/11/listing. The format is straight forward as stated in the Routes table. However I am confuse when it comes to PATCH, PUT, DELETE & CREATE. For example the output below, with params, was when I clicked SAVE button. My question, what's the actual URL that Rails generated when I clicked that SAVE button?
Started PATCH "/rooms/11" for 127.0.0.1 at 2019-08-20 05:25:32 +0800
(0.8ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
↳ /usr/local/rvm/gems/ruby-2.6.0/gems/activerecord-5.2.2/lib/active_record/log_subscriber.rb:98
Processing by RoomsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JHiCJ6HoTsgd8SGgLTFiZ9+J9hS9U8hHGvjKf4Lz3uieQ8OO2eFqFEM/D5xocHp/Nd3eA0az9k+okrmNe65BYg==", "room"=>{"home_type"=>"Apartment", "room_type"=>"Private", "accommodate"=>"3", "bed_room"=>"4", "bath_room"=>"3"}, "commit"=>"Save", "id"=>"11"}
I know from console
app.room_path(11)
=> "/rooms/11"
Is this Rails generated URL localhost:3000/rooms/11{"utf8"=>"✓", "authenticity_token"=>"JHiCJ6HoTsgd8SGgLTFiZ9+J9hS9U8hHGvjKf4Lz3uieQ8OO2eFqFEM/D5xocHp/Nd3eA0az9k+okrmNe65BYg==", "room"=>{"home_type"=>"Apartment", "room_type"=>"Private", "accommodate"=>"3", "bed_room"=>"4", "bath_room"=>"3"}, "commit"=>"Save", "id"=>"11"}?
No, the URL generated by app.room_path(11) is http://localhost:3000/rooms/11.
PATCH, PUT, DELETE and POST are called HTTP verbs. CREATE is not an HTTP verb.
One of these verbs goes along with your request, and Rails Router uses it to route the request to the correct Controller and Action.
Requests can have parameters, likes the one you showed here:
{"utf8"=>"✓", "authenticity_token"=>"JHiCJ6HoTsgd8SGgLTFiZ9+J9hS9U8hHGvjKf4Lz3uieQ8OO2eFqFEM/D5xocHp/Nd3eA0az9k+okrmNe65BYg==", "room"=>{"home_type"=>"Apartment", "room_type"=>"Private", "accommodate"=>"3", "bed_room"=>"4", "bath_room"=>"3"}, "commit"=>"Save", "id"=>"11"}
When you clicked on the Save button your browser requested http://localhost:3000/rooms/11 using the HTTP verb POST. The parameters were encoded on the body of the request.
A good place to learn more about this would be the Rails routing guide.
Here is the result from rake routes command. As you can see, GET, PATCH and PUT share the same generated URL (/rooms/:id, in your example /rooms/11). Since Rails 4.0, PATCH is the default verb to update action. Update action is triggered when you're sending a form to a route.

`InvalidAuthenticityToken` error in rails post route

I have a rails app with a POST url which creates some resources.I have a page with a form which takes in all the information and does an AJAX call to the POST url without authenticity token.
Am doing data["authenticity_token"] = "";, before doing the AJAX call.
Parameters logged on serverside are like below
{"utf8"=>"✓", "authenticity_token"=>"", "company_customer"=>{"name"=>"Anand"}}
The resources are created without any error(I have protect_from_forgery with: :exception in my ApplicationController).
But when I tried to call the same POST url from Postman, I get InvalidAuthenticityToken error.
Why am I getting the error?
How does the rails app verify the authenticity of the POST request in first case?
In the first step i.e. from browser , you might be having some session id in the cookies but here not.
Also, if you were hitting by remote:true option, it will take the authenticity token from the page in the hidden field.
For more details , check your logs in both cases.
This token is automatically added in as hidden field when you are using form_for helper method to generate forms, so Rails makes sure the request comes from one of your forms.
You should unprotect your controller action when requested from postman or any other app, see how to here : https://stackoverflow.com/a/22715175/8352929
You can find how CSRF works from here. I recommend you go through it.
Whenever you use form_for Rails adds one hidden input field to your form which looks like the following:
<input type="hidden" name="authenticity_token" value="doLYVxrkhdrzn7zzriHXjFE6ZhNCuXVxLrau4ouENmuKKC/SWp2NMM/MeL/Ji2tDvzNcJHVN/Hc0LIluL3o5QQ==" />
Also, Rails include CSRF token in the meta tags of the website which looks like the following:
<meta name="csrf-token" content="zxnmBxg81JUQPG/C/wb3HRCah0m9Xe2A+gZ5N0Oy7cfwC+dF4hC325WxdVDLfkIxcw/CR/xyaC1phpvZ4EcgQw==" />
So, when you use Rails form_for or something similar to make AJAX call(may be with remote: true) the authenticity token is sent to the server. Which was not present when you tried to send the same request with Postman.
If you copy the CSRF token and add it to Postman params, the request will be completed successfully.

"WARNING: Can't verify CSRF token authenticity" error - CORS with Devise and :token_authenticatable

I have a single page app that authenticates to another domain using CORS. All the requests are JSON requests.
My app can authenticates OK and can make GET requests OK. Authentication is using token_authenticatable. I.e. all requests append '?auth_token=whatever'
So, my actual problem is that when I try to do a PUT request I get a WARNING: Can't verify CSRF token authenticity message in the rails log as well as a CanCan::AccessDenied (You are not authorized to access this page.) exception.
Simply adding skip_before_filter :verify_authenticity_token to the rails controller fixes the issue.
Therefore I can only conclude that my ajax requests are sending an invalid or empty csrf_token.
I don't really understand how that can be, since I believe I am correctly sending the X-CSRF-Token header correctly with each ajax request.
Basically, my app authenticates and Devise sends back an auth_token and a csrf_token:
render :status => 200, :json => {
:auth_token => #user.authentication_token,
:csrf_token => form_authenticity_token
}
I then store those tokens in my ajax app, and using ajaxSend in jQuery, set it up so jQuery passes those tokens with each request:
initialize: ->
#bindTo $(document), 'ajaxSend', #appendTokensToRequest
appendTokensToRequest: (event, jqXHR, options) ->
if not #authToken? then return
if #csrfToken?
jqXHR.setRequestHeader 'X-CSRF-Token', #csrfToken
if options.type is 'POST'
options.data = options.data + (if options.data.match(/\=/) then '&' else '') +
$.param auth_token:#authToken
else
options.url = options.url + (if options.url.match(/\?/) then '&' else '?') +
$.param auth_token:#authToken
I can then see in the chrome network tab, that for each GET request the auth_token param is being sent, as well as the X-CSRF-Token header.
On PUT requests however it doesn't seem to be working though.
My theory is that CORS is stuffing things up. If you make a CORS request, your browser actually makes an additional OPTIONS request first just to check that you do have permission to access this resource.
I suspect that it is the OPTIONS request which is not passing the X-CSRF-Token header, thus rails immediately invalidates the csrf_token on the rails end. Then when jQuery makes the actual PUT request the csrf_token it passes is no longer valid.
Could this be the problem?
What can I do to prove that? Chrome doesn't seem to show me the OPTIONS requests in the network tab to help me debug the issue.
It's not a major issue, because I can just turn the CSRF stuff off. But I'd like to know why it's not working.
I think you'll need to handle the OPTIONS request, which should respond with the various headers that will allow the CORS request, IIRC they are the access-control-allow-method, access-control-allow-origin and access-control-allow-headers. Because the OPTIONS request is failing, the PUT request probably isn't occurring.
I just ran into the same issue. The problem is that the _session_id cookie cannot be sent in CORS. As a result, when Rails tries to verify the token, the session[:_csrf_token] is null and Rails generates a new one before comparison.
To solve the issue, you need to enable cookie sending in CORS. Here is the Mozilla Developer Network reference. Work is needed on both the server and client side to make it work.
Client
- Refer to your client technologies document.
Server
- Set the header Access-Control-Allow-Credentials to true (string) in the response to the preflight (HTTP OPTIONS) call.
In Rails every form submission need CSRF token authenticity.
It use to submit form securely.
The CSRF token(each time) will create newly in rails when we open our Application.
If the CSRF token not passing inside our controller this WARNING will show.
We need to pass this token in all form submissions.

Rails duplicating requests

After correcting some other bugs on the application I found out that one page is being rendered twice on every request.
Rails completes the request normally and after a few, simply starts another request.
At first thought it was a Firebug problem or YSlow doing other requests, but after more tests the duplication remained and I discarded those reasons.
I tried even debugging rails step by step in the request, it goes normally and after completing the first request, I get stopped in the same debugger start line again, this time for the second request.
Printed some lines and things to see clearly on log and it clearly makes 2 requests.
I also found a few wierd requests that I cannot explain also
This bit of log shows the end of the first request and right after that one, there is a wierd index request without layout and then the same request starts again to be processed:
Processing ArtistImagesController#index (for 192.168.0.11 at 2010-07-08 15:10:56) [GET]
Parameters: {"action"=>"index", "locale"=>"pt", "controller"=>"artist_images", "artist_id"=>"2-tom-welling"}
#^ Start of first request
#v end of first request
Completed in 812ms (View: 429, DB: 41) | 200 OK [http://192.168.0.20/artistas/2-tom-welling/imagens]
SQL (0.2ms) SET NAMES 'utf8'
SQL (0.2ms) SET SQL_AUTO_IS_NULL=0
# v wierd request
Processing ApplicationController#index (for 192.168.0.11 at 2010-07-08 15:10:59) [GET]
Rendering rescues/layout (not_found)
-----------------------------------------------------> html
SQL (0.2ms) SET NAMES 'utf8'
SQL (0.2ms) SET SQL_AUTO_IS_NULL=0
# v start of second request
Processing ArtistImagesController#index (for 192.168.0.11 at 2010-07-08 15:11:00) [GET]
Parameters: {"action"=>"index", "locale"=>"pt", "controller"=>"artist_images", "artist_id"=>"2-tom-welling"}
Remembering that all those requests were generated by entering the page only once :/
Searched the code for possible loops or any kind of errors but haven't found any.
Please help is very appretiated
Search your page source for empty image src attributes. For such images the browser requests the site root, which seems to be the case.
I think your page is submitting two times. can i view your page.

AuthLogic one time password (persistence_token) - what config is required to use this?

Can anyone confirm what config exactly is required to make the one time password (persistence_token) work?
From what I can work out so far it is the following, however this isn't working for me so I must be wrong:
pass an additional URL parameter of "user_credentials=xxxxpersistence_tokenxxxx"
question - are there any other URL parameters required beyond this? any user id or username?
have the persistence_token field in my database table (which it is and I can see it populated)
have "acts _as _authentic " in my user model per normal
question: is "acts _as _authentic " required in each of my own models?
When I enter a URL in the browser directly to one of my own model resources following the above I see in the logs:
(a) initial request - Redirected to http://localhost:3000/user_session/new
(b) and then for this redirect:
Processing UserSessionsController#new (for 127.0.0.1 at 2009-12-03
06:14:24) [GET]
Parameters: {"action"=>"new", "controller"=>"user_sessions"}
User Columns (3.4ms) SHOW FIELDS FROM `users`
User Indexes (0.9ms) SHOW KEYS FROM `users`
Rendering template within layouts/application
Rendering user_sessions/new
SQL (0.6ms) SELECT count(*) AS count_all FROM `users` WHERE
(last_request_at > '2009-12-02 20:04:24')
Completed in 182ms (View: 151, DB: 5) | 200 OK [http://localhost/
user_session/new]
(c) But then the web-page ends up on the login page, and not
automatically on the page I was after - i.e. I was expecting that the
one-time password would allow AuthLogic to automatically do the
session and then authentication?
Thanks
PS. Wonder if it related to this authlogic code I found in params.rb
def single_access_allowed_request_types(value = nil)
rw_config(:single_access_allowed_request_types, value, ["application/rss+xml", "application/atom+xml"])
end
I think I have have it now. I did:
Pass the parameter (not header) of user_credentials=<>
Have the single_access_token column in your users table
Put the following method in the users_controller:
private
def single_access_allowed?
true
end
For other controllers (i.e. besides application, user, user_sessions)
I put: "before_filter :require_user" (not sure if there's a way to do this in the
controller that would handle it automatically?)
thanks

Resources