Rails 3 CSRF token still submits

Viewed 230

I'm working in a legacy rails application, I'm trying to clean up some CSRF vulnerabilities. If I remove the hidden CSRF field from the form I can still successfully submit the form.

The only indication that something is amiss is a warning in the logs: WARNING: Can't verify CSRF token authenticity.

There are some pages where the protect_from_forgery will catch the request and the app will crash if there is no csrf token but its hit and miss depending on the page: eg. the login page works without the token but the update user page does not.

I've tried coming up with a custom strategy (suggested by Marc Gauthier) for protect_from_forgery, something like:

protect_from_forgery with: :MyStrategy

class MyStrategy
  byebug
  def initialize(controller)
    @contriller = controller
  end

  def handle_unverified_request
    puts "HELLO!"
    Rails.logger.warn [
      "handle_unverified_request",
      "#{@controller.controller_name}-#{@controller.action_name}"
    ].join(" - ")

  end
end

This didn't seem to do anything when starting the app the byebug call will pause but I never get the puts message or the error log.

I've also tried the normal strategies such as with: :exception but nothing changes, some pages it works and some it doesn't, but they are consistent.

2 Answers

Usually it is important to protect logged-in users session from CSRF, so in many apps it's customary to disable the protection on login/logout to prevent legitimate users from getting errors. In most cases there's no much harm that can be done by RF if the session is going to be terminated/reset anyway.

Look for skip_before_action :verify_authenticity_token disabling the action that's being installed by protect_from_forgery

Check your testing method - hidden form field is not always necessary because rails also uses X-CSRF-Token headers for ajax forms. To properly test for forgery protection - do an actual forgery attempt, for example using curl.

Check that config.action_controller.allow_forgery_protection is not disabled for development/production, and controller or it's ancestors do not have allow_forgery_protection overloaded and returning false for the request in question. Very unlikely, but app may have some other parts of forgery protection overloded, see request_forgery_protection.rb

PS. byebug in class context is not very useful for this case, more visible way is to raise "Hello CSRF" in handle_unverified_request

It seems like Rails was doing what it was supposed to, just resetting the session, which wouldn't really matter on a login form or a forgotten password form. Since we only had protect_from_forgery (with: :exception did not become the default until sometime in Rails 5).

One would think that adding with: :exception would work, but it does not. The workaround is to extract the contents of the Exception class and put them in the application_controller:

 def handle_unverified_request
     raise ActionController::InvalidAuthenticityToken
  end

This works for the login and forgot password forms, the other forms (which were correctly not allowing bad requests through) remain the same, which is kind of odd because I would assume that both would fail with the InvalidAuthenticityToken raised.

This, of course, leaves the question, why is protect_from_forgery with: :exception not working?

Related