CSRF tokens for Rails API application - ruby-on-rails

In Rails API applications we don't have out-of-box CSRF protection. In particular, storing access tokens (JWT for example) in the localStorage is not recommended, it is recommended to store in cookies (with httpOnly flag, SameSite, and etc.). But this time we're vulnerable to a potential CSRF attack. In a full-stack generated Rails app, a CSRF token is generated and embedded every time we open a form. But, I don't know and couldn't find anything how we protect against CSRF using tokens in Rails API apps. Do we have best practices or could anyone suggest an approach? I use access and refresh JWTs.

This is a usual tradeoff of API design, and you can choose from several different approaches, with different risk profiles.
You can store the access token in localStorage or sessionStorage, accessible to javascript, and accept the risk. The risk obviously is mostly around cross-site scripting (XSS), because this way javascript will have access to the token, and in case of XSS, it can be accessed by the attacker leading to session compromise. If talking about an API, responses should have the content type set to application/json, which makes the API itself protected from XSS in modern browsers. However, that does not mean the client (presumably a single page javascript app) is also protected, that can easily be vulnerable and leak the token. Some frameworks are better protected by default against XSS, some are not so much, and you might have checks like static scans in your SDLC that give you a level of assurance that might allow you to accept this risk. Also if your SPA needs to send the token to multiple origins (different api endpoints), you don't really have another option. In this case the token can be sent as a request header, and CSRF is not an issue.
Or you can exchange XSS for CSRF, by storing the token in a httpOnly cookie. This is generally considered more secure, because CSRF in general is a lower risk vulnerability (but still significant ofc). In that case you will not be able to send the token to different origins, but XSS will also not have access. This does not eliminate XSS for the whole application, but at least the token will be secure. The cost is now you will have to deal with CSRF. One way to do so is the samesite attribute to cookies. Using that for the token cookie will prevent most cases of CSRF, but it is a UX tradeoff, users of some browsers will not be protected, and some cases might be missed when using the lax option for samesite (like when a GET request changes state). Only having samesite as the protection will likely also be flagged in a penetration test for the reasons above.
If based on the above you decide to have more protection, you can implement something like double submit, and still keep it stateless, which these APIs many times aim to be. In case of double submit, you generate a random value, set it as a cookie (either directly on the client, or by a response from the server), and copy the same value from the cookie in a request header. The server only has to compare the value from the cookie to the one from the request, if they match, the request is ok. The reason this works is because an attacker on their own domain (origin) cannot set or read cookies for the victim application domain, this is ensured by the same origin policy of browsers.
A somewhat different approach might be applying a message authentication code (like HMAC) computed from the whole request and a shared secret (like the API key), and checking that on the server, but this is a can of worms, it's easy to have unprotected fields not covered by the HMAC, the server needs to have access to plaintext api keys so it can compute the hmac and so on - it's not at all straightforward to get this right).
Note that if the client app is vulnerable to XSS, that negates any CSRF protection as the attacker will have a way to get any secret from the client and with that, perform any request, with any computed field (like a valid token).

Related

Preventing replay attacks using nonce

I'm trying to secure endpoints on a website by protecting them with a nonce token.
I've been looking for out-of-the-box solutions to save time, but there doesn't seem to be any?
I've found out about anti-forgery tokens within asp.net, but these can be re-used and don't function like a nonce (use once, expires immediately after use).
Because of this, the anti-forgery token can't prevent replay attacks if the attacker has a cookie with the asp.net session identifier in it as wel (please correct me if I'm wrong).
Are there any easy to use guides or out-of-the-box solutions that allow me to use a nonce to protect my endpoints from replay attacks?

Forging a Cross Site Request Forgery (CSRF) token

