Imagine you are developing a rails API for a front-end client. The client is served independently from rails so no asset pipeline is involved. In other words,
csrf_meta_tag isn’t an option. So you want to protect your site from XSS and CSRF attacks. To prevent XSS, you ensure that the client takes a JWT header upon successful authentication and stores it as a secure cookie. To prevent CSRF, you add the
form_authenticity_token value to a custom header in the response of any GET request to the API. The client includes this CSRF token in all non-GET requests to the API as a custom header. The API supports multiple clients: a react app, an iOS app, and an Android app. For this reason, you cannot effectively enforce cross-origin checks.
Now imagine you’re an attacker. You’ve created a clone of the aforementioned site. With knowledge of the above, you realize that you can fetch the CSRF token via AJAX and include it as a hidden field in your malicious, CSRF form. You call this field
authenticity_token. You then trick a victim to visit your site and incidentally bypass CSRF protections because there is no way to turn off the
authenticity_token parameter in non-GET requests.
I submitted a proposal PR that implements this: https://github.com/rails/rails/pull/35183
It adds a rails configuration option:
true, it disables the ability to send a CSRF token as a parameter. Instead, it must be sent as a custom header. However, this option is
false by default.
AFAIK the attacker’s plan is foiled as
form tags cannot submit custom headers.