Cross-site request forgery, also known as a one-click attack or session riding and abbreviated as CSRF or XSRF, is a type of malicious exploit of a website whereby unauthorized commands are transmitted from a user that the website trusts. Unlike Cross Site Scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user’s browser.Lets take a look at the schematic of the CSRF
- Step1: The Victim connect to secure Bank websites and logs into his account.
- Step2: A cookie set in the Victims browser containing the session id of the victim.
- Step3: Victim trips into visiting a malacious page.
Step4: Victim recieves a html page containig malacious hidden form. - Step5: A web request is executed from the victims browser carrying the context of cookie set in Step2.
- Step6: Bank Server completes the web requests.
Conclusion: Banking server failed to verify the validity of the web request and hence executed it without the victims knowledge.
Now we know what CSRF is, lets see how Rails help prevent CSRF.
As Rails uses MVC architecture, Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks by including a token in the rendered html for your application. This token is stored as a random string in the session, to which an attacker does not have access. When a request reaches your application, Rails verifies the received token with the token in the session. Only HTML and JavaScript requests are checked, so this will not protect your XML API (presumably you’ll have a different authentication scheme there anyway). Also, GET requests are not protected as these should be idempotent. The requests are validated using the following peice of code
def verified_request? | |
!protect_against_forgery? || request.get? || request.head? || | |
form_authenticity_token == params[request_forgery_protection_token] || | |
form_authenticity_token == request.headers['X-CSRF-Token'] | |
end |
This can be enabled with the protect_from_forgery
method, which will perform the check and handle unverified requests, if the token doesn’t match. And it will add a _authenticity_token
parameter to all forms that are automatically generated by Rails. It is recommended that this method is added in your ApplicationController
, and later on you can skip it in other controllers if not required.
With all this in mind lets take a look at Rails source code.
class ApplicationController < ActionController::Base | |
protect_from_forgery | |
end | |
def protect_from_forgery(options = {}) | |
self.request_forgery_protection_token ||= :authenticity_token | |
prepend_before_action :verify_authenticity_token, options | |
end | |
def verify_authenticity_token | |
unless verified_request? | |
logger.warn "Can't verify CSRF token authenticity" if logger | |
handle_unverified_request | |
end | |
end | |
def handle_unverified_request | |
reset_session | |
end |
From the code, we figure out, CSRF protection resets session and lets the request through when CSRF token verification fails.
This in itself is a CSRF vulnerability since it allows anyone to logout users by directing their browser to a page that requires CSRF protection
With Rails 4 application, the ApplicationController
now passes a parameter to protect_from_forgery
.
class ApplicationController < ActionController::Base | |
# Prevent CSRF attacks by raising an exception. | |
# For APIs, you may want to use :null_session instead. | |
protect_from_forgery with: :exception | |
end | |
def protect_from_forgery(options = {}) | |
self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) | |
self.request_forgery_protection_token ||= :authenticity_token | |
prepend_before_action :verify_authenticity_token, options | |
end | |
def verify_authenticity_token | |
unless verified_request? | |
logger.warn "Can't verify CSRF token authenticity" if logger | |
handle_unverified_request | |
end | |
end | |
def handle_unverified_request | |
forgery_protection_strategy.new(self).handle_unverified_request | |
end |
This raises an exception when an unverified request is encountered. Same behavior can be achieved with Rails 3 by overriding the default
handle_unverified_request method.