I had a look at Rails' ActionController::RequestForgeryProtection module and couldn't find anything related to using secrets. Basically, it uses secure PRNG as a one time pad, xors, computes Base64 and embeds into HTML (form, tags). I agree that it is impossible for an attacker to guess what a PRNG generates, but nevertheless I can generate (or forge if you like) a similar token, embed it into my "evil" form and submit. As far as understand Rails compares ( verifies) it on the backend. But I can't fully understand why it is secure. After all, I can generate my own token exactly like Rails does. Could someone clarify how the security is achieved?
You might misunderstand what this protects against, so let's first clarify what CSRF is, and what it is not. Sorry if this is not the point of confusion, might still be helpful for others, and we will get to the point afterwards.
Let's say you have an application that allows you to say transfer money with a POST request (do something that "changes state"), and uses cookie-based sessions. (Note that this is not the only case csrf might be possible, but by far the most common.) This application receives the request and performs the action. As an attacker, I can set up another application on a different domain, and get a user to visit my rogue application. It does not even have to look similar to the real one, it can be completely different, just having a user visit my rogue domain is enough. I as the attacker can then send a post to the victim application's domain, to the exact url with all the necessary parameters so that money gets transferred (the action will be performed). The victim user need not even know if this happens in xhr from javascript - or I can just properly post a form, the user gets redirected, but the harm is done.
This is affected by a few things, but the point is that cross-origin requests are not prevented by the same origin policy, only the response will not be available to the other domain - but in this case when server state changes in the victim application (like money gets transferred), the attacker might not care much about the response itself. All this needs is that the victim user that visits the attacker's page while still being logged in to the victim application. Cookies will be sent with the request regardless of the page the request is sent from, the only thing that counts is the destination domain (well, unless samesite is set for the cookie, but that's a different story).
Ok, so how does Rails (and similar synchronizer token solutions) prevent this? If you lok at lines 318 and 322 in the source, the token received from the user is compared to the one already stored in the session. When a user logs in, a random token is generated and stored for that particular user, for that particular session. Subsequent requests that change state (everything apart from GET) check if the token received from the client is the same that's stored in the session. If you (or an attcker) generate and send a new one, that will be different and the request will fail validation. An attacker on their own website cannot guess the correct token to send, so the attack above becomes impossible. An attacker on a different origin also cannot read the token of a user, because that is prevented by the same origin policy (the attacker can send a GET request to the victim app, but cannot read the response).
So to clarify, CSRF is not a protection against parameter tampering, which might have caused your confusion. In your own requests, if you know the token, you can change the request in any way, send any parameter, the CSRF token does not protect against this. It is against the attack outlined above.
Note that the description above is only scratching the surface, there is a lot of depth to CSRF protection, and Rails too does a little more, with some other frameworks doing a lot more to protect against less likely attacks.

PKCE: Surely hacker can still steal access token?

From my understanding, the advantage that Authorization Code Flow has over Implicit Flow is that with ACF, the access token gets sent to a server side app rather than to a browser app. This makes the access token much harder to steal, because the access token never reaches the browser (and is thus not susceptible to a Cross Site Scripting attack).
I would have thought that PKCE would try to solve this issue. But it does not. The access token is still sent to the browser. Hence it can still be stolen.
Is there something I am missing here?
Many thanks.
Authorization Code Flow (PKCE) is considered superior security to the previous solution of Implicit Flow:
With implicit flow the access token was returned directly in a browser URL and could perhaps be viewed in logs or the browser history
With Authorization Code Flow this is handled better, with reduced scope for exploits:
Phase 1: A browser redirect that returns a one time use 'authorization code'
Phase 2: Swapping the code for tokens is then done via a direct Ajax request
PKCE also provides protection against a malicious party intercepting the authorization code from the browser response and being able to swap it for tokens.
Both are client side flows and their reason for existing is to use access tokens in public clients. Authorization Code Flow (PKCE) is the standard flow for all of these:
Single Page Apps
Mobile Apps
Desktop Apps
In the SPA case the token should not be easily stealable, especially if stored only in memory as recommended. However, there are more concerns when using tokens in a browser, since it is a dangerous place, and you need to follow SPA Best Practices.
In the browser case there are other options of course, such as routing requests via a web back end or reverse proxy in order to keep tokens out of the browser, and dealing with auth cookies in addition to using tokens.
I think your are right. The tokens are not in a http-only cookie, and are therefore accessible by a malicious script (injected via an XSS attack). The attacking script can read all tokens (after a successful and normal auth flow) from local storage (or wherever they got put) and use them.
I think CORS protections should prevent the malicious script from sending the tokens out to an attacker directly, which would be a devastating failure, as this potentially includes a long lived refresh token. Therefore, I suspect configuring CORS correctly is super critical when using these local-client based flows (by local-client I mean a browser, mobile app, or native PC app).
In short, these local-client flows can be made secure, but if there is an XSS attack, or badly configured CORS, then those attacks can become extremely dangerous - because the refresh token could potentially be sent to the attacker for them to use at will in their own good time, which is about as bad as an attack can get.

