Should CSRF tokens be server-side validated? - django

First, I want to make sure I got the CSRF token workflow right.
The server sets a cookie on my machine, on the site's domain. The browser prevents access to this cookie from other domains. When a POST request is made, I send the CSRF token to the server that then compares it to my cookie. It they're not the same, a 403 Forbidden page is returned.
Now, if I manually change the value of the token in the cookie and send that new value in the POST request, should the server return a 403 or not? Does the server need to validate the token agains a value stored on the server or on the cookie?
I am using the default implementation of CSRF protection on Django 1.3 (https://docs.djangoproject.com/en/1.3/ref/contrib/csrf/) and it validates the token sent in the request against the token only.

How do you send the token?
Usually, the tokens should be some function (with a secret key - known only to the server; e.g., MAC) of the cookie! not the cookie.
Than the flow is as follows:
1. Client sends the server request with a cookie.
2. Server returns a web page with CSRF token(s) for different purposes (e.g., forms or just a simple get requests via the URL).
3. The client performs some action (via POST or GET) and sends request with the token (in the request body or in the URL) and with the cookie.
4. The server is stateless, but it can verify that the request was sent by the same client by calculating the function (with the secret key that the server knows) on the cookie (or on part of it), and comparing the output with the token.
In the case of CSRF, the cookie is automatically appended to the request by the browser, but the attacker (that probably even doesn't know the cookie) cannot add the corresponding tokens.
I believe you should do something like this.
Now, if I manually change the value of the token in the cookie and
send that new value in the POST request, should the server return a
403 or not? Does the server need to validate the token agains a value
stored on the server or on the cookie?
The server should be stateless (usually). You don't want to verify the token every request against some value in a database or something like that. It is better to verify against the cookie.
In that case, if you change the token, than it probably won't match the cookie, and you should send 403.

TL;DR: Yes, either you, or the framework you are using, needs to have server-side logic to validate a CSRF token. It cannot be a cookie, it has to be something that requires the user to be on your page, versus click on a link an attacker provides.
You've got the workflow pretty much correct. The first step is to generate a cryptographically random string that cannot be predicted by an attacker. Every programming language has its own construct to do this, but a 24 - 32 character string should be good to serve the purpose.
Before we get to the next step, let's make sure we know what threat we're dealing with - we don't want an attacker to make a request on behalf of the user, so there should be something that is accessible to the browser that requires the user to perform an action to send the token, BUT, if the user clicks on something the attacker has set up, the token should not be sent.
Given this, the one way this should NOT be done is using cookies. The browser will automatically send cookies every single time a request is made to the domain the cookie is set on, so this automatically defeats our defense.
That said, let's go to the next step, which is to set this token in a way that is verifiable by you on the server side, but not accessible to the attacker. There's multiple ways to do this:
1) A CSRF Header: This is done in many node.js/Express installations - the CSRF token is sent as a header, to be specific, a X-CSRF-Token header. After generating this token, the server stores this in the session store for that particular cookie. On the front end, the token is stored as a JavaScript variable, which means only requests generated on that particular page can have the header.. Whenever a request is made, both the session cookie (in the case of node.js, connect.sid) and the X-CSRF-Token is required for all POST/PUT/DELETE requests. If the wrong token is sent, the server sends a 401 Unauthorized, and regenerates the token, requesting login from the user.
<script type="text/javascript">
window.NODE_ENV = {};
window.NODE_ENV.csrf = "q8t4gLkMFSxFupWO7vqkXXqD";
window.NODE_ENV.isDevelopment = "true";
</script>
2) A Hidden Form Value: A lot of PHP installations use this as the CSRF defense mechanism. Depending on the configuration, either a session specific or a request specific (latter is overkill unless the application needs it) token is embedded in a hidden form field. This way, it is sent every time a form is submitted. The method of verification varies - it can be via verifying it against the database, or it can be a server-specific session store.
3) Double Submit Cookies: This is a mechanism suggested by OWASP, where in addition to sending the session cookies via the header, you also include it in the forms submitted. This way, once you verify that the session is valid, you can verify that the form contains the session variables also. If you use this mechanism, it is critical to make sure that you validate the user's session before validating CSRF; otherwise, it introduces flaws.
While building/testing this mechanism, it is important to note that while a lot of implementations limit it to POST/DELETE/PUT transactions, this is because it is automatically assumed that all sensitive transactions happen through this verbs. If your application performs sensitive transactions (such as activations) using GET, then you need this mechanism for GET/HEAD also.

