Is It Possible for an Attacker to Parse and Submit Authenticity Token Separately?

I am trying to learn how CSRF vulnerability works, and how authenticity tokens in Rails help us prevent it. There’s one point that I don’t quite understand and would really appreciate it if the Rails security experts in this forum would shed some light on it.

From my understanding, the main problem behind a CSRF attack is that the server application can not differentiate between a genuine request (one that came from the application by a genuine user) vs. a forged request that came from the attacker’s website.

Rails solves this problem by inserting a unique, random token inside each form on the web application. When the form is submitted from the real application by a real user, this token is sent along with it.

My question is, what prevents an attacker to do the following:

  1. write JavaScript code that makes a GET request to the Rails app,
  2. parse its HTML contents to retrieve the authenticity token, and
  3. use it to make a forged request, just like a valid AJAX request would, inserting the token as part of the request?

The Rails app, upon receiving the request would be fooled into thinking the request came from the user’s app since it contains the authenticity token.

Does Rails already protects against the above scenario? If yes, how? or am I missing something that would make the above scenario impossible?

Any information is really appreciated. Thank you!

(This question was prompted by this comment on my Reddit post)

1 Like

Because of CORS you would be able to make a request to another domain. The browser would make an OPTIONS request that would be denied and thus the GET request would not happen.

1 Like

After Dorian’s hint above, and digging into the source, I think I may have found the answer (please correct me if needed).

Rails prevents the above scenario by verifying if the request originated from the same origin by looking at the Origin header. The Origin request header indicates the origin (scheme, hostname, and port) that caused the request.

Here’s the RequestForgeryProtection#valid_request_origin? method which handles this.

def valid_request_origin?
  if forgery_protection_origin_check
    # We accept blank origin headers because some user agents don't send it.
    raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
    request.origin.nil? || request.origin == request.base_url
  else
    true
  end
end

Typically, browsers add the Origin request header to:

Since the attacker’s Origin header won’t match the request’s base_url, the valid_request_origin? method will return false, and Rails will handle the request just like an unverified request.

1 Like