I read from the web2py docs (http://web2py.com/books/default/chapter/29/01/introduction#Security) that
web2py prevents CSRF as well as accidental double submission of forms by assigning a one-time random token to each form. Moreover web2py uses UUID for session cookie.
Would someone be so kind as to explain to me how CSRF is prevented by the above, given that the random token is done for forms on web2py generated pages? Also the UUID in the cookie does not prevent CSRF as cookies get sent with the malicious request automatically, right?
Presumably a malicious site could perform the attack described on https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) via external forms:
... the vulnerable request looks like this:
POST http://bank.com/transfer.do HTTP/1.1
acct=BOB&amount=100
Such a request cannot be delivered using standard A or IMG tags, but can be delivered using a FORM tag:
<form action="http://bank.com/transfer.do" method="POST">
<input type="hidden" name="acct" value="MARIA"/>
<input type="hidden" name="amount" value="100000"/>
<input type="submit" value="View my pictures"/>
</form>
This form will require the user to click on the submit button, but this can be also executed automatically using JavaScript:
<body onload="document.forms[0].submit()">
<form...
These forms won't have the random token protection, or I have misunderstood this terribly?
In addition, won't standard GET requests (which don't change state but return sensitive information) also be vulnerable to CSRF attacks?
Would someone be so kind as to explain to me how CSRF is prevented by the above, given that the random token is done for forms on web2py generated pages?
CSRF is prevented precisely because the random token is available only on web2py generated pages. Therefore, if an attacker attempts to initiate a form submission from a non-web2py-generated page (i.e., a page owned by the attacker), the form submission will not include the random token, and web2py will reject the form submission.
When web2py generates the token, it also stores a copy in the user session on the server (the user session is identified based on the session cookie). When the form is submitted, the submitted token must match the token stored in the session on the server. If there is no token or the token does not match, the submission is rejected.
Also the UUID in the cookie does not prevent CSRF as cookies get sent with the malicious request automatically, right?
Right, the UUID cookie alone does not prevent CSRF (it just makes it harder to hijack the session) -- protection requires the CSRF token.
Presumably a malicious site could perform the attack described on https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) via external forms:
No, as noted above, an external form will not contain the CSRF token and will therefore be rejected by web2py. In other words, it is not the presence of the token that prevents CSRF, it is the absence of the token.
In addition, won't standard GET requests (which don't change state but return sensitive information) also be vulnerable to CSRF attacks?
No, as the attacker does not receive the response returned by the server -- the end user gets that response in the browser. This is why browsers do not allow cross-origin Ajax requests. You might be thinking of cross-site scripting (XSS) attacks (one way that web2py protects against that is by automatically escaping any content written into the view).
Related
As far as I know, we define CSRF as security vulnerabilities that make the user perform a user-specific action without the user's consent by using various scripts by a malicious attacker. For example, our malicious attacker convinced the user to enter the website where the following code is running to change the user's email address.
<form action="https://examplefornonsafewebsite.com/email/change" method="POST">
<input type="hidden" name="email" value="malicioususer#example.com">
</form>
<script>
document.forms[0].submit();
</script>
In such a case, if the website does not have CSRF protection, the user's email address will be changed without the user's consent as a result of an action taken by the user. My question is, "What separates the CSRF token from a session cookie?" How can the session cookie be accessed but not the CSRF token while the malicious attacker is making the user change? After all, since this is done from the user's browser, shouldn't the CSRF token be accessible as well?
A session cookie should be HttpOnly and is then not accessible via Javascript, but it is automatically included in every request to the examplefornonsafewebsite.com domain from which it was issued.
To make the examplefornonsafewebsite.com web page safe, it must obtain an CSRF token prior to the making the POST request. This token is valid only in combination with ("is bound to") the session cookie and is obtained
either when the browser loads the <form> by navigating to https://examplefornonsafewebsite.com/email/form and this contains an <input type="hidden" name="X-CSRF-Token" value="..."/>
or by making a GET request with the Javascript command fetch("https://examplefornonsafewebsite.com/email/gettoken") and reading the CSRF token from the response. This token must then be included in a subsequent POST request
fetch("https://examplefornonsafewebsite.com/email/change", {
method: "POST",
headers: {"Content-Type": "x-www-form-urlencoded"},
body: "email=malicioususer#example.com&X-CSRF-Token=..."
})
The examplefornonsafewebsite.com web page can use either option to include the CSRF token when it makes an email change request.
But neither option is possible for the attacker: In the first option, the browser has already navigated to the examplefornonsafewebsite.com web page and the attacker's page is no longer involved at all.
In the second option, the attacker's page can make the fetch request, but will not be able to read the response, because this is a cross-origin (CORS) request: It is made by Javascript from the attacker's web page but goes to the different origin https://examplefornonsafewebsite.com/. Then, since the response contains no Access-Control-Allow-Origin header, the fetch request is rejected with a "TypeError: Failed to fetch" and the attacker is not able to read the CSRF token.
Importantly, if the attacker tried to obtain a CSRF token prior to the attack (by making a request to https://examplefornonsafewebsite.com/email/gettoken with their own browser), they might get one, but it would not be bound to the victim's session cookie, so it would be worthless.
To summarize: The session cookie1 cannot be accessed via Javascript, neither by an attacker nor by the legitimate web page, but will be sent by the browser regardless. The CSRF token can be obtained only by the legitimate web page, by virtue of the CORS protocol. Only by combining both do you ensure that
the email change request comes from the user's browser (otherwise it would lack the session cookie) and
the user's browser is currently visiting the legitimate web page examplefornonsafewebsite.com (otherwise the CORS protocol would prevent the CSRF token from being obtained).
The CSRF protection mechanism therefore relies on CORS.
1 This mechanism works even if the cookie in question is not a session cookie, see Should CSRF protection token be giving before authenticating?
Recently I'm studying some basics of Web security and there is something I couldn't understand.
How do anti-CSRF tokens work in SPA-API communications?
As far as I understand, anti-CSRF is used in SPA-API communications as followings;
The browser sends a login request to the API.
The API servers generates a token and sends it back to the browser.
The browser stores it, and when the browser makes the next request, token with be sent together.
The API can make sure that the request came from the genuine front-end because it contains the token.
A question pops up in my mind--how can it prevent CSRF?
If the token is stored in cookie, it will automatically be sent to API whenever a request happens, like usual session cookies. And even if it's stored in other storage(like session storage or local storage), it can be accessed using JavaScript.
So once users are attracted to the attacker's site, anti-CSRF tokens are completely useless.
On the top of that, I can't understand what's the difference between anti-CSRF token and usual cookies used in authentication/authorization……
Maybe I've made a terrible misunderstanding about how anti-CSRF tokes work. Please put a finger on what's wrong about it.
One of the most common CSRF vulnerabilities exists when an attacker can submit a request to an endpoint using an authenticated user's cookies. If you're not using cookies (i.e., to authenticate a user's request) or some other automatic authentication technique (like HTTP Basic Authentication), then there's generally no need for CSRF tokens.
Example #1:
Let's say you're using a REST API that depends on an access token or bearer token for authentication. This token is usually submitted in the HTTP Authorization header (not a cookie). In this case, as long as authentication isn't automatic (e.g., using a cookie), then there's no need for a CSRF token.
Example #2:
In this case, let's say the browser does send a session cookie to a web service/API to authenticate the request. Then, yes, you would be vulnerable to CSRF if anti-CSRF controls aren't implemented. One way to prevent this is to provide an anti-CSRF token to the browser when the SPA is loaded. The browser can then send that token with the request to the endpoint. The web service will then have to validate that token when the request is received.
There are a number of ways that this validation can occur. This could be done using double-submit cookies, a cookie-to-header token, cryptographic techniques, or even a shared database.
Using Anti-CSRF Tokens:
Anti-CSRF tokens generally should not be stored in cookies. As stated in the OWASP CSRF Prevention Cheat Sheet:
A CSRF token can be included in the <meta> tag. All subsequent calls
in the page can extract the CSRF token from this <meta> tag. It can
also be stored in a JavaScript variable or anywhere on the DOM.
However, it is not recommended to store it in cookies or browser local
storage.
For example, an anti-CSRF token might get embedded in the page as:
<meta name="csrf-token" content="{{ csrf_token() }}">
Where the csrf_token() calls some server-side function that embeds the token in the tag.
It can then be read in JavaScript using:
let csrf_token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
And then transmitted to the server when an API request is made (e.g., in a X-CSRF-Token header in a POST request). In addition, the token should be unique to the session.
However, even if a token were to be stored in a cookie, the cookie could be set with the HttpOnly header. This prevents the cookie from being read by JavaScript. This is more so useful in mitigating cross-site scripting (XSS) attacks.
Additional Info:
This StackExchange Security question includes a lot of good info on using CSRF protection in REST APIs.
Other good resources about CSRF in general:
https://portswigger.net/web-security/csrf
https://owasp.org/www-community/attacks/csrf
Is it a good practice to save the csrf token in a cookie or is it better to use a hidden field in a form? Also is it good to regenerate csrf token every user request like what captchas are doing?
Thanks
It is best to include it in the form. The idea behind a CSRF token is that it is not passed passively (e.g. if a malicious user is able to trick the browser into accessing some URL that does something nasty). Cookies are passed passively.
The best explaination to this question can be found on OWASP website at OWASP CSRF Prevention Cheat Sheet page.
Firstly, using cookie for a CSRF token can not help much because all cookies, even the secret ones, will be submitted with every request. All authentication tokens will be submitted regardless of whether or not the end-user was tricked into submitting the request.
Secondly, the application can include hidden input parameter in the form with a common name such as "CSRFToken". The value of this token must be randomly generated such that it cannot be guessed by an attacker.
Furthermore, Challenge-Response is another defense option for CSRF. It can be implemented in following ways:
CAPTCHA
Re-Authentication (password)
One-time Token
The CSRF cookie is certainly open to attack but implementation safe as the session value will always be checked against a submitted token value either stored in the body or header of the request so I can't see a reason against. The double submit (http only cookie vs post data) or token synchronizer (session vs post data) patterns outlined on the OWASP website are good pratices and both use cookies.
Double submit as mentioned earlier moves the storage to the client so is considered stateless but either way two tokens for comparison, of which one always remains unknown to the attacker.
I had one doubt about CSRF prevention. A lot of sites say that CSRF can be prevented by using 'tokens' which are randomly generated per session.
Now my doubt is,
suppose i have a function like :
$.post("abcd.php",{'fbuid':userid,'code':'<?php echo md5($_SESSION['randcode']); ?>'}
now this md5 hash would obviously be visible to any hacker through the source code.He could simply open this page, generate a token, and keep the page open, so that the session doesn't get destroyed, and useanother tab or anything else , to start hacking,
No ?
Or is my idea of tokens incorrect ?
Thanks for your help :D
I think you are misunderstanding what needs to be done. To protect against CSRF you need to create a token and save it for that session. Then you need to append all your submits and AJAX calls with that token.
For another person to send you to a page on your website they would need to have access to the request with in the same session. It is true that one could parse the HTML and look for the token. But when they try to request a http call on your website they will have a new session created. The new session will have a new token which will not match the token that was passed.
Next you will ask what if you can copy the cookies and the session id as a result. This is not something that is protected. I can simply sit anybody's computer and copy all their cookies and I will then be logged in as them.
As kapep points out, you are confusing the two seperate issues of input validation and cross-site form posting. You must validate your inputs anyway, so the case of your malicious attacker using his own session token is already handled if you have sound input validation. CSRF protection is not there to protect the data, it is simply to ensure that only forms from your own application can post data back to that application. the CSRF protection is simply stopping other people being able to post data directly into your app from forms they put up on their own site.
One specific point to be aware of is that the token is visible to any javascript running on your page, so as soon as you have a cross-site scripting (XSS) vulnerability, your CSRF protection is defeated.
See Cross-site scripting and the prevention cheat sheet
You should use a per request token.
Generate a token and store it in the session.
Pass the token to the client.
Execute actions.
Destroy the token.
The token is safer and cannot be used more than one time.
I would define a stolen token as a token that is used by someone else, and not the one you have send the token to. If you send someone a token he can't really steal it from himself.
If you are concerned that a user can run a malicious script with his own token, your design seems to be broken. You can't prevent a user from sending data that you didn't indented to receive. It's your job to validate any data, the session token is just there to identify multiple requests by the same client.
It could be a security issue if you send that token over unsecured http. Then it could easily be stolen by monitoring the clients network.
I am writing an application (Django, it so happens) and I just want an idea of what actually a "CSRF token" is and how it protects the data.
Is the post data not safe if you do not use CSRF tokens?
Cross-Site Request Forgery (CSRF) in simple words
Assume you are currently logged into your online banking at www.mybank.com
Assume a money transfer from mybank.com will result in a request of (conceptually) the form http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount>. (Your account number is not needed, because it is implied by your login.)
You visit www.cute-cat-pictures.org, not knowing that it is a malicious site.
If the owner of that site knows the form of the above request (easy!) and correctly guesses you are logged into mybank.com (requires some luck!), they could include on their page a request like http://www.mybank.com/transfer?to=123456;amount=10000 (where 123456 is the number of their Cayman Islands account and 10000 is an amount that you previously thought you were glad to possess).
You retrieved that www.cute-cat-pictures.org page, so your browser will make that request.
Your bank cannot recognize this origin of the request: Your web browser will send the request along with your www.mybank.com cookie and it will look perfectly legitimate. There goes your money!
This is the world without CSRF tokens.
Now for the better one with CSRF tokens:
The transfer request is extended with a third argument: http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971.
That token is a huge, impossible-to-guess random number that mybank.com will include on their own web page when they serve it to you. It is different each time they serve any page to anybody.
The attacker is not able to guess the token, is not able to convince your web browser to surrender it (if the browser works correctly...), and so the attacker will not be able to create a valid request, because requests with the wrong token (or no token) will be refused by www.mybank.com.
Result: You keep your 10000 monetary units.
(Your mileage may vary.)
EDIT from comment worth reading by SOFe:
It would be worthy to note that script from www.cute-cat-pictures.org normally does not have access to your anti-CSRF token from www.mybank.com because of HTTP access control. This note is important for some people who unreasonably send a header Access-Control-Allow-Origin: * for every website response without knowing what it is for, just because they can't use the API from another website.
Yes, the post data is safe. But the origin of that data is not. This way somebody can trick user with JS into logging in to your site, while browsing attacker's web page.
In order to prevent that, django will send a random key both in cookie, and form data.
Then, when users POSTs, it will check if two keys are identical. In case where user is tricked, 3rd party website cannot get your site's cookies, thus causing auth error.
The Cloud Under blog has a good explanation of CSRF tokens. (archived)
Imagine you had a website like a simplified Twitter, hosted on a.com.
Signed in users can enter some text (a tweet) into a form that’s being
sent to the server as a POST request and published when they hit the
submit button. On the server the user is identified by a cookie
containing their unique session ID, so your server knows who posted
the Tweet.
The form could be as simple as that:
<form action="http://a.com/tweet" method="POST">
<input type="text" name="tweet">
<input type="submit">
</form>
Now imagine, a bad guy copies and pastes this form to his malicious
website, let’s say b.com. The form would still work. As long
as a user is signed in to your Twitter (i.e. they’ve got a valid
session cookie for a.com), the POST request would be sent to
http://a.com/tweet and processed as usual when the user clicks the
submit button.
So far this is not a big issue as long as the user is made aware about
what the form exactly does, but what if our bad guy tweaks the form
like this:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
<input type="submit" value="Click to win!">
</form>
Now, if one of your users ends up on the bad guy’s website and hits
the “Click to win!” button, the form is submitted to
your website, the user is correctly identified by the session ID in
the cookie and the hidden Tweet gets published.
If our bad guy was even worse, he would make the innocent user submit
this form as soon they open his web page using JavaScript, maybe even
completely hidden away in an invisible iframe. This basically is
cross-site request forgery.
A form can easily be submitted from everywhere to everywhere.
Generally that’s a common feature, but there are many more cases where
it’s important to only allow a form being submitted from the domain
where it belongs to.
Things are even worse if your web application doesn’t distinguish
between POST and GET requests (e.g. in PHP by using $_REQUEST instead
of $_POST). Don’t do that! Data altering requests could be submitted
as easy as <img src="http://a.com/tweet?tweet=This+is+really+bad">,
embedded in a malicious website or even an email.
How do I make sure a form can only be submitted from my own website?
This is where the CSRF token comes in. A CSRF token is a random,
hard-to-guess string. On a page with a form you want to protect, the
server would generate a random string, the CSRF token, add it to the
form as a hidden field and also remember it somehow, either by storing
it in the session or by setting a cookie containing the value. Now the
form would look like this:
<form action="https://example.com/tweet" method="POST">
<input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
<input type="text" name="tweet">
<input type="submit">
</form>
When the user submits the form, the server simply has to compare the
value of the posted field csrf-token (the name doesn’t
matter) with the CSRF token remembered by the server. If both strings
are equal, the server may continue to process the form. Otherwise the
server should immediately stop processing the form and respond with an
error.
Why does this work? There are several reasons why the bad guy from our
example above is unable to obtain the CSRF token:
Copying the static source code from our page to a different website
would be useless, because the value of the hidden field changes with
each user. Without the bad guy’s website knowing the current user’s
CSRF token your server would always reject the POST request.
Because the bad guy’s malicious page is loaded by your user’s browser
from a different domain (b.com instead of a.com), the bad guy has no
chance to code a JavaScript, that loads the content and therefore our
user’s current CSRF token from your website. That is because web
browsers don’t allow cross-domain AJAX requests by default.
The bad guy is also unable to access the cookie set by your server,
because the domains wouldn’t match.
When should I protect against cross-site request forgery? If you can
ensure that you don’t mix up GET, POST and other request methods as
described above, a good start would be to protect all POST requests by
default.
You don’t have to protect PUT and DELETE requests, because as
explained above, a standard HTML form cannot be submitted by a browser
using those methods.
JavaScript on the other hand can indeed make other types of requests,
e.g. using jQuery’s $.ajax() function, but remember, for AJAX requests
to work the domains must match (as long as you don’t explicitly
configure your web server otherwise).
This means, often you do not even have to add a CSRF token to AJAX
requests, even if they are POST requests, but you will have to make
sure that you only bypass the CSRF check in your web application if
the POST request is actually an AJAX request. You can do that by
looking for the presence of a header like X-Requested-With, which AJAX
requests usually include. You could also set another custom header and
check for its presence on the server side. That’s safe, because a
browser would not add custom headers to a regular HTML form submission
(see above), so no chance for Mr Bad Guy to simulate this behaviour
with a form.
If you’re in doubt about AJAX requests, because for some reason you
cannot check for a header like X-Requested-With, simply pass the
generated CSRF token to your JavaScript and add the token to the AJAX
request. There are several ways of doing this; either add it to the
payload just like a regular HTML form would, or add a custom header to
the AJAX request. As long as your server knows where to look for it in
an incoming request and is able to compare it to the original value it
remembers from the session or cookie, you’re sorted.
The site generates a unique token when it makes the form page. This token is required to post/get data back to the server.
Since the token is generated by your site and provided only when the page with the form is generated, some other site can't mimic your forms -- they won't have the token and therefore can't post to your site.
The root of it all is to make sure that the requests are coming from the actual users of the site. A csrf token is generated for the forms and Must be tied to the user's sessions. It is used to send requests to the server, in which the token validates them. This is one way of protecting against csrf, another would be checking the referrer header.