Related

Dj rest auth using JWT Token stored in HttpOnly cookies

I'm making a Django Rest Framework application with a JWT authentication with tokens stored in HttpOnly cookies. Authentication is performed via reading the access cookie. I'm using a dj-rest-auth library for this purpose, but I'm a little confused of the security aspect of such method. Knowing that having authentication data stored in cookies can be used to perform CSRF attack, how can one protect their web against such attacks for a specific case I've described above? All cookies are set to be SameSite=Lex.
Do I need to also send X-CSRFTOKEN header obtained from the backend? That would mean that every request to the api will need to have that header. What should be the optimal setup having all those libraries?
The OWASP foundation has created a detailed cheat-sheet on how to prevent from CSRF attacks: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
Two things worth remembering:
CSRF is a concern only for data-changing requests, so POST/PUT/PATCH/DELETE methods. An attacker performing a CSRF attack can't read the response from the server, so GET requests are not your concern.
The most common way of preventing CSRF is with a csrf token. The server should create the token for the user's session and the token is saved in frontend, but not in a cookie. Then you send that token either in a header or as part of the request body. The server then validates that the token is the same as the one it has for that user session.
If you don't want to persist the token in your server, you can additionally save it in a cookie. So the frontend will keep the token in two places - one in memory/in a hidden form field/etc. and another one in the cookie. Then the server can check if the value from the cookie matches the value from header/body.
All cookies are set to be SameSite=Lex.
Do I need to also send X-CSRFTOKEN header obtained from the backend?
SameSite is not supported by older browsers. As per caniuse, it is supported by 90% browsers in use globally.
Arguments AGAINST implementing CSRF tokens:
I think it's a low risk. If you think your users will be among those 90%, then don't bother with CSRF tokens.
Since you're using JWTs, I assume the frontend is going to be a modern SPA app. If you don't expect it to run on older browsers, then, again, don't bother with CSRF tokens.
Arguments FOR implementing CSRF tokens:
Lax offers no protection on GET requests. You'll have to make sure, today and in future as well, that you never modify server state in GET requests. If you implement CSRF tokens, you'll have one less thing to worry about. Or you can also use SameSite=Strict value.
My opinion:
Personally, I'd just implement CSRF tokens. It's a one time setup and most frameworks already take care of this. So, why not?
P.S.: I'm not sure if you've a typo there, but it is Lax, not Lex.

django rest framework - session auth vs token auth, csrf

I have DRF set with the default settings. My ajax clients works fine with the session authentication. I want another remote server to consume the same API as the javascript clients.
My login code is simple:
class Login(APIView):
def post(self, request, *args, **kwargs):
user = authenticate(username=username, password=password)
if user is None:
return Response(status=status.HTTP_401_UNAUTHORIZED)
login(request, user)
# ...
The issue is when I use a client from another host, like python requests, I get a CSRF error. According to DRF docs, I think I should use a token authentication instead.
Questions:
Why do I need token authentication? The sessionid cookie is already a token, why I can't use it both for ajax clients and software clients? Thus avoid another separate db table for the tokens.
Since I do want to use only session authentication, how to enforce CSRF only for ajax clients?
It's not really compulsory to use Token Authentication, just that Session Authentication is liable to CSRF attacks. You can try to prevent this using CORS mecahnisms and CSRF tokens but it is still not entirely safe.
To be honest, Token Authentication doesn't entirely work well with browsers either as the token can be easily retrieved using the browser's developer tools if you don't use a very complex and sophiscated mechanism for handling it. It's just simpler to use it for third-party apps.
Though CSRF attacks are only applicable to browsers(Ajax clients), you shouldn't try to exlude them because the method of checking if the request is from an ajax client request.is_ajax() depends on whether the client has set the X-Requested-With header. It may be possible for an attacker to remove this header. Again I would advise that you also add CORS verification which is the method used by browsers to safeguard against CSRF attacks in addition to Django's CSRF tokens. This is typically done using Django-cors-headers package
And why token authentication isn't subject to csrf attacks? It does not seem more secure to me than the session. As I see it, both of them use HTTP headers to pass a token (in token authentication is in the Authorization header, and session is a cookie which is also a header)
Tokens are sent using the Authorization header(you could also decide to use a custom header but this is the standard for interoperability) while session auth uses cookies which are automatically sent by the browser and this is why they're susceptible to CSRF attacks. For tokens, the client has to explicitly set the header so it has to know the token, while the attacker will not even have to know what is stored in the cookies as the browser just automatically sends whatever is in its cookie store for that site.
You shouldn't enable CSRF protection for ajax clients only – it doesn't make any sense. How can you differentiate between "ajax" client and "normal" client? If it will be done e.g. by some query param, then an attacker can just use this "normal" link to do bad things.
When you're using token-based authentication, an attacker cannot just use common URL to make your request be authenticated transparently. That's why only session-based authentication requires a valid CSRF token to be included into request.
So for security reasons there are 2 options:
either use session-based authentication, but then you need to send auth cookie and CSRF token with every request;
or use token-based authentication, which is simpler since you only need to provide auth token e.g. as a query param.
Can I use token authentication that gets the token from the standard django_session table? just use that as token?
In theory you can achieve that by writing some custom authentication middleware that will use token from query param and match it with session table, but that's generally bad idea.
First, there's no such a big overhead in using one more table, but without it you're making the system harder to read and maintain.
Second, it will make the system more fragile as well. Since sessions and tokens are 2 completely different entities, they can have e.g. different lifetime. Sessions can be flushed, their TTL can be shorter/longer than token TTL. For example, default django session TTL is 2 weeks. Do you want to complicate remote server logic to get new token every 2 weeks? Or imagine the situation when token is compromised. Do you want to force ajax client to log out as well?

