I have a simple Rails 6 app where I use a form
<%= bootstrap_form_with(model: event, local: true, html: { multipart: true }) do |form| %>
In the generated HTML I have this is the header
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="SOME TOKEN" />
And in the form
<input type="hidden" name="authenticity_token" value="SOME OTHER TOKEN" />
Usually all works fine, but sometimes I get an error
Can't verify CSRF token authenticity.
The log shows be that the token has been passed in the parameters and I cannot find out why the validation fails. I ma sure that there is no malicious use or anything like that.
Are there any reasons why a CSRF token loses its validity? Maybe it's only valid for a certain time?
CSRF protection in Rails works by storing a random value as a field in the form being submitted, and also in the user session. If the values don't match when a form is submitted, Rails rejects the form submission request.
If you're using the default cookie session store in Rails, then
sessions won't expire (until the cookie does). If you're using
something else (file or DB backed sessions), then yes, if those
sessions expire, the form submission will fail with a CSRF error.
So if you're using cookie based sessions (the default), check the cookie expiry. If that looks OK, it's probably some other issue.
Related
I was working with Rails 5.2.3 and got the below error:
undefined method content_security_policy? for class.
After looking at the error I found that it generates due to csp_meta_tag in layout application.html.erb.
So, what exactly it does mean and how does it help?
My interpretation is this: CSP is used to allow only trustable scripts in your app. Therefore, it's used to protect your application from unknown scripts that could be injected to damage (or hack) your app. But how does it do that?
Here is the code for csp_meta_tag:
def csp_meta_tag
if content_security_policy?
tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
end
end
According to the documentation:
Returns a meta tag “csp-nonce” with the per-session nonce value for
allowing inline tags.
But what is a nonce and how is it used?
A nonce is a random string. It is cryptographically made by a secure function. The only way that a script would run in the user's browser was if the script had the nonce attach to it.
Example:
<script nonce="AsnfAsf%28217%(*">
<!-- Some code here -->
<script>
Therefore, an attacker that wants to inject a script in your app, won't be able to do it, because he/she doesn't have this random string. Furthermore, the nonce is regenerated every time the browser page is loaded, making the attack even more difficult.
I have an MVC 4.0 site that uses AntiForgeryTokens to protect against CSRF attacks.
What I am finding is that I can reuse an old __RequestVerificationToken after new sessions have been opened.
I start with adding to the .cshtml
#Html.AntiForgeryToken()
I then decorate the Post Action method in the controller with
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DoSomething(MyViewModel model)
{
}
I then do the following :-
Log in to admin site and scrape the __RequestVerificationToken value.
Log Out
Log Back In again (in theory there should now be a new token created)
Then submit the form below in a new tab in the same browser (see that I have used the token from the previous request).
<html>
<body>
<form action="http://adminsite.com/DoAction" method="POST">
<input type="hidden" name="id" value="26" />
<input name="__RequestVerificationToken" type="hidden" value="rt95zr0voZbgLga117YNBfwwLpTU8onGCDmZ4IQEisvhiNH_9ISTtsbDzIVgIkRUzwH81PpbrTRGK4MLSp3S3j-JMNjsJTL04TRl2J38rNz8KKomL98gLjEiJoXgMXFt0qaJ8tPaB4_PvGo8ATaxLcA2" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
What I find is that even though I have logged out and back in again, I am still able to use the old __RequestVerificationToken and I can successfully post the form.
After reading through the documentation (http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages) there is no mention of invalidation of token.
My questions are :-
Shouldn't it now be invalid?
If not, why not?
Is there a way of making the __RequestVerificationToken invalid after logging out and back in again?
I'm attempting to get full page caching in Rails but I've hit a big of a snag with regards to CSRF - or perhaps just my understanding of it. I currently have the form_authenticity_token string stored in a cookie that JS can access and rewrite the header tags with.
There are two places I find tokens in my generated HTML:
1) In the head
<meta name="csrf-token" content="[hash]">
2) Inside a form's hidden input element
<input type="hidden" name="authenticity_token" value="[different hash]">
As indicated, these hashes are different from one another (in development mode where caching isn't enabled). Why are they different? Why is it that I can delete the head meta tags and leave the form input alone and the request is allowed? Yet when I delete the form input tag and leave the headers the request is rejected?
Effectively this means the head tags are useless, no? I can rewrite the form input tag to the value in my cookie just like I did with the header tags, but since they are different from one another I'm cautious as to what the end result might mean especially when it comes to full page caching.
Application Controller contains:
protect_from_forgery with: :exception
before_filter :csrf_cookie
def csrf_cookie
cookies['authenticity-token'.freeze] = {
value: form_authenticity_token,
expires: 1.day.from_now,
secure: (Rails.env.staging? || Rails.env.production?)
}
end
Browsing SO on another issue led me to the answer. In short, Rails helps out jQuery users by inserting the CSRF token into ajax requests automatically. It looks for it in the meta tags.
So having the CSRF token inside the form is useful for when submitting POST requests and having it in the head is useful for saving time/effort/mistakes with ajax requests.
Perhaps it's good to have it in both also because you may want to do an ajax request when there isn't a form present. If there IS a form and javascript is disabled, having it in the header doesn't do anyone any favours as it won't be included in the POST request.
As to why they are different, I can only guess it has something to do with the algorithm at the time of generation...but that's neither here nor there as both tokens work.
A single Razor view contains several forms, each with its own call to #Html.AntiForgeryToken()
<form id="f1">
#Html.AntiForgeryToken()
</form>
<form id="f2">
#Html.AntiForgeryToken()
</form>
As I understand it, both of these anti forgery tokens should be the same.
<form id="f1">
<input name="__RequestVerificationToken" type="hidden" value="duVT4VtiYybun-61lnSY1ol__qBwawnELooyqT5OSrCJrvcHvDs_Nr9GLxNxwvBaI4hUcKZVkm6mDEmH2UqNorHD1FnJbKJQLWe8Su_dhy_nnGGl5GhqqC3yRGzcxbBM0" />
</form>
<form id="f2">
<input name="__RequestVerificationToken" type="hidden" value="ZMISz3IWHU_HCKP4FppDQ5lvzoYhlQGhN1cmzKBPz4OgDzyqSUK3Q1dqvw1uHsb4eNyd9U3AbFcnW8tR7g1QS8Dyhp0tFc-ee1sfDAOqbLCcgd3PDnLCbXx09pnPREaq0" />
</form>
Why are the values different?
Surely they should be the same, because they are sent in the same Response from the server?
The documentation says nothing about calling it once only.
I am afraid that won't work.
The antiforgery token also travels in the response cookie, so yours will contain just the last token, and therefore the first form will always fail.
You can try to do something like this:
#{
ViewBag.Title = "Index";
var token = Html.AntiForgeryToken();
}
<form id="f1">
#token
</form>
<form id="f2">
#token
</form>
I have tried it, and the same token is used in both forms.
The values HAVE to be different. Not because of implementation inner workings or API voodoo but because each form represents an independent request to the server.
If the forms had the same token, once an attacker knew the token value for one form he would be able to trick the server into accepting the data sent by the other forms, although they were not submitted by the user, defeating the protection provided by the AntiCSRF Token.
The objective of the token is to provide a random id parameter, making it very hard for the attacker to fool the application into thinking that it was the logged in user that filled the form.
For those that are not acquainted with CSRF attacks, please take a look here.
The Anti-Forgery token is not compared directly - the server has to unprotect it first and compare the protected data inside. Having different protected tokens doesn't necessarily mean they contain differing data.
What the System.Web.Helpers.AntiXsrf.TokenValidator compares is the SecurityToken inside the decrypted AntiForgeryToken instances. These instances, however, also contain an AdditionalData field, a UserName field and a ClaimUid field.
Also, the SecurityToken inside the AntiForgeryToken is directly copied from the (current if it is valid, else the freshly generated) AntiForgery cookie inside AntiForgeryWorker.
Given that all that data is serialized, encrypted then encoded, you may have variances in the protected token due to differences between the AdditionalData between tokens or it is likely due to a pseudorandom nonce used in the encryption process (which it likely uses, since I can test 2 completely different tokens as valid against the same cookie).
Surely they should be the same, because they are sent in the same Response?
The Response has nothing to do with it. #Html.AntiForgeryToken() is a static method of HtmlHelper which generates a unique token that is added to the html and the response cookie. Your calling the method multiple times so your generating multiple tokens.
If it did not generate a unique token each time it would hardly be secure.
#Html.AntiForgeryToken() basically generate encrypted value based on the cookie and form data. So if you declare and use this #Html.AntiForgeryToken() for each than it will generate two different _RequestValidationToken. Better declare one global #token variable with #Html.AntiForgeryToken() method and it will create a single token for each request.
These are not equal antiforgerytoken commands.
The MVC is generate uniqe for all uniqe commands.
So you shouldnt wait same generated ID. As I know :)
Thank you buffjape, for your comment
I've never used Rails (that only could answer my question), but I see it puts CSRF tokens in each page with forms.
What I don't understand is why it uses two meta tags for that:
<meta name="csrf-token" content="<%= form_authenticity_token %>" />
<meta name="csrf-param" content="authenticity_token" />
Why not just the csrf-token meta?
<meta name="csrf-token" content="<%= form_authenticity_token %>" />
What's the use of csrf-param?
Rails allows you to do a lot of configuration under the hood related to the CSRF token. If you like, you can change the name of the param -- but if you do, the jQuery UJS driver needs to know the name of the new parameter (since it's used in Ajax requests). That's why there are two meta params here: the first is the actual authenticity token, obviously, but the second is required by Rails' JavaScript drivers in order to even know the name of the first one. (You can see this in action in the jQuery driver or the Prototype driver.)
You could argue this gets you into some kind of crazy loop -- why can't you rename the csrf-param meta tag with another meta tag? I think this was done to allow Rails to easily adopt existing CSRF solutions without needing a lot of manual overrides. Also it allows your apps to be slightly future-proofed. If the HTML5 standard ever adopts an official tag for CSRF tokens, and Rails opts to change the default CSRF tag in a future version, the JavaScript drivers won't have to change at all.
Ultimately, I think that's closest to the real reason this exists: it's insurance against future changes in the CSRF system, preventing unnecessary and possibly extremely annoying deprecations down the road.
csrf-param contains the name of the parameter and csrf-token is the value of the parameter.
So, your form would look like this :
<form action="/" method="post">
<input type="hidden" name="authenticity_token" value="OWY4NmQwODE4ODRjN2Q2NTlhMmZlYWEwYzU1YWQwMTVhM2JmNGYxYjJiMGI4MjkGTfMTVi
MGYwMGEwOA==">
…
</form>