CSRF Tokens vs Session Cookies - csrf

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?

Related

How do anti-CSRF tokens work in SPA-API communications?

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

JWT stored in cookie - security concerns

I'm building SPA app with server side rendering, using JWT-based authentication.
Current implementation is:
JWT token is issued and transferred to client after successful username and password verification
token is then stored in cookie (not HttpOnly) - the purpose of that is to avoid the need to login again after full refresh or closing page
logging out deleted cookie with token
Authorization header is attached to every API request if token exists
full SSL traffic
I can't store token in LocalStorage because of server side rendering, also the there is no HttpOnly because I need to access cookie in order to construct Authorization header.
What are the possibilities of stealing the token in such architecture?
One major risk is that any single cross-site scripting vulnerability in your application could be used to steal the token from the cookie, because it's not httpOnly (while I understand why that is the case). XSS in a javascript-heavy application like an SPA is very common and hard to avoid.
Also you're saying the token is kept in the cookie so that after closing the browser, the user is still logged in. On the one hand, that's bad practice, a user closing the browser probably expects being logged out. On the other hand, this means the cookie is persisted to disk, so it is much easier for an attacker to steal it from the client.
Another thing that comes to mind is cross-site request forgery (CSRF), but if I understand correctly, authentication is actually based on the Authorize header, where the token is copied in each request. If that's the case, CSRF is not an issue for you (but it would be, if sending the token in the cookie was enough).
So at the very least, I think you should
not use a persisted cookie for the token
try to minimize the chance of XSS (eg. by automatically scanning your code, but that will never be 100%, also by carefully choosing secure by default technologies)
make sure auhentication is based on the Authorize header and not the cookie
Still mainly because of the XSS risk, I would probably not recommend doing it this way in a security-critical application.

CSRF protection in web2py

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).

CSRF token timeout

This page describes a use case to explain CSRF attacks (16.1):
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html
But if the user is indeed logged in the bank's website, then isn't it possible for the evil website to make a GET request to get a fresh CSRF token, and craft a POST without needing the user at all?
The answer must be no, otherwise CSRF token would be useless, but I don't understand why?
The answer is "no" and the reason is Same-Origin Policy.
SOP means that a page from evil.com cannot read any response to requests that it may send to example.com. Most direct means to send a request will be blocked by the browser (SOP), but there are many workarounds. For example, evil.com could send
GET requests by imbedding an <img>, <script>, <css> and setting src="http://example.com/path" (or <a href="http://example.com/path">).
POST requests by submitting a form.
Since evil.com cannot read any of the responses, it cannot read the CSRF token.

What is a CSRF token? What is its importance and how does it work?

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.