lemmy
lemmy copied to clipboard
[Security]: No token/session invalidation after logout/reset password
Requirements
- [X] Is this a bug report? For questions or discussions use https://lemmy.ml/c/lemmy_support
- [X] Did you check to see if this issue already exists?
- [X] Is this only a single bug? Do not put multiple bugs in one issue.
- [X] Is this a backend issue? Use the lemmy-ui repo for UI / frontend issues.
Summary
When a user change its password or logs out, the old tokens are still considered valid and can be used to impersonate the user.
Due to the long validity (1 year) of the token, it means that in case of account breach, the user will have no possibility of invalidating previous sessions.
This could be an issue in more scenarios, for example:
- User logged in in a shared computer and forgot to log out
- User token was intercepted by any corporate proxy
- User token was leaked in logs
- User token was access by a malicious extension/XSS/CSRF
- etc.
This comes from the use of only using long lasting Access Tokens.
Steps to Reproduce
1.Authenticate 2.Extract your JWT value from the session cookie that was set as authentication and store it somewhere 3.Proceed to password reset/logout 4. Manually set your authentication cookie to the previous JWT value
Expected: authorization/authentication error.
What happens: you get back your session and full access to it.
Technical Details
The backend should keep track one way or the other of each tokens that should be invalidated by either:
- User logging out
- User resetting its password
- Instance admin forcing the logout of the user (if/when the feature is implemented)
The best practice to handle these type of scenarios is called Refresh Token Rotation, which implements, on top of the Access Tokens used atm, a Refresh Token that can be used to request new Access Tokens.
When the user logs in, the back-end will send him both a one time Refresh Token and an Access-Token. The access token must have a short lifespan, something around an hour or less, the shorter the better. The Refresh-Token has a longer lifetime, several weeks to months depending on the ultimate limit of activity you'd want your user to log-in again, and will be stored in local storage, which is not secure but is mitigated by what I'll explain below.
Once the user's Access Token expires, the client will silently use the Refresh Token to get a new Access Token and a new one time Refresh Token, rinse and repeat.
On the backend, at token refresh, it will check if the Refresh Token has been used previously. If not, just return the new pair of tokens and go on your way. If yes, it means that someone, somewhere has captured the token of the user and tries to impersonate him. In that case, the back-end needs to revoke/invalidate all user refresh tokens that were issued. Since no valid Refresh Token will exist, once the Access Token of the user/attacker expires, it will prompt for a new log-in.
It will mean that an attacker has only, at the maximum, an attack window of 1 Access Token lifespan to use the session maliciously.
The main pain point would be to maintain the list of valid Refresh Tokens in the backend DB, and the last N previous Refresh Tokens to be able to detect reuse. Since JWT should be considered opaque for a client, an endpoint in the backend used to return if an Access Token is still valid or not should be implemented, best being an endpoint that returns the remaining lifetime of the token to the client, and the client should either wait the time needed or refresh the Access Token to avoid a forced logout.
A more detailed explanation can be found here.
Since this is not a directly exploitable vulnerability, and more of an implementation issue, I'm opening this as bug to be able to discuss it a bit more.
Version
N/A
Lemmy Instance URL
N/A
I support this proposal (use of an access token together with a refresh token and moving access token to Authentication header).
A JWT is typically self-contained and therefore the client should be able to validate the token (or at least extract the expiry time exp
claim) without a need for a backend endpoint. That means the client can decide when to refresh the access token all by itself.
As long as the access token lifetime is sufficiently short (10 to 60 minutes) invalidation of an access token upon password change should not be necessary (as stated in the bug report, "an attacker has only, at the maximum, an attack window of 1 access token lifespan").
In the light of the recent vulnerability used to steal tokens, could we get an acknowledgment/feedback on this @dessalines @Nutomic ? The fact that the tokens don't expire will make the post-exploitation fix of this painful for admins.
I'd implement it if I had a clue about Rust but unfortunately this is not the case and I'm not confident enough for my Rust trial to be an important security feature.
I can explain the logic in pseudo code if necessary.
https://github.com/LemmyNet/lemmy/pull/3818