Security of storing Bearer token in cookies

My SPA uses React as front end and laravel API as backend.
When the user logs in (via axios and api), the api returns an access (Bearer token) as response. I use the react-cookie framework to store the access token as cookie in the Browser. This cookie will be read and used for any future request.
Is this the right way to do?
Isn't cookie data just something in the Browser that can be easily obtained by any attacker? Since it is just a file one the computer somewhere.
What is stopping an attacker from grabbing that cookie, impersonate as that user and start performing actions that requires authentication?
The token has a life span of lets say 1 year. It will only be refreshed every time the user logs in. I understand that if I set the life span shorter it will be more secure. However that will mean the user would have to log in constantly?
-----Update-----
Im not sure if any of the provided solution answered my question. A SPA app is front end based and the request can be from anywhere such as Postman, Mobile app, or any third party device that wish to talk to my backed server. So those device needs a way to store some access token locally to be used for any future request.
The only way I know this could happen is for my server to send some auth token to the requester and have it store it somewhere to be used for next request.
In this case, Im not sure if CSRF token or any other means would help my concern?
Just like facebook, if I clear my cache, I will have to re-login. That means facebook is storing something on my location computer so I can be automatically authenticated next time
I just want to add some disadvantages of storing tokens in cookies that you should also be aware of:
The max size of a cookie is only 4kb so that may be problematic if
you have many claims attached to the token.
Cookies can be vulnerable to cross-site request forgery (CSRF or
XSRF) attacks. Using a web app framework’s CSRF protection makes
cookies a secure option for storing a JWT. CSRF can also be partially
prevented by checking the HTTP Referer and Origin header. You can
also set the SameSite=strict cookie flag to prevent CSRF attacks.
Can be difficult to implement if the application requires
cross-domain access. Cookies have additional properties (Domain/Path)
that can be modified to allow you to specify where the cookie is
allowed to be sent.
------- Update -----
You can also use cookies to store the auth token, even it is better (at least in my opinion than using local storage, or some session middleware like Redis). And there are some different ways to control the lifetime of a cookie if we put aside the httpOnly and the secure flags:
Cookies can be destroyed after the browser is closed (session
cookies).
Implement a server-side check (typically done for you by
the web framework in use), and you could implement expiration or sliding window expiration.
Cookies can be persistent (not destroyed
after the browser is closed) with an expiration.
Your JS should not have access to the cookie. There are flags you can set on cookies that will help protect them and make sure they are only used for the correct purposes.
The HttpOnly flag is set on the cookie then JS will not be able to access it but it will still be sent with any request.
The SameSite flag will ensure that the cookie is only sent back to the site that gave it to you. Which prevents leakage.
The Secure flag will make it only send the cookie over a secured connection to prevent someone from sniffing it out of your web traffic.
Edit
You might want to lookup an authorization workflow but the gist of it is this:
User logs in with username and password
A JSON web token is issued upon login from the backend and sent to the browser
The JWT(JSON web token) can be stored in a cookie in the Web Storage(Session Storage) on the browser
Subsequent requests to the REST API will have the token embedded in the header or query string for authorization. With that form of authorization, your REST API understands who is making the request and what kind of resource to return based on the level of authorization
Please see #tpopov answer as he also made some really good points.