Should an oAuth server give the same accessToken to a same client request?

I am developing an oAuth2 server and I've stumbled upon this question.
Lets suppose a scenario where my tokens are set to expire within one hour. On this timeframe, some client goes through the implicit auth fifty times using the same client_id and same redirect_uri. Basically same everything.
Should I give it the same accessToken generated on the first request on the subsequent ones until it expires or should I issue a new accessToken on every request?
The benefits of sending the same token is that I won't leave stale and unused tokens of a client on the server, minimizing the window for an attacker trying to guess a valid token.
I know that I should rate-limit things and I am doing it, but in the case of a large botnet attack from thousands of different machines, some limits won't take effect immediately.
However, I am not sure about the downsides of this solution and that's why I came here. Is it a valid solution?
I would rather say - no.
Reasons:
You should NEVER store access tokens in plain text on the Authorization Server side. Access tokens are credentials and should be stored hashed. Salting might not be necessary since they are generated strings anyway. See OAuth RFC point 10.3.
Depending how you handle subsequent requests - an attacker who knows that a certain resource owner is using your service and repeat requests for the used client id. That way an attacker will be able to impersonate the resource owner. If you really return the same token then at least ensure that you authenticate the resource owner every time.
What about the "state" parameter? Will you consider requests to be the "same" if the state parameter is different? If no then a botnet attack will simply use a different state every time and force you to issue new tokens.
As an addition - generally defending against a botnet attack via application logic is very hard. The server exposing your AS to the internet should take care for that. On application layer you should take care that it does not go down from small-bandwidth attacks.
You can return the same access_token if it is still valid, there's no issue with that. The only downside may be in the fact that you use the Implicit flow and thus repeatedly send the - same, valid - access token in a URL fragment which is considered less secure than using e.g. the Authorization Code flow.
As a thumb rule never reuse keys, this will bring additional security in the designed system in case of key capture
You can send different access token when requested after proper authentication and also send refresh token along your access token.
Once your access token expires, you should inform user about that and user should re-request for new access token providing one-time-use refresh token previously provided to them skipping need for re-authentication, and you should provide new access token and refresh token.
To resist attack with fake refresh token, you should blacklist them along with their originating IP after few warnings.
PS: Never use predictable tokens. Atleast make it extremely difficult to brute force attacks by using totally random, long alpha-numeric strings. I would suggest bin2hex(openssl_random_pseudo_bytes(512)), if you are using php.

Store JWT token in cookie

This is my setup:
1 authentication server which gives out JWT token on successfull
authentication.
Multiple API resource servers which gives information (when the user
is authenticated).
Now I want to build my ASP.NET MVC frontend. Is it ok to take the token, which I receive after authentication, and put it in a cookie so I can access it with every secured call I need to make? I use the RestSharp DLL for doing my http calls. If it has a security flaw, then where should I store my token?
I would use this code for the cookie:
System.Web.HttpContext.Current.Response.Cookies.Add(new System.Web.HttpCookie("Token")
{
Value = token.access_token,
HttpOnly = true
});
You’re on the right path! The cookie should always have the HttpOnly flag, setting this flag will prevent the JavaScript environment (in the web browser) from accessing the cookie. This is the best way to prevent XSS attacks in the browser.
You should also use the Secure flag in production, to ensure that the cookie is only sent over HTTPS.
You also need to prevent CSRF attacks. This is typically done by setting a value in another cookie, which must be supplied on every request.
I work at Stormpath and we’ve written a lot of information about front-end security. These two posts may be useful for understanding all the facets:
Token Based Authentication for Single Page Apps (SPAs)
https://stormpath.com/blog/build-secure-user-interfaces-using-jwts/
Are you generating your own JWTs?
If yes, you should consider using a signing algorithm based on asymetric encryption, like "RS256" or "RS512" -- this way you can verify the claims in your client application without sharing the private secret.
Do you really need to pass the JWT into the Cookie?
It might be safer to just put a random id in your Cookie, which references the JWT access token, and do the de-referencing magic on the server which serves your web-app.

Resources