Preventing CSRF with implicit flow and JWTs?

I'm reading through openid connect document ATM and it says:
Put into a browser cookie the ID token can be used to implement lightweight stateless sessions.
IIUC we want to avoid using cookies in order to ensure avoid CSRF attacks, since the browser will send the cookie with all requests, and if the user loads an image (While logged in to notsosecurebank.com) with the URL:
href="http://notsosecurebank.com/transfer.do?acct=AttackerA&amount;=$100">Read more!
The browser will send the access token (Since it's a cookie) and this allows the attack to happen.
If the token is storage in session or local storage and only sent via REST / XHR requests, then there's no way for CSRF to happen? Did I understand this correctly?

Django CSRF cookie accessible by javascript?

On django website, https://docs.djangoproject.com/en/dev/ref/contrib/csrf/ it states:
The CSRF protection is based on the following things:
1. A CSRF cookie that is set to a random value (a session independent nonce, as it is called), which other sites will not have access to.
2. ...
Then, it also states the csrf token can be obtained from cookie by javascript:
var csrftoken = $.cookie('csrftoken');
Aren't these two statements conflicting? Say there is a Cross Origin attack, then the attacker can just obtain the CSRF token from cookie, and then make a POST request with the CSRF token in the header? Can someone explain this please?
UPDATE
I realize now that, only the javascript from the same origin is allowed to access the cookie. A follow-up question is:
If a POST request automatically adds the cookie as part of the request, and django's csrf cookie value is the same as csrf token, then a malicious cross source request will still have the correct CSRF token anyways? (in cookie)
I believe that this post answers your updated question:
Because of the same-origin policy, the attacker cannot access the cookie indeed. But the browser will add the cookie to the POST request anyway, as you mentioned. For this reason, one must post the CSRF token from the code as well (e.g. in a hidden field). In this case, the attacker must know the value of the CSRF token as stored in the victim's cookie at the time she creates the malicious form. Since she cannot access the cookie, then she cannot replicate the token in her malicious code, and the attack fails.
Now, one might imagine other ways of storing the token than in the cookie. The point is that the attacker must not be able to get it. And the server must have a way to verify it. You could imagine saving the token together with the session on the server-side, and storing the token in some "safe" way on the client side ("safe" meaning that the attacker cannot access it).
Here is a quote from OWASP:
In general, developers need only generate this token once for the current session. After initial generation of this token, the value is stored in the session and is utilized for each subsequent request until the session expires. When a request is issued by the end-user, the server-side component must verify the existence and validity of the token in the request as compared to the token found in the session. If the token was not found within the request or the value provided does not match the value within the session, then the request should be aborted, token should be reset and the event logged as a potential CSRF attack in progress.
In the end, the security needs two things:
The CSRF token must be sent from the code, which means that the malicious code must know it.
The CSRF token must be stored in some "safe" place for comparison (the cookie is convenient for this).
I am not a specialist, but this is my understanding of the problem. Hope it helps.
From the name CSRF (Cross Site Request Forgery), you can already guess the attacker must perform the request from "cross site" (other site).
"The key to understanding CSRF attacks is to recognize that websites typically don't verify that a request came from an authorized user. Instead they verify only that the request came from the browser of an authorized user." - quoted here
So for sites that don't prevent CSRF attacks, the attacker can send the malicious request from anywhere: browsers, emails, terminal... Since the website doesn't check the origin of the request, it believes that the authorized user made the request.
In this case, in every Django form, you have a hidden input called "CSRF token". This value is randomly and uniquely generated at the time the form rendered, and will be compared after the request has been made. So the request can only be sent from the authorized user's browser. There is no way (which I know of) an attacker can get this token and perform the malicious request that can be accepted by Django backend.
Clear enough?